diff --git a/.gitignore b/.gitignore index dac9fcceb..7b69a62d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ /.idea +/error.log /nbproject /npm-debug.log +/rainloop.sublime-project +/rainloop.sublime-workspace /rainloop/v/0.0.0/static/css/*.css /rainloop/v/0.0.0/static/js/*.js /rainloop/v/0.0.0/static/js/**/*.js diff --git a/README.md b/README.md index 4ce2b542d..0d358d727 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ RainLoop Webmail ================== -## About +[![Join the chat at https://gitter.im/RainLoop/rainloop-webmail](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/RainLoop/rainloop-webmail?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) Simple, modern & fast web-based email client. @@ -21,4 +21,4 @@ It's not recommended to use in production environment. **Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported (CC BY-NC-SA 3.0)** http://creativecommons.org/licenses/by-nc-sa/3.0/ -Copyright (c) 2014 Rainloop Team \ No newline at end of file +Copyright (c) 2015 Rainloop Team diff --git a/build/langs.php b/build/langs.php new file mode 100644 index 000000000..9d6248989 --- /dev/null +++ b/build/langs.php @@ -0,0 +1,76 @@ + $aSectionValue) + { + foreach (\array_keys($aSectionValue) as $sParamKey) + { + if (isset($aFromLang[$sSectionKey][$sParamKey])) + { + $aEngLang[$sSectionKey][$sParamKey] = $aFromLang[$sSectionKey][$sParamKey]; + } + else + { + echo $sSectionKey.'/'.$sParamKey.','."\n"; + $iCount++; + } + } + } + + return $aEngLang; +} + +function saveLangStructure($sLangFile, $aLang) +{ + $aResultLines = array(); +// $aResultLines[] = '; '.$sLangFile; + + foreach ($aLang as $sSectionKey => $aSectionValue) + { + $aResultLines[] = ''; + $aResultLines[] = '['.$sSectionKey.']'; + + foreach ($aSectionValue as $sParamKey => $sParamValue) + { + $aResultLines[] = $sParamKey.' = "'. + \str_replace(array('\\', '"'), array('\\\\', '\\"'), \trim($sParamValue)).'"'; + } + } + + \file_put_contents(LANGS_PATH.'/'.$sLangFile, implode("\n", $aResultLines)); +} + +$sNL = "\n"; +$aEngLang = \getLangStructure('en.ini'); + +$aFiles = \glob(LANGS_PATH.'/*.ini'); +foreach ($aFiles as $sFile) +{ + $iCount = 0; + $sFileName = \basename($sFile); + + $aNextLang = \getLangStructure($sFileName); + $aNewLang = \mergeLangStructure($aNextLang, $aEngLang, $iCount); + + if (0 === $iCount) + { + echo $sFileName.': ok'.$sNL; + } + else + { + echo $sFileName.': changed ('.$iCount.')'.$sNL; + } + +// \saveLangStructure($sFileName, $aNewLang); +} \ No newline at end of file diff --git a/build/owncloud/rainloop-app/INSTALL b/build/owncloud/rainloop-app/INSTALL index 8a8662368..314f5babe 100755 --- a/build/owncloud/rainloop-app/INSTALL +++ b/build/owncloud/rainloop-app/INSTALL @@ -1,26 +1,23 @@ -************************************************************************ -* -* ownCloud - RainLoop Webmail mail plugin -* -* @author RainLoop Team -* @copyright 2014 RainLoop Team -* -* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud -* -************************************************************************ - -REQUIREMENTS: -- Installed and configured RainLoop Webmail (standalone) -- ownCloud version 6 or higher -- Both apps (RainLoop & ownCloud) running on the same domain - - -INSTALL: -- Unpack the RainLoop Webmail application package in the apps directory of your OwnCloud instance - - -CONFIGURATION: -- ownCloud: - 1) In the Apps > Enable 'RainLoop' plugin - 2) In the Settings > Admin > Enter "RainLoop Webmail URL" and "Absolute (full) path to RainLoop Webmail installation" - 3) In the Settings > Personal > Type your mail server email (login) and password +************************************************************************ +* +* ownCloud - RainLoop Webmail package +* +* @author RainLoop Team +* @copyright 2015 RainLoop Team +* +* https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud +* +************************************************************************ + +REQUIREMENTS: +- ownCloud version 6 or higher + + +INSTALL: +- Unpack the RainLoop Webmail application package in the apps directory of your OwnCloud instance + + +CONFIGURATION: +- ownCloud: + 1) In the Apps > Enable 'RainLoop' plugin + 2) In the Settings > Personal > Type your mail server email (login) and password diff --git a/build/owncloud/rainloop-app/admin.php b/build/owncloud/rainloop-app/admin.php index ba8913669..3f7a6bf3f 100644 --- a/build/owncloud/rainloop-app/admin.php +++ b/build/owncloud/rainloop-app/admin.php @@ -1,20 +1,19 @@ -assign('rainloop-url', OCP\Config::getAppValue('rainloop', 'rainloop-url', '')); -$oTemplate->assign('rainloop-path', OCP\Config::getAppValue('rainloop', 'rainloop-path', '')); -$oTemplate->assign('rainloop-autologin', OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false)); -return $oTemplate->fetchPage(); +assign('rainloop-admin-panel-link', OC_RainLoop_Helper::getAppUrl().'?admin'); +$oTemplate->assign('rainloop-autologin', OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false)); +return $oTemplate->fetchPage(); diff --git a/build/owncloud/rainloop-app/ajax/admin.php b/build/owncloud/rainloop-app/ajax/admin.php index fd35d61f4..29fe17eab 100644 --- a/build/owncloud/rainloop-app/ajax/admin.php +++ b/build/owncloud/rainloop-app/ajax/admin.php @@ -4,7 +4,7 @@ * ownCloud - RainLoop mail plugin * * @author RainLoop Team - * @copyright 2014 RainLoop Team + * @copyright 2015 RainLoop Team * * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud */ @@ -17,22 +17,20 @@ $sUrl = ''; $sPath = ''; $bAutologin = false; -if (isset($_POST['appname'], $_POST['rainloop-url'], $_POST['rainloop-path']) && 'rainloop' === $_POST['appname']) +if (isset($_POST['appname']) && 'rainloop' === $_POST['appname']) { - OCP\Config::setAppValue('rainloop', 'rainloop-url', $_POST['rainloop-url']); - OCP\Config::setAppValue('rainloop', 'rainloop-path', $_POST['rainloop-path']); OCP\Config::setAppValue('rainloop', 'rainloop-autologin', isset($_POST['rainloop-autologin']) ? '1' === $_POST['rainloop-autologin'] : false); - $sUrl = OCP\Config::getAppValue('rainloop', 'rainloop-url', ''); - $sPath = OCP\Config::getAppValue('rainloop', 'rainloop-path', ''); $bAutologin = OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false); } else { - OC_JSON::error(array('Message' => 'Invalid Argument(s)', 'Url' => $sUrl, 'Path' => $sPath)); + sleep(1); + OC_JSON::error(array('Message' => 'Invalid Argument(s)')); return false; } -OCP\JSON::success(array('Message' => 'Saved successfully', 'Url' => $sUrl, 'Path' => $sPath)); +sleep(1); +OCP\JSON::success(array('Message' => 'Saved successfully')); return true; diff --git a/build/owncloud/rainloop-app/ajax/personal.php b/build/owncloud/rainloop-app/ajax/personal.php index 3fcf22fb7..4ea738417 100644 --- a/build/owncloud/rainloop-app/ajax/personal.php +++ b/build/owncloud/rainloop-app/ajax/personal.php @@ -4,7 +4,7 @@ * ownCloud - RainLoop mail plugin * * @author RainLoop Team - * @copyright 2014 RainLoop Team + * @copyright 2015 RainLoop Team * * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud */ @@ -37,9 +37,11 @@ if (isset($_POST['appname'], $_POST['rainloop-password'], $_POST['rainloop-email } else { + sleep(1); OC_JSON::error(array('Message' => 'Invalid argument(s)', 'Email' => $sEmail)); return false; } +sleep(1); OCP\JSON::success(array('Message' => 'Saved successfully', 'Email' => $sEmail)); return true; diff --git a/build/owncloud/rainloop-app/app.php b/build/owncloud/rainloop-app/app.php new file mode 100644 index 000000000..802dc1a68 --- /dev/null +++ b/build/owncloud/rainloop-app/app.php @@ -0,0 +1,34 @@ + 'rainloop_index', + 'order' => 10, + 'href' => OCP\Util::linkToRoute('rainloop_index'), + 'icon' => OCP\Util::imagePath('rainloop', 'mail.png'), + 'name' => 'Email' +)); - OCP\App::addNavigationEntry(array( - 'id' => 'rainloop_index', - 'order' => 10, - 'href' => OCP\Util::linkTo('rainloop', 'index.php'), - 'icon' => OCP\Util::imagePath('rainloop', 'mail.png'), - 'name' => 'Email' - )); -} diff --git a/build/owncloud/rainloop-app/appinfo/info.xml b/build/owncloud/rainloop-app/appinfo/info.xml index c261b5b93..7ec7dbdd9 100644 --- a/build/owncloud/rainloop-app/appinfo/info.xml +++ b/build/owncloud/rainloop-app/appinfo/info.xml @@ -2,9 +2,12 @@ rainloop RainLoop - RainLoop Webmail + Simple, modern and fast web-based email client. 0.0 CC BY-NC-SA 3.0 RainLoop Team 6.0 + + + \ No newline at end of file diff --git a/build/owncloud/rainloop-app/appinfo/routes.php b/build/owncloud/rainloop-app/appinfo/routes.php new file mode 100644 index 000000000..7716e0a74 --- /dev/null +++ b/build/owncloud/rainloop-app/appinfo/routes.php @@ -0,0 +1,13 @@ +create('rainloop_index', '/') + ->actionInclude('rainloop/index.php'); + +$this->create('rainloop_app', '/app/') + ->actionInclude('rainloop/app.php'); + +$this->create('rainloop_ajax_personal', 'ajax/personal.php') + ->actionInclude('rainloop/ajax/personal.php'); + +$this->create('rainloop_ajax_admin', 'ajax/admin.php') + ->actionInclude('rainloop/ajax/admin.php'); diff --git a/build/owncloud/rainloop-app/css/style.css b/build/owncloud/rainloop-app/css/style.css new file mode 100644 index 000000000..462226253 --- /dev/null +++ b/build/owncloud/rainloop-app/css/style.css @@ -0,0 +1,6 @@ +/* +Empty style sheet! +Only needed to give you the opportunity to theme the owncloud part +of the rainloop app with the theming system integrated in ownCoud +if the rainloop app is activated. +*/ \ No newline at end of file diff --git a/build/owncloud/rainloop-app/index.php b/build/owncloud/rainloop-app/index.php index a958d631a..931ecf153 100644 --- a/build/owncloud/rainloop-app/index.php +++ b/build/owncloud/rainloop-app/index.php @@ -4,7 +4,7 @@ * ownCloud - RainLoop mail plugin * * @author RainLoop Team - * @copyright 2014 RainLoop Team + * @copyright 2015 RainLoop Team * * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud */ @@ -13,41 +13,15 @@ OCP\User::checkLoggedIn(); OCP\App::checkAppEnabled('rainloop'); OCP\App::setActiveNavigationEntry('rainloop_index'); -$sUrl = trim(OCP\Config::getAppValue('rainloop', 'rainloop-url', '')); -$sPath = trim(OCP\Config::getAppValue('rainloop', 'rainloop-path', '')); -$bAutologin = OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false); +// Load the empty file ../css/style.css, that's needed to allow theming of +// the ownCloud header and navigation if rainloop is the active app. +OCP\Util::addStyle('rainloop', 'style'); -if ('' === $sUrl || '' === $sPath) -{ - $oTemplate = new OCP\Template('rainloop', 'index-empty', 'user'); -} -else -{ - include_once OC_App::getAppPath('rainloop').'/lib/RainLoopHelper.php'; +include_once OC_App::getAppPath('rainloop').'/lib/RainLoopHelper.php'; - OC_Config::setValue('xframe_restriction', false); +$sUrl = OC_RainLoop_Helper::normalizeUrl(OC_RainLoop_Helper::getAppUrl()); - $sUser = OCP\User::getUser(); - - if ($bAutologin) - { - $sEmail = $sUser; - $sEncodedPassword = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-autologin-password', ''); - } - else - { - $sEmail = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-email', ''); - $sEncodedPassword = OCP\Config::getUserValue($sUser, 'rainloop', 'rainloop-password', ''); - } - - $sDecodedPassword = OC_RainLoop_Helper::decodePassword($sEncodedPassword, md5($sEmail)); - $sSsoHash = OC_RainLoop_Helper::getSsoHash($sPath, $sEmail, $sDecodedPassword); - - $sUrl = OC_RainLoop_Helper::normalizeUrl($sUrl); - $sResultUrl = empty($sSsoHash) ? $sUrl.'?sso' : $sUrl.'?sso&hash='.$sSsoHash; - - $oTemplate = new OCP\Template('rainloop', 'index', 'user'); - $oTemplate->assign('rainloop-url', $sResultUrl); -} +$oTemplate = new OCP\Template('rainloop', 'index', 'user'); +$oTemplate->assign('rainloop-iframe-url', OC_RainLoop_Helper::normalizeUrl($sUrl).'?OwnCloudAuth'); $oTemplate->printpage(); diff --git a/build/owncloud/rainloop-app/js/admin.js b/build/owncloud/rainloop-app/js/admin.js index c47ed8b2c..a7a2bc4be 100755 --- a/build/owncloud/rainloop-app/js/admin.js +++ b/build/owncloud/rainloop-app/js/admin.js @@ -1,13 +1,13 @@ - -/** - * ownCloud - RainLoop mail plugin - * - * @author RainLoop Team - * @copyright 2014 RainLoop Team - * - * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud - */ - -$(function() { - RainLoopFormHelper('#mail-rainloop-admin-form', 'admin.php'); -}); + +/** + * ownCloud - RainLoop mail plugin + * + * @author RainLoop Team + * @copyright 2015 RainLoop Team + * + * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud + */ + +$(function() { + RainLoopFormHelper('#mail-rainloop-admin-form', 'admin.php'); +}); diff --git a/build/owncloud/rainloop-app/js/personal.js b/build/owncloud/rainloop-app/js/personal.js index 008bf41b4..ab65be751 100755 --- a/build/owncloud/rainloop-app/js/personal.js +++ b/build/owncloud/rainloop-app/js/personal.js @@ -1,13 +1,13 @@ - -/** - * ownCloud - RainLoop mail plugin - * - * @author RainLoop Team - * @copyright 2014 RainLoop Team - * - * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud - */ - -$(function() { - RainLoopFormHelper('#mail-rainloop-personal-form', 'personal.php'); -}); + +/** + * ownCloud - RainLoop mail plugin + * + * @author RainLoop Team + * @copyright 2015 RainLoop Team + * + * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud + */ + +$(function() { + RainLoopFormHelper('#mail-rainloop-personal-form', 'personal.php'); +}); diff --git a/build/owncloud/rainloop-app/lib/RainLoopHelper.php b/build/owncloud/rainloop-app/lib/RainLoopHelper.php index cc67e8c3d..729caf397 100644 --- a/build/owncloud/rainloop-app/lib/RainLoopHelper.php +++ b/build/owncloud/rainloop-app/lib/RainLoopHelper.php @@ -2,6 +2,23 @@ class OC_RainLoop_Helper { + /** + * @return string + */ + public static function getAppUrl() + { + if (class_exists('\\OCP\\Util')) + { + return OCP\Util::linkToRoute('rainloop_app'); + } + + $sRequestUri = empty($_SERVER['REQUEST_URI']) ? '': trim($_SERVER['REQUEST_URI']); + $sRequestUri = preg_replace('/index.php\/.+$/', 'index.php/', $sRequestUri); + $sRequestUri = $sRequestUri.'apps/rainloop/app/'; + + return '/'.ltrim($sRequestUri, '/\\'); + } + /** * @param string $sPath * @param string $sEmail @@ -16,6 +33,8 @@ class OC_RainLoop_Helper $sPath = rtrim(trim($sPath), '\\/').'/index.php'; if (file_exists($sPath)) { + self::regRainLoopDataFunction(); + $_ENV['RAINLOOP_INCLUDE_AS_API'] = true; include $sPath; @@ -82,14 +101,8 @@ class OC_RainLoop_Helper $sEmail = $sUser; $sPassword = $aParams['password']; - $sUrl = trim(OCP\Config::getAppValue('rainloop', 'rainloop-url', '')); - $sPath = trim(OCP\Config::getAppValue('rainloop', 'rainloop-path', '')); - - if ('' !== $sUrl && '' !== $sPath) - { - $sPassword = self::encodePassword($sPassword, md5($sEmail)); - return OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-autologin-password', $sPassword); - } + return OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-autologin-password', + self::encodePassword($sPassword, md5($sEmail))); } return false; @@ -97,8 +110,24 @@ class OC_RainLoop_Helper public static function logout() { - return OCP\Config::setUserValue( + OCP\Config::setUserValue( OCP\User::getUser(), 'rainloop', 'rainloop-autologin-password', ''); + + $sApiPath = __DIR__.'/../app/index.php'; + if (file_exists($sApiPath)) + { + self::regRainLoopDataFunction(); + + $_ENV['RAINLOOP_INCLUDE_AS_API'] = true; + include $sApiPath; + + if (class_exists('\\RainLoop\\Api')) + { + \RainLoop\Api::LogoutCurrentLogginedUser(); + } + } + + return true; } public static function changePassword($aParams) @@ -110,18 +139,115 @@ class OC_RainLoop_Helper $sEmail = $sUser; $sPassword = $aParams['password']; - $sUrl = trim(OCP\Config::getAppValue('rainloop', 'rainloop-url', '')); - $sPath = trim(OCP\Config::getAppValue('rainloop', 'rainloop-path', '')); + OCP\Util::writeLog('rainloop', 'rainloop|login: Setting new RainLoop password for '.$sEmail, OCP\Util::DEBUG); - if ('' !== $sUrl && '' !== $sPath) - { - OCP\Util::writeLog('rainloop', 'rainloop|login: Setting new RainLoop password for '.$sEmail, OCP\Util::DEBUG); + OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-autologin-password', + self::encodePassword($sPassword, md5($sEmail))); - $sPassword = self::encodePassword($sPassword, md5($sEmail)); - return OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-password', $sPassword); - } + OCP\Config::setUserValue($sUser, 'rainloop', 'rainloop-password', + self::encodePassword($sPassword, md5($sEmail))); + + return true; } return false; } + + public static function regRainLoopDataFunction() + { + if (!@function_exists('__get_custom_data_full_path')) + { + $_ENV['RAINLOOP_OWNCLOUD'] = true; + + function __get_custom_data_full_path() + { + $sData = __DIR__.'/../../data/'; + if (class_exists('OC_Config')) + { + $sData = rtrim(trim(OC_Config::getValue('datadirectory', '')), '\\/').'/'; + } + + return @is_dir($sData) ? $sData.'rainloop-storage' : ''; + } + } + } + + public static function mimeContentType($filename) { + + $mime_types = array( + + 'woff' => 'application/font-woff', + + 'txt' => 'text/plain', + 'htm' => 'text/html', + 'html' => 'text/html', + 'php' => 'text/html', + 'css' => 'text/css', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'xml' => 'application/xml', + 'swf' => 'application/x-shockwave-flash', + 'flv' => 'video/x-flv', + + // images + 'png' => 'image/png', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'gif' => 'image/gif', + 'bmp' => 'image/bmp', + 'ico' => 'image/vnd.microsoft.icon', + 'tiff' => 'image/tiff', + 'tif' => 'image/tiff', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + + // archives + 'zip' => 'application/zip', + 'rar' => 'application/x-rar-compressed', + 'exe' => 'application/x-msdownload', + 'msi' => 'application/x-msdownload', + 'cab' => 'application/vnd.ms-cab-compressed', + + // audio/video + 'mp3' => 'audio/mpeg', + 'qt' => 'video/quicktime', + 'mov' => 'video/quicktime', + + // adobe + 'pdf' => 'application/pdf', + 'psd' => 'image/vnd.adobe.photoshop', + 'ai' => 'application/postscript', + 'eps' => 'application/postscript', + 'ps' => 'application/postscript', + + // ms office + 'doc' => 'application/msword', + 'rtf' => 'application/rtf', + 'xls' => 'application/vnd.ms-excel', + 'ppt' => 'application/vnd.ms-powerpoint', + + // open office + 'odt' => 'application/vnd.oasis.opendocument.text', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + ); + + if (0 < strpos($filename, '.')) + { + $ext = strtolower(array_pop(explode('.',$filename))); + if (array_key_exists($ext, $mime_types)) + { + return $mime_types[$ext]; + } + else if (function_exists('finfo_open')) + { + $finfo = finfo_open(FILEINFO_MIME); + $mimetype = finfo_file($finfo, $filename); + finfo_close($finfo); + return $mimetype; + } + } + + return 'application/octet-stream'; + } } diff --git a/build/owncloud/rainloop-app/personal.php b/build/owncloud/rainloop-app/personal.php index 31ca9c409..92b1ee659 100644 --- a/build/owncloud/rainloop-app/personal.php +++ b/build/owncloud/rainloop-app/personal.php @@ -4,7 +4,7 @@ * ownCloud - RainLoop mail plugin * * @author RainLoop Team - * @copyright 2014 RainLoop Team + * @copyright 2015 RainLoop Team * * https://github.com/RainLoop/rainloop-webmail/tree/master/build/owncloud */ @@ -14,11 +14,7 @@ OCP\App::checkAppEnabled('rainloop'); OCP\Util::addScript('rainloop', 'personal'); -$sUrl = trim(OCP\Config::getAppValue('rainloop', 'rainloop-url', '')); -$sPath = trim(OCP\Config::getAppValue('rainloop', 'rainloop-path', '')); -$bAutologin = OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false); - -if ($bAutologin || '' === $sUrl || '' === $sPath) +if (OCP\Config::getAppValue('rainloop', 'rainloop-autologin', false)) { $oTemplate = new OCP\Template('rainloop', 'empty'); } diff --git a/build/owncloud/rainloop-app/templates/admin.php b/build/owncloud/rainloop-app/templates/admin-local.php similarity index 65% rename from build/owncloud/rainloop-app/templates/admin.php rename to build/owncloud/rainloop-app/templates/admin-local.php index d2dca468c..27a90acf2 100644 --- a/build/owncloud/rainloop-app/templates/admin.php +++ b/build/owncloud/rainloop-app/templates/admin-local.php @@ -6,17 +6,15 @@

t('RainLoop Webmail')); ?>


+ +

+ + t('Go to RainLoop Webmail admin panel')); ?> + +

+
+

- t('RainLoop Webmail URL')); ?>: -
- -
-
- t('Absolute (full) path to RainLoop Webmail installation')); ?>: -
- -
-
checked="checked" />

+ + + + + @@ -98,5 +103,10 @@ + + + + + diff --git a/dev/App/Abstract.js b/dev/App/Abstract.js index 96a6ce2bc..06f43d7e8 100644 --- a/dev/App/Abstract.js +++ b/dev/App/Abstract.js @@ -7,11 +7,14 @@ window = require('window'), _ = require('_'), $ = require('$'), + key = require('key'), Globals = require('Common/Globals'), + Enums = require('Common/Enums'), Utils = require('Common/Utils'), - LinkBuilder = require('Common/LinkBuilder'), + Links = require('Common/Links'), Events = require('Common/Events'), + Translator = require('Common/Translator'), Settings = require('Storage/Settings'), @@ -44,11 +47,42 @@ oEvent.originalEvent.lineno, window.location && window.location.toString ? window.location.toString() : '', Globals.$html.attr('class'), - Utils.microtime() - Globals.now + Utils.microtime() - Globals.startMicrotime ); } }); + Globals.$win.on('resize', function () { + Events.pub('window.resize'); + }); + + Events.sub('window.resize', _.throttle(function () { + + var + iH = Globals.$win.height(), + iW = Globals.$win.height() + ; + + if (Globals.$win.__sizes[0] !== iH || Globals.$win.__sizes[1] !== iW) + { + Globals.$win.__sizes[0] = iH; + Globals.$win.__sizes[1] = iW; + + Events.pub('window.resize.real'); + } + + }, 50)); + + // DEBUG +// Events.sub({ +// 'window.resize': function () { +// window.console.log('window.resize'); +// }, +// 'window.resize.real': function () { +// window.console.log('window.resize.real'); +// } +// }); + Globals.$doc.on('keydown', function (oEvent) { if (oEvent && oEvent.ctrlKey) { @@ -60,6 +94,14 @@ Globals.$html.removeClass('rl-ctrl-key-pressed'); } }); + + Globals.$doc.on('mousemove keypress click', _.debounce(function () { + Events.pub('rl.auto-logout-refresh'); + }, 5000)); + + key('esc, enter', Enums.KeyState.All, _.bind(function () { + Utils.detectDropdownVisibility(); + }, this)); } _.extend(AbstractApp.prototype, AbstractBoot.prototype); @@ -117,23 +159,55 @@ return true; }; + AbstractApp.prototype.googlePreviewSupportedCache = null; + + /** + * @return {boolean} + */ + AbstractApp.prototype.googlePreviewSupported = function () + { + if (null === this.googlePreviewSupportedCache) + { + this.googlePreviewSupportedCache = !!Settings.settingsGet('AllowGoogleSocial') && + !!Settings.settingsGet('AllowGoogleSocialPreview'); + } + + return this.googlePreviewSupportedCache; + }; + /** * @param {string} sTitle */ - AbstractApp.prototype.setTitle = function (sTitle) + AbstractApp.prototype.setWindowTitle = function (sTitle) { sTitle = ((Utils.isNormal(sTitle) && 0 < sTitle.length) ? sTitle + ' - ' : '') + Settings.settingsGet('Title') || ''; - window.document.title = ''; + window.document.title = sTitle + ' ...'; window.document.title = sTitle; }; + AbstractApp.prototype.redirectToAdminPanel = function () + { + _.delay(function () { + window.location.href = Links.rootAdmin(); + }, 100); + }; + + AbstractApp.prototype.clearClientSideToken = function () + { + if (window.__rlah_clear) + { + window.__rlah_clear(); + } + }; + /** + * @param {boolean=} bAdmin = false * @param {boolean=} bLogout = false * @param {boolean=} bClose = false */ - AbstractApp.prototype.loginAndLogoutReload = function (bLogout, bClose) + AbstractApp.prototype.loginAndLogoutReload = function (bAdmin, bLogout, bClose) { var kn = require('Knoin/Knoin'), @@ -144,12 +218,19 @@ bLogout = Utils.isUnd(bLogout) ? false : !!bLogout; bClose = Utils.isUnd(bClose) ? false : !!bClose; + if (bLogout) + { + this.clearClientSideToken(); + } + if (bLogout && bClose && window.close) { window.close(); } - if (bLogout && '' !== sCustomLogoutLink && window.location.href !== sCustomLogoutLink) + sCustomLogoutLink = sCustomLogoutLink || (bAdmin ? Links.rootAdmin() : Links.rootUser()); + + if (bLogout && window.location.href !== sCustomLogoutLink) { _.delay(function () { if (bInIframe && window.parent) @@ -165,7 +246,7 @@ else { kn.routeOff(); - kn.setHash(LinkBuilder.root(), true); + kn.setHash(Links.root(), true); kn.routeOff(); _.delay(function () { @@ -190,15 +271,32 @@ { Events.pub('rl.bootstart'); - var ssm = require('ssm'); + var + ssm = require('ssm'), + ko = require('ko') + ; - Utils.initOnStartOrLangChange(function () { - Utils.initNotificationLanguage(); - }, null); + ko.components.register('SaveTrigger', require('Component/SaveTrigger')); + ko.components.register('Input', require('Component/Input')); + ko.components.register('Select', require('Component/Select')); + ko.components.register('TextArea', require('Component/TextArea')); + ko.components.register('Radio', require('Component/Radio')); - _.delay(function () { - Utils.windowResize(); - }, 1000); + ko.components.register('x-script', require('Component/Script')); + + if (Settings.settingsGet('MaterialDesign') && Globals.bAnimationSupported) + { + ko.components.register('Checkbox', require('Component/MaterialDesign/Checkbox')); + } + else + { +// ko.components.register('Checkbox', require('Component/Classic/Checkbox')); + ko.components.register('Checkbox', require('Component/Checkbox')); + } + + Translator.initOnStartOrLangChange(Translator.initNotificationLanguage, Translator); + + _.delay(Utils.windowResizeCallback, 1000); ssm.addState({ 'id': 'mobile', @@ -261,6 +359,10 @@ }); ssm.ready(); + + require('Stores/Language').populate(); + require('Stores/Theme').populate(); + require('Stores/Social').populate(); }; module.exports = AbstractApp; diff --git a/dev/App/Admin.js b/dev/App/Admin.js index 15c10514a..cd4a42a9f 100644 --- a/dev/App/Admin.js +++ b/dev/App/Admin.js @@ -11,11 +11,17 @@ Enums = require('Common/Enums'), Utils = require('Common/Utils'), - LinkBuilder = require('Common/LinkBuilder'), + Links = require('Common/Links'), + Translator = require('Common/Translator'), Settings = require('Storage/Settings'), - Data = require('Storage/Admin/Data'), - Remote = require('Storage/Admin/Remote'), + AppStore = require('Stores/Admin/App'), + DomainStore = require('Stores/Admin/Domain'), + PluginStore = require('Stores/Admin/Plugin'), + LicenseStore = require('Stores/Admin/License'), + PackageStore = require('Stores/Admin/Package'), + CoreStore = require('Stores/Admin/Core'), + Remote = require('Remote/Admin/Ajax'), kn = require('Knoin/Knoin'), AbstractApp = require('App/Abstract') @@ -37,17 +43,12 @@ return Remote; }; - AdminApp.prototype.data = function () - { - return Data; - }; - AdminApp.prototype.reloadDomainList = function () { - Data.domainsLoading(true); + DomainStore.domains.loading(true); Remote.domainList(function (sResult, oData) { - Data.domainsLoading(false); + DomainStore.domains.loading(false); if (Enums.StorageResultType.Success === sResult && oData && oData.Result) { var aList = _.map(oData.Result, function (bEnabled, sName) { @@ -58,17 +59,18 @@ }; }, this); - Data.domains(aList); + DomainStore.domains(aList); } }); }; AdminApp.prototype.reloadPluginList = function () { - Data.pluginsLoading(true); + PluginStore.plugins.loading(true); + Remote.pluginList(function (sResult, oData) { - Data.pluginsLoading(false); + PluginStore.plugins.loading(false); if (Enums.StorageResultType.Success === sResult && oData && oData.Result) { @@ -80,31 +82,31 @@ }; }, this); - Data.plugins(aList); + PluginStore.plugins(aList); } }); }; AdminApp.prototype.reloadPackagesList = function () { - Data.packagesLoading(true); - Data.packagesReal(true); + PackageStore.packages.loading(true); + PackageStore.packagesReal(true); Remote.packagesList(function (sResult, oData) { - Data.packagesLoading(false); + PackageStore.packages.loading(false); if (Enums.StorageResultType.Success === sResult && oData && oData.Result) { - Data.packagesReal(!!oData.Result.Real); - Data.packagesMainUpdatable(!!oData.Result.MainUpdatable); + PackageStore.packagesReal(!!oData.Result.Real); + PackageStore.packagesMainUpdatable(!!oData.Result.MainUpdatable); var aList = [], aLoading = {} ; - _.each(Data.packages(), function (oItem) { + _.each(PackageStore.packages(), function (oItem) { if (oItem && oItem['loading']()) { aLoading[oItem['file']] = oItem; @@ -123,33 +125,34 @@ })); } - Data.packages(aList); + PackageStore.packages(aList); } else { - Data.packagesReal(false); + PackageStore.packagesReal(false); } }); }; AdminApp.prototype.updateCoreData = function () { - Data.coreUpdating(true); + CoreStore.coreUpdating(true); Remote.updateCoreData(function (sResult, oData) { - Data.coreUpdating(false); - Data.coreRemoteVersion(''); - Data.coreRemoteRelease(''); - Data.coreVersionCompare(-2); + CoreStore.coreUpdating(false); + CoreStore.coreVersion(''); + CoreStore.coreRemoteVersion(''); + CoreStore.coreRemoteRelease(''); + CoreStore.coreVersionCompare(-2); if (Enums.StorageResultType.Success === sResult && oData && oData.Result) { - Data.coreReal(true); + CoreStore.coreReal(true); window.location.reload(); } else { - Data.coreReal(false); + CoreStore.coreReal(false); } }); @@ -157,28 +160,36 @@ AdminApp.prototype.reloadCoreData = function () { - Data.coreChecking(true); - Data.coreReal(true); + CoreStore.coreChecking(true); + CoreStore.coreReal(true); Remote.coreData(function (sResult, oData) { - Data.coreChecking(false); + CoreStore.coreChecking(false); if (Enums.StorageResultType.Success === sResult && oData && oData.Result) { - Data.coreReal(!!oData.Result.Real); - Data.coreUpdatable(!!oData.Result.Updatable); - Data.coreAccess(!!oData.Result.Access); - Data.coreRemoteVersion(oData.Result.RemoteVersion || ''); - Data.coreRemoteRelease(oData.Result.RemoteRelease || ''); - Data.coreVersionCompare(Utils.pInt(oData.Result.VersionCompare)); + CoreStore.coreReal(!!oData.Result.Real); + CoreStore.coreChannel(oData.Result.Channel || 'stable'); + CoreStore.coreType(oData.Result.Type || 'stable'); + CoreStore.coreUpdatable(!!oData.Result.Updatable); + CoreStore.coreAccess(!!oData.Result.Access); + CoreStore.coreWarning(!!oData.Result.Warning); + CoreStore.coreVersion(oData.Result.Version || ''); + CoreStore.coreRemoteVersion(oData.Result.RemoteVersion || ''); + CoreStore.coreRemoteRelease(oData.Result.RemoteRelease || ''); + CoreStore.coreVersionCompare(Utils.pInt(oData.Result.VersionCompare)); } else { - Data.coreReal(false); - Data.coreRemoteVersion(''); - Data.coreRemoteRelease(''); - Data.coreVersionCompare(-2); + CoreStore.coreReal(false); + CoreStore.coreChannel('stable'); + CoreStore.coreType('stable'); + CoreStore.coreWarning(false); + CoreStore.coreVersion(''); + CoreStore.coreRemoteVersion(''); + CoreStore.coreRemoteRelease(''); + CoreStore.coreVersionCompare(-2); } }); }; @@ -191,18 +202,22 @@ { bForce = Utils.isUnd(bForce) ? false : !!bForce; - Data.licensingProcess(true); - Data.licenseError(''); + LicenseStore.licensingProcess(true); + LicenseStore.licenseError(''); Remote.licensing(function (sResult, oData) { - Data.licensingProcess(false); + + LicenseStore.licensingProcess(false); + if (Enums.StorageResultType.Success === sResult && oData && oData.Result && Utils.isNormal(oData.Result['Expired'])) { - Data.licenseValid(true); - Data.licenseExpired(Utils.pInt(oData.Result['Expired'])); - Data.licenseError(''); + LicenseStore.licenseValid(true); + LicenseStore.licenseExpired(Utils.pInt(oData.Result['Expired'])); + LicenseStore.licenseError(''); - Data.licensing(true); + LicenseStore.licensing(true); + + AppStore.prem(true); } else { @@ -211,19 +226,19 @@ Enums.Notification.LicensingExpired ])) { - Data.licenseError(Utils.getNotification(Utils.pInt(oData.ErrorCode))); - Data.licensing(true); + LicenseStore.licenseError(Translator.getNotification(Utils.pInt(oData.ErrorCode))); + LicenseStore.licensing(true); } else { if (Enums.StorageResultType.Abort === sResult) { - Data.licenseError(Utils.getNotification(Enums.Notification.LicensingServerIsUnavailable)); - Data.licensing(true); + LicenseStore.licenseError(Translator.getNotification(Enums.Notification.LicensingServerIsUnavailable)); + LicenseStore.licensing(true); } else { - Data.licensing(false); + LicenseStore.licensing(false); } } } @@ -234,14 +249,15 @@ { AbstractApp.prototype.bootstart.call(this); - Data.populateDataOnStart(); + require('Stores/Admin/App').populate(); + require('Stores/Admin/Capa').populate(); kn.hideLoading(); if (!Settings.settingsGet('AllowAdminPanel')) { kn.routeOff(); - kn.setHash(LinkBuilder.root(), true); + kn.setHash(Links.root(), true); kn.routeOff(); _.defer(function () { diff --git a/dev/App/App.js b/dev/App/App.js deleted file mode 100644 index 9f0a8ddbb..000000000 --- a/dev/App/App.js +++ /dev/null @@ -1,1461 +0,0 @@ - -(function () { - - 'use strict'; - - var - window = require('window'), - _ = require('_'), - $ = require('$'), - moment = require('moment'), - SimplePace = require('SimplePace'), - - Enums = require('Common/Enums'), - Globals = require('Common/Globals'), - Consts = require('Common/Consts'), - Plugins = require('Common/Plugins'), - Utils = require('Common/Utils'), - LinkBuilder = require('Common/LinkBuilder'), - Events = require('Common/Events'), - - kn = require('Knoin/Knoin'), - - Local = require('Storage/Local'), - Settings = require('Storage/Settings'), - Data = require('Storage/App/Data'), - Cache = require('Storage/App/Cache'), - Remote = require('Storage/App/Remote'), - - EmailModel = require('Model/Email'), - FolderModel = require('Model/Folder'), - MessageModel = require('Model/Message'), - AccountModel = require('Model/Account'), - IdentityModel = require('Model/Identity'), - OpenPgpKeyModel = require('Model/OpenPgpKey'), - - AbstractApp = require('App/Abstract') - ; - - /** - * @constructor - * @extends AbstractApp - */ - function AppApp() - { - AbstractApp.call(this, Remote); - - this.oMoveCache = {}; - - this.quotaDebounce = _.debounce(this.quota, 1000 * 30); - this.moveOrDeleteResponseHelper = _.bind(this.moveOrDeleteResponseHelper, this); - - this.messagesMoveTrigger = _.debounce(this.messagesMoveTrigger, 500); - - window.setInterval(function () { - Events.pub('interval.30s'); - }, 30000); - - window.setInterval(function () { - Events.pub('interval.1m'); - }, 60000); - - window.setInterval(function () { - Events.pub('interval.2m'); - }, 60000 * 2); - - window.setInterval(function () { - Events.pub('interval.3m'); - }, 60000 * 3); - - window.setInterval(function () { - Events.pub('interval.5m'); - }, 60000 * 5); - - window.setInterval(function () { - Events.pub('interval.10m'); - }, 60000 * 10); - - window.setTimeout(function () { - window.setInterval(function () { - Events.pub('interval.10m-after5m'); - }, 60000 * 10); - }, 60000 * 5); - - $.wakeUp(function () { - - Remote.jsVersion(function (sResult, oData) { - if (Enums.StorageResultType.Success === sResult && oData && !oData.Result) - { - if (window.parent && !!Settings.settingsGet('InIframe')) - { - window.parent.location.reload(); - } - else - { - window.location.reload(); - } - } - }, Settings.settingsGet('Version')); - - }, {}, 60 * 60 * 1000); - - - this.socialUsers = _.bind(this.socialUsers, this); - } - - _.extend(AppApp.prototype, AbstractApp.prototype); - - AppApp.prototype.remote = function () - { - return Remote; - }; - - AppApp.prototype.data = function () - { - return Data; - }; - - AppApp.prototype.reloadFlagsCurrentMessageListAndMessageFromCache = function () - { - _.each(Data.messageList(), function (oMessage) { - Cache.initMessageFlagsFromCache(oMessage); - }); - - Cache.initMessageFlagsFromCache(Data.message()); - }; - - /** - * @param {boolean=} bDropPagePosition = false - * @param {boolean=} bDropCurrenFolderCache = false - */ - AppApp.prototype.reloadMessageList = function (bDropPagePosition, bDropCurrenFolderCache) - { - var - self = this, - iOffset = (Data.messageListPage() - 1) * Data.messagesPerPage() - ; - - if (Utils.isUnd(bDropCurrenFolderCache) ? false : !!bDropCurrenFolderCache) - { - Cache.setFolderHash(Data.currentFolderFullNameRaw(), ''); - } - - if (Utils.isUnd(bDropPagePosition) ? false : !!bDropPagePosition) - { - Data.messageListPage(1); - iOffset = 0; - } - - Data.messageListLoading(true); - Remote.messageList(function (sResult, oData, bCached) { - - if (Enums.StorageResultType.Success === sResult && oData && oData.Result) - { - Data.messageListError(''); - Data.messageListLoading(false); - self.setMessageList(oData, bCached); - } - else if (Enums.StorageResultType.Unload === sResult) - { - Data.messageListError(''); - Data.messageListLoading(false); - } - else if (Enums.StorageResultType.Abort !== sResult) - { - Data.messageList([]); - Data.messageListLoading(false); - Data.messageListError(oData && oData.ErrorCode ? - Utils.getNotification(oData.ErrorCode) : Utils.i18n('NOTIFICATIONS/CANT_GET_MESSAGE_LIST') - ); - } - - }, Data.currentFolderFullNameRaw(), iOffset, Data.messagesPerPage(), Data.messageListSearch()); - }; - - AppApp.prototype.recacheInboxMessageList = function () - { - Remote.messageList(Utils.emptyFunction, 'INBOX', 0, Data.messagesPerPage(), '', true); - }; - - AppApp.prototype.reloadMessageListHelper = function (bEmptyList) - { - this.reloadMessageList(bEmptyList); - }; - - /** - * @param {Function} fResultFunc - * @returns {boolean} - */ - AppApp.prototype.contactsSync = function (fResultFunc) - { - var oContacts = Data.contacts; - if (oContacts.importing() || oContacts.syncing() || !Data.enableContactsSync() || !Data.allowContactsSync()) - { - return false; - } - - oContacts.syncing(true); - - Remote.contactsSync(function (sResult, oData) { - - oContacts.syncing(false); - - if (fResultFunc) - { - fResultFunc(sResult, oData); - } - }); - - return true; - }; - - AppApp.prototype.messagesMoveTrigger = function () - { - var - self = this, - sSpamFolder = Data.spamFolder() - ; - - _.each(this.oMoveCache, function (oItem) { - - var - bSpam = sSpamFolder === oItem['To'], - bHam = !bSpam && sSpamFolder === oItem['From'] && 'INBOX' === oItem['To'] - ; - - Remote.messagesMove(self.moveOrDeleteResponseHelper, oItem['From'], oItem['To'], oItem['Uid'], - bSpam ? 'SPAM' : (bHam ? 'HAM' : '')); - }); - - this.oMoveCache = {}; - }; - - AppApp.prototype.messagesMoveHelper = function (sFromFolderFullNameRaw, sToFolderFullNameRaw, aUidForMove) - { - var sH = '$$' + sFromFolderFullNameRaw + '$$' + sToFolderFullNameRaw + '$$'; - if (!this.oMoveCache[sH]) - { - this.oMoveCache[sH] = { - 'From': sFromFolderFullNameRaw, - 'To': sToFolderFullNameRaw, - 'Uid': [] - }; - } - - this.oMoveCache[sH]['Uid'] = _.union(this.oMoveCache[sH]['Uid'], aUidForMove); - this.messagesMoveTrigger(); - }; - - AppApp.prototype.messagesCopyHelper = function (sFromFolderFullNameRaw, sToFolderFullNameRaw, aUidForCopy) - { - Remote.messagesCopy( - this.moveOrDeleteResponseHelper, - sFromFolderFullNameRaw, - sToFolderFullNameRaw, - aUidForCopy - ); - }; - - AppApp.prototype.messagesDeleteHelper = function (sFromFolderFullNameRaw, aUidForRemove) - { - Remote.messagesDelete( - this.moveOrDeleteResponseHelper, - sFromFolderFullNameRaw, - aUidForRemove - ); - }; - - AppApp.prototype.moveOrDeleteResponseHelper = function (sResult, oData) - { - if (Enums.StorageResultType.Success === sResult && Data.currentFolder()) - { - if (oData && Utils.isArray(oData.Result) && 2 === oData.Result.length) - { - Cache.setFolderHash(oData.Result[0], oData.Result[1]); - } - else - { - Cache.setFolderHash(Data.currentFolderFullNameRaw(), ''); - - if (oData && -1 < Utils.inArray(oData.ErrorCode, - [Enums.Notification.CantMoveMessage, Enums.Notification.CantCopyMessage])) - { - window.alert(Utils.getNotification(oData.ErrorCode)); - } - } - - this.reloadMessageListHelper(0 === Data.messageList().length); - this.quotaDebounce(); - } - }; - - /** - * @param {string} sFromFolderFullNameRaw - * @param {Array} aUidForRemove - */ - AppApp.prototype.deleteMessagesFromFolderWithoutCheck = function (sFromFolderFullNameRaw, aUidForRemove) - { - this.messagesDeleteHelper(sFromFolderFullNameRaw, aUidForRemove); - Data.removeMessagesFromList(sFromFolderFullNameRaw, aUidForRemove); - }; - - /** - * @param {number} iDeleteType - * @param {string} sFromFolderFullNameRaw - * @param {Array} aUidForRemove - * @param {boolean=} bUseFolder = true - */ - AppApp.prototype.deleteMessagesFromFolder = function (iDeleteType, sFromFolderFullNameRaw, aUidForRemove, bUseFolder) - { - var - self = this, - oMoveFolder = null, - nSetSystemFoldersNotification = null - ; - - switch (iDeleteType) - { - case Enums.FolderType.Spam: - oMoveFolder = Cache.getFolderFromCacheList(Data.spamFolder()); - nSetSystemFoldersNotification = Enums.SetSystemFoldersNotification.Spam; - break; - case Enums.FolderType.NotSpam: - oMoveFolder = Cache.getFolderFromCacheList('INBOX'); - break; - case Enums.FolderType.Trash: - oMoveFolder = Cache.getFolderFromCacheList(Data.trashFolder()); - nSetSystemFoldersNotification = Enums.SetSystemFoldersNotification.Trash; - break; - case Enums.FolderType.Archive: - oMoveFolder = Cache.getFolderFromCacheList(Data.archiveFolder()); - nSetSystemFoldersNotification = Enums.SetSystemFoldersNotification.Archive; - break; - } - - bUseFolder = Utils.isUnd(bUseFolder) ? true : !!bUseFolder; - if (bUseFolder) - { - if ((Enums.FolderType.Spam === iDeleteType && Consts.Values.UnuseOptionValue === Data.spamFolder()) || - (Enums.FolderType.Trash === iDeleteType && Consts.Values.UnuseOptionValue === Data.trashFolder()) || - (Enums.FolderType.Archive === iDeleteType && Consts.Values.UnuseOptionValue === Data.archiveFolder())) - { - bUseFolder = false; - } - } - - if (!oMoveFolder && bUseFolder) - { - kn.showScreenPopup(require('View/Popup/FolderSystem'), [nSetSystemFoldersNotification]); - } - else if (!bUseFolder || (Enums.FolderType.Trash === iDeleteType && - (sFromFolderFullNameRaw === Data.spamFolder() || sFromFolderFullNameRaw === Data.trashFolder()))) - { - kn.showScreenPopup(require('View/Popup/Ask'), [Utils.i18n('POPUPS_ASK/DESC_WANT_DELETE_MESSAGES'), function () { - - self.messagesDeleteHelper(sFromFolderFullNameRaw, aUidForRemove); - Data.removeMessagesFromList(sFromFolderFullNameRaw, aUidForRemove); - - }]); - } - else if (oMoveFolder) - { - this.messagesMoveHelper(sFromFolderFullNameRaw, oMoveFolder.fullNameRaw, aUidForRemove); - Data.removeMessagesFromList(sFromFolderFullNameRaw, aUidForRemove, oMoveFolder.fullNameRaw); - } - }; - - /** - * @param {string} sFromFolderFullNameRaw - * @param {Array} aUidForMove - * @param {string} sToFolderFullNameRaw - * @param {boolean=} bCopy = false - */ - AppApp.prototype.moveMessagesToFolder = function (sFromFolderFullNameRaw, aUidForMove, sToFolderFullNameRaw, bCopy) - { - if (sFromFolderFullNameRaw !== sToFolderFullNameRaw && Utils.isArray(aUidForMove) && 0 < aUidForMove.length) - { - var - oFromFolder = Cache.getFolderFromCacheList(sFromFolderFullNameRaw), - oToFolder = Cache.getFolderFromCacheList(sToFolderFullNameRaw) - ; - - if (oFromFolder && oToFolder) - { - if (Utils.isUnd(bCopy) ? false : !!bCopy) - { - this.messagesCopyHelper(oFromFolder.fullNameRaw, oToFolder.fullNameRaw, aUidForMove); - } - else - { - this.messagesMoveHelper(oFromFolder.fullNameRaw, oToFolder.fullNameRaw, aUidForMove); - } - - Data.removeMessagesFromList(oFromFolder.fullNameRaw, aUidForMove, oToFolder.fullNameRaw, bCopy); - return true; - } - } - - return false; - }; - - /** - * @param {Function=} fCallback - */ - AppApp.prototype.folders = function (fCallback) - { - Data.foldersLoading(true); - Remote.folders(_.bind(function (sResult, oData) { - - Data.foldersLoading(false); - if (Enums.StorageResultType.Success === sResult) - { - this.setFolders(oData); - if (fCallback) - { - fCallback(true); - } - } - else - { - if (fCallback) - { - fCallback(false); - } - } - }, this)); - }; - - AppApp.prototype.reloadOpenPgpKeys = function () - { - if (Data.capaOpenPGP()) - { - var - aKeys = [], - oEmail = new EmailModel(), - oOpenpgpKeyring = Data.openpgpKeyring, - oOpenpgpKeys = oOpenpgpKeyring ? oOpenpgpKeyring.getAllKeys() : [] - ; - - _.each(oOpenpgpKeys, function (oItem, iIndex) { - if (oItem && oItem.primaryKey) - { - var - - oPrimaryUser = oItem.getPrimaryUser(), - sUser = (oPrimaryUser && oPrimaryUser.user) ? oPrimaryUser.user.userId.userid - : (oItem.users && oItem.users[0] ? oItem.users[0].userId.userid : '') - ; - - oEmail.clear(); - oEmail.mailsoParse(sUser); - - if (oEmail.validate()) - { - aKeys.push(new OpenPgpKeyModel( - iIndex, - oItem.primaryKey.getFingerprint(), - oItem.primaryKey.getKeyId().toHex().toLowerCase(), - sUser, - oEmail.email, - oItem.isPrivate(), - oItem.armor()) - ); - } - } - }); - - Data.openpgpkeys(aKeys); - } - }; - - AppApp.prototype.accountsAndIdentities = function () - { - Data.accountsLoading(true); - Data.identitiesLoading(true); - - Remote.accountsAndIdentities(function (sResult, oData) { - - Data.accountsLoading(false); - Data.identitiesLoading(false); - - if (Enums.StorageResultType.Success === sResult && oData.Result) - { - var - sParentEmail = Settings.settingsGet('ParentEmail'), - sAccountEmail = Data.accountEmail() - ; - - sParentEmail = '' === sParentEmail ? sAccountEmail : sParentEmail; - - if (Utils.isArray(oData.Result['Accounts'])) - { - Data.accounts(_.map(oData.Result['Accounts'], function (sValue) { - return new AccountModel(sValue, sValue !== sParentEmail); - })); - } - - if (Utils.isArray(oData.Result['Identities'])) - { - Data.identities(_.map(oData.Result['Identities'], function (oIdentityData) { - - var - sId = Utils.pString(oIdentityData['Id']), - sEmail = Utils.pString(oIdentityData['Email']), - oIdentity = new IdentityModel(sId, sEmail, sId !== sAccountEmail) - ; - - oIdentity.name(Utils.pString(oIdentityData['Name'])); - oIdentity.replyTo(Utils.pString(oIdentityData['ReplyTo'])); - oIdentity.bcc(Utils.pString(oIdentityData['Bcc'])); - - return oIdentity; - })); - } - } - }); - }; - - AppApp.prototype.quota = function () - { - Remote.quota(function (sResult, oData) { - if (Enums.StorageResultType.Success === sResult && oData && oData.Result && - Utils.isArray(oData.Result) && 1 < oData.Result.length && - Utils.isPosNumeric(oData.Result[0], true) && Utils.isPosNumeric(oData.Result[1], true)) - { - Data.userQuota(Utils.pInt(oData.Result[1]) * 1024); - Data.userUsageSize(Utils.pInt(oData.Result[0]) * 1024); - } - }); - }; - - /** - * @param {string} sFolder - * @param {Array=} aList = [] - */ - AppApp.prototype.folderInformation = function (sFolder, aList) - { - if ('' !== Utils.trim(sFolder)) - { - var self = this; - Remote.folderInformation(function (sResult, oData) { - if (Enums.StorageResultType.Success === sResult) - { - if (oData && oData.Result && oData.Result.Hash && oData.Result.Folder) - { - var - iUtc = moment().unix(), - sHash = Cache.getFolderHash(oData.Result.Folder), - oFolder = Cache.getFolderFromCacheList(oData.Result.Folder), - bCheck = false, - sUid = '', - aList = [], - bUnreadCountChange = false, - oFlags = null - ; - - if (oFolder) - { - oFolder.interval = iUtc; - - if (oData.Result.Hash) - { - Cache.setFolderHash(oData.Result.Folder, oData.Result.Hash); - } - - if (Utils.isNormal(oData.Result.MessageCount)) - { - oFolder.messageCountAll(oData.Result.MessageCount); - } - - if (Utils.isNormal(oData.Result.MessageUnseenCount)) - { - if (Utils.pInt(oFolder.messageCountUnread()) !== Utils.pInt(oData.Result.MessageUnseenCount)) - { - bUnreadCountChange = true; - } - - oFolder.messageCountUnread(oData.Result.MessageUnseenCount); - } - - if (bUnreadCountChange) - { - Cache.clearMessageFlagsFromCacheByFolder(oFolder.fullNameRaw); - } - - if (oData.Result.Flags) - { - for (sUid in oData.Result.Flags) - { - if (oData.Result.Flags.hasOwnProperty(sUid)) - { - bCheck = true; - oFlags = oData.Result.Flags[sUid]; - Cache.storeMessageFlagsToCacheByFolderAndUid(oFolder.fullNameRaw, sUid.toString(), [ - !oFlags['IsSeen'], !!oFlags['IsFlagged'], !!oFlags['IsAnswered'], !!oFlags['IsForwarded'], !!oFlags['IsReadReceipt'] - ]); - } - } - - if (bCheck) - { - self.reloadFlagsCurrentMessageListAndMessageFromCache(); - } - } - - Data.initUidNextAndNewMessages(oFolder.fullNameRaw, oData.Result.UidNext, oData.Result.NewMessages); - - if (oData.Result.Hash !== sHash || '' === sHash) - { - if (oFolder.fullNameRaw === Data.currentFolderFullNameRaw()) - { - self.reloadMessageList(); - } - else if ('INBOX' === oFolder.fullNameRaw) - { - self.recacheInboxMessageList(); - } - } - else if (bUnreadCountChange) - { - if (oFolder.fullNameRaw === Data.currentFolderFullNameRaw()) - { - aList = Data.messageList(); - if (Utils.isNonEmptyArray(aList)) - { - self.folderInformation(oFolder.fullNameRaw, aList); - } - } - } - } - } - } - }, sFolder, aList); - } - }; - - /** - * @param {boolean=} bBoot = false - */ - AppApp.prototype.folderInformationMultiply = function (bBoot) - { - bBoot = Utils.isUnd(bBoot) ? false : !!bBoot; - - var - self = this, - iUtc = moment().unix(), - aFolders = Data.getNextFolderNames(bBoot) - ; - - if (Utils.isNonEmptyArray(aFolders)) - { - Remote.folderInformationMultiply(function (sResult, oData) { - if (Enums.StorageResultType.Success === sResult) - { - if (oData && oData.Result && oData.Result.List && Utils.isNonEmptyArray(oData.Result.List)) - { - _.each(oData.Result.List, function (oItem) { - - var - aList = [], - sHash = Cache.getFolderHash(oItem.Folder), - oFolder = Cache.getFolderFromCacheList(oItem.Folder), - bUnreadCountChange = false - ; - - if (oFolder) - { - oFolder.interval = iUtc; - - if (oItem.Hash) - { - Cache.setFolderHash(oItem.Folder, oItem.Hash); - } - - if (Utils.isNormal(oItem.MessageCount)) - { - oFolder.messageCountAll(oItem.MessageCount); - } - - if (Utils.isNormal(oItem.MessageUnseenCount)) - { - if (Utils.pInt(oFolder.messageCountUnread()) !== Utils.pInt(oItem.MessageUnseenCount)) - { - bUnreadCountChange = true; - } - - oFolder.messageCountUnread(oItem.MessageUnseenCount); - } - - if (bUnreadCountChange) - { - Cache.clearMessageFlagsFromCacheByFolder(oFolder.fullNameRaw); - } - - if (oItem.Hash !== sHash || '' === sHash) - { - if (oFolder.fullNameRaw === Data.currentFolderFullNameRaw()) - { - self.reloadMessageList(); - } - } - else if (bUnreadCountChange) - { - if (oFolder.fullNameRaw === Data.currentFolderFullNameRaw()) - { - aList = Data.messageList(); - if (Utils.isNonEmptyArray(aList)) - { - self.folderInformation(oFolder.fullNameRaw, aList); - } - } - } - } - }); - - if (bBoot) - { - self.folderInformationMultiply(true); - } - } - } - }, aFolders); - } - }; - - AppApp.prototype.setMessageSeen = function (oMessage) - { - if (oMessage.unseen()) - { - oMessage.unseen(false); - - var oFolder = Cache.getFolderFromCacheList(oMessage.folderFullNameRaw); - if (oFolder) - { - oFolder.messageCountUnread(0 <= oFolder.messageCountUnread() - 1 ? - oFolder.messageCountUnread() - 1 : 0); - } - - Cache.storeMessageFlagsToCache(oMessage); - this.reloadFlagsCurrentMessageListAndMessageFromCache(); - } - - Remote.messageSetSeen(Utils.emptyFunction, oMessage.folderFullNameRaw, [oMessage.uid], true); - }; - - AppApp.prototype.googleConnect = function () - { - window.open(LinkBuilder.socialGoogle(), 'Google', 'left=200,top=100,width=650,height=600,menubar=no,status=no,resizable=yes,scrollbars=yes'); - }; - - AppApp.prototype.twitterConnect = function () - { - window.open(LinkBuilder.socialTwitter(), 'Twitter', 'left=200,top=100,width=650,height=350,menubar=no,status=no,resizable=yes,scrollbars=yes'); - }; - - AppApp.prototype.facebookConnect = function () - { - window.open(LinkBuilder.socialFacebook(), 'Facebook', 'left=200,top=100,width=650,height=335,menubar=no,status=no,resizable=yes,scrollbars=yes'); - }; - - /** - * @param {boolean=} bFireAllActions - */ - AppApp.prototype.socialUsers = function (bFireAllActions) - { - if (bFireAllActions) - { - Data.googleActions(true); - Data.facebookActions(true); - Data.twitterActions(true); - } - - Remote.socialUsers(function (sResult, oData) { - - if (Enums.StorageResultType.Success === sResult && oData && oData.Result) - { - Data.googleUserName(oData.Result['Google'] || ''); - Data.facebookUserName(oData.Result['Facebook'] || ''); - Data.twitterUserName(oData.Result['Twitter'] || ''); - } - else - { - Data.googleUserName(''); - Data.facebookUserName(''); - Data.twitterUserName(''); - } - - Data.googleLoggined('' !== Data.googleUserName()); - Data.facebookLoggined('' !== Data.facebookUserName()); - Data.twitterLoggined('' !== Data.twitterUserName()); - - Data.googleActions(false); - Data.facebookActions(false); - Data.twitterActions(false); - }); - }; - - AppApp.prototype.googleDisconnect = function () - { - Data.googleActions(true); - Remote.googleDisconnect(this.socialUsers); - }; - - AppApp.prototype.facebookDisconnect = function () - { - Data.facebookActions(true); - Remote.facebookDisconnect(this.socialUsers); - }; - - AppApp.prototype.twitterDisconnect = function () - { - Data.twitterActions(true); - Remote.twitterDisconnect(this.socialUsers); - }; - - /** - * @param {string} sQuery - * @param {Function} fCallback - */ - AppApp.prototype.getAutocomplete = function (sQuery, fCallback) - { - var - aData = [] - ; - - Remote.suggestions(function (sResult, oData) { - if (Enums.StorageResultType.Success === sResult && oData && Utils.isArray(oData.Result)) - { - aData = _.map(oData.Result, function (aItem) { - return aItem && aItem[0] ? new EmailModel(aItem[0], aItem[1]) : null; - }); - - fCallback(_.compact(aData)); - } - else if (Enums.StorageResultType.Abort !== sResult) - { - fCallback([]); - } - - }, sQuery); - }; - - /** - * @param {string} sQuery - * @param {Function} fCallback - */ - AppApp.prototype.getContactTagsAutocomplete = function (sQuery, fCallback) - { - fCallback(_.filter(Data.contactTags(), function (oContactTag) { - return oContactTag && oContactTag.filterHelper(sQuery); - })); - }; - - AppApp.prototype.setMessageList = function (oData, bCached) - { - if (oData && oData.Result && 'Collection/MessageCollection' === oData.Result['@Object'] && - oData.Result['@Collection'] && Utils.isArray(oData.Result['@Collection'])) - { - var - mLastCollapsedThreadUids = null, - iIndex = 0, - iLen = 0, - iCount = 0, - iOffset = 0, - aList = [], - iUtc = moment().unix(), - aStaticList = Data.staticMessageList, - oJsonMessage = null, - oMessage = null, - oFolder = null, - iNewCount = 0, - bUnreadCountChange = false - ; - - iCount = Utils.pInt(oData.Result.MessageResultCount); - iOffset = Utils.pInt(oData.Result.Offset); - - if (Utils.isNonEmptyArray(oData.Result.LastCollapsedThreadUids)) - { - mLastCollapsedThreadUids = oData.Result.LastCollapsedThreadUids; - } - - oFolder = Cache.getFolderFromCacheList( - Utils.isNormal(oData.Result.Folder) ? oData.Result.Folder : ''); - - if (oFolder && !bCached) - { - oFolder.interval = iUtc; - - Cache.setFolderHash(oData.Result.Folder, oData.Result.FolderHash); - - if (Utils.isNormal(oData.Result.MessageCount)) - { - oFolder.messageCountAll(oData.Result.MessageCount); - } - - if (Utils.isNormal(oData.Result.MessageUnseenCount)) - { - if (Utils.pInt(oFolder.messageCountUnread()) !== Utils.pInt(oData.Result.MessageUnseenCount)) - { - bUnreadCountChange = true; - } - - oFolder.messageCountUnread(oData.Result.MessageUnseenCount); - } - - Data.initUidNextAndNewMessages(oFolder.fullNameRaw, oData.Result.UidNext, oData.Result.NewMessages); - } - - if (bUnreadCountChange && oFolder) - { - Cache.clearMessageFlagsFromCacheByFolder(oFolder.fullNameRaw); - } - - for (iIndex = 0, iLen = oData.Result['@Collection'].length; iIndex < iLen; iIndex++) - { - oJsonMessage = oData.Result['@Collection'][iIndex]; - if (oJsonMessage && 'Object/Message' === oJsonMessage['@Object']) - { - oMessage = aStaticList[iIndex]; - if (!oMessage || !oMessage.initByJson(oJsonMessage)) - { - oMessage = MessageModel.newInstanceFromJson(oJsonMessage); - } - - if (oMessage) - { - if (Cache.hasNewMessageAndRemoveFromCache(oMessage.folderFullNameRaw, oMessage.uid) && 5 >= iNewCount) - { - iNewCount++; - oMessage.newForAnimation(true); - } - - oMessage.deleted(false); - - if (bCached) - { - Cache.initMessageFlagsFromCache(oMessage); - } - else - { - Cache.storeMessageFlagsToCache(oMessage); - } - - oMessage.lastInCollapsedThread(mLastCollapsedThreadUids && -1 < Utils.inArray(Utils.pInt(oMessage.uid), mLastCollapsedThreadUids) ? true : false); - - aList.push(oMessage); - } - } - } - - Data.messageListCount(iCount); - Data.messageListSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : ''); - Data.messageListPage(window.Math.ceil((iOffset / Data.messagesPerPage()) + 1)); - Data.messageListEndFolder(Utils.isNormal(oData.Result.Folder) ? oData.Result.Folder : ''); - Data.messageListEndSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : ''); - Data.messageListEndPage(Data.messageListPage()); - - Data.messageList(aList); - Data.messageListIsNotCompleted(false); - - if (aStaticList.length < aList.length) - { - Data.staticMessageList = aList; - } - - Cache.clearNewMessageCache(); - - if (oFolder && (bCached || bUnreadCountChange || Data.useThreads())) - { - this.folderInformation(oFolder.fullNameRaw, aList); - } - } - else - { - Data.messageListCount(0); - Data.messageList([]); - Data.messageListError(Utils.getNotification( - oData && oData.ErrorCode ? oData.ErrorCode : Enums.Notification.CantGetMessageList - )); - } - }; - - /** - * @param {string} sNamespace - * @param {Array} aFolders - * @return {Array} - */ - AppApp.prototype.folderResponseParseRec = function (sNamespace, aFolders) - { - var - self = this, - iIndex = 0, - iLen = 0, - oFolder = null, - oCacheFolder = null, - sFolderFullNameRaw = '', - aSubFolders = [], - aList = [] - ; - - for (iIndex = 0, iLen = aFolders.length; iIndex < iLen; iIndex++) - { - oFolder = aFolders[iIndex]; - if (oFolder) - { - sFolderFullNameRaw = oFolder.FullNameRaw; - - oCacheFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw); - if (!oCacheFolder) - { - oCacheFolder = FolderModel.newInstanceFromJson(oFolder); - if (oCacheFolder) - { - Cache.setFolderToCacheList(sFolderFullNameRaw, oCacheFolder); - Cache.setFolderFullNameRaw(oCacheFolder.fullNameHash, sFolderFullNameRaw); - } - } - - if (oCacheFolder) - { - oCacheFolder.collapsed(!self.isFolderExpanded(oCacheFolder.fullNameHash)); - - if (oFolder.Extended) - { - if (oFolder.Extended.Hash) - { - Cache.setFolderHash(oCacheFolder.fullNameRaw, oFolder.Extended.Hash); - } - - if (Utils.isNormal(oFolder.Extended.MessageCount)) - { - oCacheFolder.messageCountAll(oFolder.Extended.MessageCount); - } - - if (Utils.isNormal(oFolder.Extended.MessageUnseenCount)) - { - oCacheFolder.messageCountUnread(oFolder.Extended.MessageUnseenCount); - } - } - - aSubFolders = oFolder['SubFolders']; - if (aSubFolders && 'Collection/FolderCollection' === aSubFolders['@Object'] && - aSubFolders['@Collection'] && Utils.isArray(aSubFolders['@Collection'])) - { - oCacheFolder.subFolders( - this.folderResponseParseRec(sNamespace, aSubFolders['@Collection'])); - } - - aList.push(oCacheFolder); - } - } - } - - return aList; - }; - - /** - * @param {*} oData - */ - AppApp.prototype.setFolders = function (oData) - { - var - aList = [], - bUpdate = false, - fNormalizeFolder = function (sFolderFullNameRaw) { - return ('' === sFolderFullNameRaw || Consts.Values.UnuseOptionValue === sFolderFullNameRaw || - null !== Cache.getFolderFromCacheList(sFolderFullNameRaw)) ? sFolderFullNameRaw : ''; - } - ; - - if (oData && oData.Result && 'Collection/FolderCollection' === oData.Result['@Object'] && - oData.Result['@Collection'] && Utils.isArray(oData.Result['@Collection'])) - { - if (!Utils.isUnd(oData.Result.Namespace)) - { - Data.namespace = oData.Result.Namespace; - } - - Data.threading(!!Settings.settingsGet('UseImapThread') && oData.Result.IsThreadsSupported && true); - - aList = this.folderResponseParseRec(Data.namespace, oData.Result['@Collection']); - Data.folderList(aList); - - if (oData.Result['SystemFolders'] && - '' === '' + Settings.settingsGet('SentFolder') + Settings.settingsGet('DraftFolder') + - Settings.settingsGet('SpamFolder') + Settings.settingsGet('TrashFolder') + Settings.settingsGet('ArchiveFolder') + - Settings.settingsGet('NullFolder')) - { - // TODO Magic Numbers - Settings.settingsSet('SentFolder', oData.Result['SystemFolders'][2] || null); - Settings.settingsSet('DraftFolder', oData.Result['SystemFolders'][3] || null); - Settings.settingsSet('SpamFolder', oData.Result['SystemFolders'][4] || null); - Settings.settingsSet('TrashFolder', oData.Result['SystemFolders'][5] || null); - Settings.settingsSet('ArchiveFolder', oData.Result['SystemFolders'][12] || null); - - bUpdate = true; - } - - Data.sentFolder(fNormalizeFolder(Settings.settingsGet('SentFolder'))); - Data.draftFolder(fNormalizeFolder(Settings.settingsGet('DraftFolder'))); - Data.spamFolder(fNormalizeFolder(Settings.settingsGet('SpamFolder'))); - Data.trashFolder(fNormalizeFolder(Settings.settingsGet('TrashFolder'))); - Data.archiveFolder(fNormalizeFolder(Settings.settingsGet('ArchiveFolder'))); - - if (bUpdate) - { - Remote.saveSystemFolders(Utils.emptyFunction, { - 'SentFolder': Data.sentFolder(), - 'DraftFolder': Data.draftFolder(), - 'SpamFolder': Data.spamFolder(), - 'TrashFolder': Data.trashFolder(), - 'ArchiveFolder': Data.archiveFolder(), - 'NullFolder': 'NullFolder' - }); - } - - Local.set(Enums.ClientSideKeyName.FoldersLashHash, oData.Result.FoldersHash); - } - }; - - /** - * @param {string} sFullNameHash - * @return {boolean} - */ - AppApp.prototype.isFolderExpanded = function (sFullNameHash) - { - var aExpandedList = Local.get(Enums.ClientSideKeyName.ExpandedFolders); - return Utils.isArray(aExpandedList) && -1 !== _.indexOf(aExpandedList, sFullNameHash); - }; - - /** - * @param {string} sFullNameHash - * @param {boolean} bExpanded - */ - AppApp.prototype.setExpandedFolder = function (sFullNameHash, bExpanded) - { - var aExpandedList = Local.get(Enums.ClientSideKeyName.ExpandedFolders); - if (!Utils.isArray(aExpandedList)) - { - aExpandedList = []; - } - - if (bExpanded) - { - aExpandedList.push(sFullNameHash); - aExpandedList = _.uniq(aExpandedList); - } - else - { - aExpandedList = _.without(aExpandedList, sFullNameHash); - } - - Local.set(Enums.ClientSideKeyName.ExpandedFolders, aExpandedList); - }; - - AppApp.prototype.initLayoutResizer = function (sLeft, sRight, sClientSideKeyName) - { - var - iDisabledWidth = 60, - iMinWidth = 155, - oLeft = $(sLeft), - oRight = $(sRight), - - mLeftWidth = Local.get(sClientSideKeyName) || null, - - fSetWidth = function (iWidth) { - if (iWidth) - { - oLeft.css({ - 'width': '' + iWidth + 'px' - }); - - oRight.css({ - 'left': '' + iWidth + 'px' - }); - } - }, - - fDisable = function (bDisable) { - if (bDisable) - { - oLeft.resizable('disable'); - fSetWidth(iDisabledWidth); - } - else - { - oLeft.resizable('enable'); - var iWidth = Utils.pInt(Local.get(sClientSideKeyName)) || iMinWidth; - fSetWidth(iWidth > iMinWidth ? iWidth : iMinWidth); - } - }, - - fResizeFunction = function (oEvent, oObject) { - if (oObject && oObject.size && oObject.size.width) - { - Local.set(sClientSideKeyName, oObject.size.width); - - oRight.css({ - 'left': '' + oObject.size.width + 'px' - }); - } - } - ; - - if (null !== mLeftWidth) - { - fSetWidth(mLeftWidth > iMinWidth ? mLeftWidth : iMinWidth); - } - - oLeft.resizable({ - 'helper': 'ui-resizable-helper', - 'minWidth': iMinWidth, - 'maxWidth': 350, - 'handles': 'e', - 'stop': fResizeFunction - }); - - Events.sub('left-panel.off', function () { - fDisable(true); - }); - - Events.sub('left-panel.on', function () { - fDisable(false); - }); - }; - - AppApp.prototype.bootstartLoginScreen = function () - { - var sCustomLoginLink = Utils.pString(Settings.settingsGet('CustomLoginLink')); - if (!sCustomLoginLink) - { - kn.hideLoading(); - - kn.startScreens([ - require('Screen/App/Login') - ]); - - Plugins.runHook('rl-start-login-screens'); - Events.pub('rl.bootstart-login-screens'); - } - else - { - kn.routeOff(); - kn.setHash(LinkBuilder.root(), true); - kn.routeOff(); - - _.defer(function () { - window.location.href = sCustomLoginLink; - }); - } - }; - - AppApp.prototype.bootstart = function () - { - AbstractApp.prototype.bootstart.call(this); - - Data.populateDataOnStart(); - - var - self = this, - $LAB = require('$LAB'), - sJsHash = Settings.settingsGet('JsHash'), - iContactsSyncInterval = Utils.pInt(Settings.settingsGet('ContactsSyncInterval')), - bGoogle = Settings.settingsGet('AllowGoogleSocial'), - bFacebook = Settings.settingsGet('AllowFacebookSocial'), - bTwitter = Settings.settingsGet('AllowTwitterSocial') - ; - - Utils.initOnStartOrLangChange(function () { - - $.extend(true, $.magnificPopup.defaults, { - 'tClose': Utils.i18n('MAGNIFIC_POPUP/CLOSE'), - 'tLoading': Utils.i18n('MAGNIFIC_POPUP/LOADING'), - 'gallery': { - 'tPrev': Utils.i18n('MAGNIFIC_POPUP/GALLERY_PREV'), - 'tNext': Utils.i18n('MAGNIFIC_POPUP/GALLERY_NEXT'), - 'tCounter': Utils.i18n('MAGNIFIC_POPUP/GALLERY_COUNTER') - }, - 'image': { - 'tError': Utils.i18n('MAGNIFIC_POPUP/IMAGE_ERROR') - }, - 'ajax': { - 'tError': Utils.i18n('MAGNIFIC_POPUP/AJAX_ERROR') - } - }); - - }, this); - - if (SimplePace) - { - SimplePace.set(70); - SimplePace.sleep(); - } - - Globals.leftPanelDisabled.subscribe(function (bValue) { - Events.pub('left-panel.' + (bValue ? 'off' : 'on')); - }); - - if (!!Settings.settingsGet('Auth')) - { - this.setTitle(Utils.i18n('TITLES/LOADING')); - - self.folders(_.bind(function (bValue) { - - kn.hideLoading(); - - if (bValue) - { - if ($LAB && window.crypto && window.crypto.getRandomValues && Settings.capa(Enums.Capa.OpenPGP)) - { - $LAB.script(window.openpgp ? '' : LinkBuilder.openPgpJs()).wait(function () { - if (window.openpgp) - { - Data.openpgp = window.openpgp; - Data.openpgpKeyring = new window.openpgp.Keyring(); - Data.capaOpenPGP(true); - - Events.pub('openpgp.init'); - - self.reloadOpenPgpKeys(); - } - }); - } - else - { - Data.capaOpenPGP(false); - } - - kn.startScreens([ - require('Screen/App/MailBox'), - require('Screen/App/Settings'), - require('Screen/App/About') - ]); - - if (bGoogle || bFacebook || bTwitter) - { - self.socialUsers(true); - } - - Events.sub('interval.2m', function () { - self.folderInformation('INBOX'); - }); - - Events.sub('interval.2m', function () { - var sF = Data.currentFolderFullNameRaw(); - if ('INBOX' !== sF) - { - self.folderInformation(sF); - } - }); - - Events.sub('interval.3m', function () { - self.folderInformationMultiply(); - }); - - Events.sub('interval.5m', function () { - self.quota(); - }); - - Events.sub('interval.10m', function () { - self.folders(); - }); - - iContactsSyncInterval = 5 <= iContactsSyncInterval ? iContactsSyncInterval : 20; - iContactsSyncInterval = 320 >= iContactsSyncInterval ? iContactsSyncInterval : 320; - - window.setInterval(function () { - self.contactsSync(); - }, iContactsSyncInterval * 60000 + 5000); - - _.delay(function () { - self.contactsSync(); - }, 5000); - - _.delay(function () { - self.folderInformationMultiply(true); - }, 500); - - Plugins.runHook('rl-start-user-screens'); - Events.pub('rl.bootstart-user-screens'); - - if (!!Settings.settingsGet('AccountSignMe') && window.navigator.registerProtocolHandler) - { - _.delay(function () { - try { - window.navigator.registerProtocolHandler('mailto', - window.location.protocol + '//' + window.location.host + window.location.pathname + '?mailto&to=%s', - '' + (Settings.settingsGet('Title') || 'RainLoop')); - } catch(e) {} - - if (Settings.settingsGet('MailToEmail')) - { - Utils.mailToHelper(Settings.settingsGet('MailToEmail'), require('View/Popup/Compose')); - } - }, 500); - } - - if (!Globals.bMobileDevice) - { - _.defer(function () { - self.initLayoutResizer('#rl-left', '#rl-right', Enums.ClientSideKeyName.FolderListSize); - }); - } - } - else - { - self.bootstartLoginScreen(); - } - - if (SimplePace) - { - SimplePace.set(100); - } - - }, self)); - } - else - { - this.bootstartLoginScreen(); - - if (SimplePace) - { - SimplePace.set(100); - } - } - - if (bGoogle) - { - window['rl_' + sJsHash + '_google_service'] = function () { - Data.googleActions(true); - self.socialUsers(); - }; - } - - if (bFacebook) - { - window['rl_' + sJsHash + '_facebook_service'] = function () { - Data.facebookActions(true); - self.socialUsers(); - }; - } - - if (bTwitter) - { - window['rl_' + sJsHash + '_twitter_service'] = function () { - Data.twitterActions(true); - self.socialUsers(); - }; - } - - Events.sub('interval.1m', function () { - Globals.momentTrigger(!Globals.momentTrigger()); - }); - - Plugins.runHook('rl-start-screens'); - Events.pub('rl.bootstart-end'); - }; - - module.exports = new AppApp(); - -}()); \ No newline at end of file diff --git a/dev/App/User.js b/dev/App/User.js new file mode 100644 index 000000000..616451270 --- /dev/null +++ b/dev/App/User.js @@ -0,0 +1,1533 @@ + +(function () { + + 'use strict'; + + var + window = require('window'), + _ = require('_'), + $ = require('$'), + SimplePace = require('SimplePace'), + Tinycon = require('Tinycon'), + + Enums = require('Common/Enums'), + Globals = require('Common/Globals'), + Consts = require('Common/Consts'), + Plugins = require('Common/Plugins'), + Utils = require('Common/Utils'), + Links = require('Common/Links'), + Events = require('Common/Events'), + + Translator = require('Common/Translator'), + Momentor = require('Common/Momentor'), + + kn = require('Knoin/Knoin'), + + Cache = require('Common/Cache'), + + SocialStore = require('Stores/Social'), + SettingsStore = require('Stores/User/Settings'), + AccountStore = require('Stores/User/Account'), + IdentityStore = require('Stores/User/Identity'), + TemplateStore = require('Stores/User/Template'), + FolderStore = require('Stores/User/Folder'), + PgpStore = require('Stores/User/Pgp'), + MessageStore = require('Stores/User/Message'), + ContactStore = require('Stores/User/Contact'), + + Local = require('Storage/Client'), + Settings = require('Storage/Settings'), + + Remote = require('Remote/User/Ajax'), + Promises = require('Promises/User/Ajax'), + + EmailModel = require('Model/Email'), + AccountModel = require('Model/Account'), + IdentityModel = require('Model/Identity'), + TemplateModel = require('Model/Template'), + OpenPgpKeyModel = require('Model/OpenPgpKey'), + + AbstractApp = require('App/Abstract') + ; + + /** + * @constructor + * @extends AbstractApp + */ + function AppUser() + { + AbstractApp.call(this, Remote); + + this.oMoveCache = {}; + + this.quotaDebounce = _.debounce(this.quota, 1000 * 30); + this.moveOrDeleteResponseHelper = _.bind(this.moveOrDeleteResponseHelper, this); + + this.messagesMoveTrigger = _.debounce(this.messagesMoveTrigger, 500); + + window.setInterval(function () { + Events.pub('interval.30s'); + }, 30000); + + window.setInterval(function () { + Events.pub('interval.1m'); + }, 60000); + + window.setInterval(function () { + Events.pub('interval.2m'); + }, 60000 * 2); + + window.setInterval(function () { + Events.pub('interval.3m'); + }, 60000 * 3); + + window.setInterval(function () { + Events.pub('interval.5m'); + }, 60000 * 5); + + window.setInterval(function () { + Events.pub('interval.10m'); + }, 60000 * 10); + + window.setInterval(function () { + Events.pub('interval.15m'); + }, 60000 * 15); + + window.setInterval(function () { + Events.pub('interval.20m'); + }, 60000 * 15); + + window.setTimeout(function () { + window.setInterval(function () { + Events.pub('interval.5m-after5m'); + }, 60000 * 5); + }, 60000 * 5); + + window.setTimeout(function () { + window.setInterval(function () { + Events.pub('interval.10m-after5m'); + }, 60000 * 10); + }, 60000 * 5); + + $.wakeUp(function () { + + Remote.jsVersion(function (sResult, oData) { + if (Enums.StorageResultType.Success === sResult && oData && !oData.Result) + { + if (window.parent && !!Settings.settingsGet('InIframe')) + { + window.parent.location.reload(); + } + else + { + window.location.reload(); + } + } + }, Settings.settingsGet('Version')); + + }, {}, 60 * 60 * 1000); + + if (Settings.settingsGet('UserBackgroundHash')) + { + _.delay(function () { + $('#rl-bg').attr('style', 'background-image: none !important;') + .backstretch(Links.userBackground(Settings.settingsGet('UserBackgroundHash')), { + 'fade': Globals.bAnimationSupported ? 1000 : 0, 'centeredX': true, 'centeredY': true + }).removeAttr('style'); + }, 1000); + } + + this.socialUsers = _.bind(this.socialUsers, this); + } + + _.extend(AppUser.prototype, AbstractApp.prototype); + + AppUser.prototype.remote = function () + { + return Remote; + }; + + AppUser.prototype.reloadFlagsCurrentMessageListAndMessageFromCache = function () + { + _.each(MessageStore.messageList(), function (oMessage) { + Cache.initMessageFlagsFromCache(oMessage); + }); + + _.each(MessageStore.messageThreadList(), function (oMessage) { + Cache.initMessageFlagsFromCache(oMessage); + }); + + Cache.initMessageFlagsFromCache(MessageStore.message()); + + this.reloadFlagsCurrentMessageThreadListFromCache(); + }; + + AppUser.prototype.reloadFlagsCurrentMessageThreadListFromCache = function () + { + _.each(MessageStore.messageThreadList(), function (oMessage) { + Cache.initMessageFlagsFromCache(oMessage); + }); + }; + + /** + * @param {boolean=} bDropPagePosition = false + * @param {boolean=} bDropCurrenFolderCache = false + */ + AppUser.prototype.reloadMessageList = function (bDropPagePosition, bDropCurrenFolderCache) + { + var + iOffset = (MessageStore.messageListPage() - 1) * SettingsStore.messagesPerPage() + ; + + if (Utils.isUnd(bDropCurrenFolderCache) ? false : !!bDropCurrenFolderCache) + { + Cache.setFolderHash(FolderStore.currentFolderFullNameRaw(), ''); + } + + if (Utils.isUnd(bDropPagePosition) ? false : !!bDropPagePosition) + { + MessageStore.messageListPage(1); + iOffset = 0; + } + + MessageStore.messageListLoading(true); + Remote.messageList(function (sResult, oData, bCached) { + + if (Enums.StorageResultType.Success === sResult && oData && oData.Result) + { + MessageStore.messageListError(''); + MessageStore.messageListLoading(false); + + MessageStore.setMessageList(oData, bCached); + } + else if (Enums.StorageResultType.Unload === sResult) + { + MessageStore.messageListError(''); + MessageStore.messageListLoading(false); + } + else if (Enums.StorageResultType.Abort !== sResult) + { + MessageStore.messageList([]); + MessageStore.messageListLoading(false); + MessageStore.messageListError(oData && oData.ErrorCode ? + Translator.getNotification(oData.ErrorCode) : Translator.i18n('NOTIFICATIONS/CANT_GET_MESSAGE_LIST') + ); + } + + }, FolderStore.currentFolderFullNameRaw(), iOffset, SettingsStore.messagesPerPage(), MessageStore.messageListSearch()); + }; + + AppUser.prototype.recacheInboxMessageList = function () + { + Remote.messageList(Utils.emptyFunction, Cache.getFolderInboxName(), 0, SettingsStore.messagesPerPage(), '', true); + }; + + /** + * @param {Function} fResultFunc + * @return {boolean} + */ + AppUser.prototype.contactsSync = function (fResultFunc) + { + var oContacts = ContactStore.contacts; + if (oContacts.importing() || oContacts.syncing() || !ContactStore.enableContactsSync() || !ContactStore.allowContactsSync()) + { + return false; + } + + oContacts.syncing(true); + + Remote.contactsSync(function (sResult, oData) { + + oContacts.syncing(false); + + if (fResultFunc) + { + fResultFunc(sResult, oData); + } + }); + + return true; + }; + + AppUser.prototype.messagesMoveTrigger = function () + { + var + self = this, + sSpamFolder = FolderStore.spamFolder() + ; + + _.each(this.oMoveCache, function (oItem) { + + var + bSpam = sSpamFolder === oItem['To'], + bHam = !bSpam && sSpamFolder === oItem['From'] && Cache.getFolderInboxName() === oItem['To'] + ; + + Remote.messagesMove(self.moveOrDeleteResponseHelper, oItem['From'], oItem['To'], oItem['Uid'], + bSpam ? 'SPAM' : (bHam ? 'HAM' : '')); + }); + + this.oMoveCache = {}; + }; + + AppUser.prototype.messagesMoveHelper = function (sFromFolderFullNameRaw, sToFolderFullNameRaw, aUidForMove) + { + var sH = '$$' + sFromFolderFullNameRaw + '$$' + sToFolderFullNameRaw + '$$'; + if (!this.oMoveCache[sH]) + { + this.oMoveCache[sH] = { + 'From': sFromFolderFullNameRaw, + 'To': sToFolderFullNameRaw, + 'Uid': [] + }; + } + + this.oMoveCache[sH]['Uid'] = _.union(this.oMoveCache[sH]['Uid'], aUidForMove); + this.messagesMoveTrigger(); + }; + + AppUser.prototype.messagesCopyHelper = function (sFromFolderFullNameRaw, sToFolderFullNameRaw, aUidForCopy) + { + Remote.messagesCopy( + this.moveOrDeleteResponseHelper, + sFromFolderFullNameRaw, + sToFolderFullNameRaw, + aUidForCopy + ); + }; + + AppUser.prototype.messagesDeleteHelper = function (sFromFolderFullNameRaw, aUidForRemove) + { + Remote.messagesDelete( + this.moveOrDeleteResponseHelper, + sFromFolderFullNameRaw, + aUidForRemove + ); + }; + + AppUser.prototype.moveOrDeleteResponseHelper = function (sResult, oData) + { + if (Enums.StorageResultType.Success === sResult && FolderStore.currentFolder()) + { + if (oData && Utils.isArray(oData.Result) && 2 === oData.Result.length) + { + Cache.setFolderHash(oData.Result[0], oData.Result[1]); + } + else + { + Cache.setFolderHash(FolderStore.currentFolderFullNameRaw(), ''); + + if (oData && -1 < Utils.inArray(oData.ErrorCode, + [Enums.Notification.CantMoveMessage, Enums.Notification.CantCopyMessage])) + { + window.alert(Translator.getNotification(oData.ErrorCode)); + } + } + + this.reloadMessageList(0 === MessageStore.messageList().length); + this.quotaDebounce(); + } + }; + + /** + * @param {string} sFromFolderFullNameRaw + * @param {Array} aUidForRemove + */ + AppUser.prototype.deleteMessagesFromFolderWithoutCheck = function (sFromFolderFullNameRaw, aUidForRemove) + { + this.messagesDeleteHelper(sFromFolderFullNameRaw, aUidForRemove); + MessageStore.removeMessagesFromList(sFromFolderFullNameRaw, aUidForRemove); + }; + + /** + * @param {number} iDeleteType + * @param {string} sFromFolderFullNameRaw + * @param {Array} aUidForRemove + * @param {boolean=} bUseFolder = true + */ + AppUser.prototype.deleteMessagesFromFolder = function (iDeleteType, sFromFolderFullNameRaw, aUidForRemove, bUseFolder) + { + var + self = this, + oMoveFolder = null, + nSetSystemFoldersNotification = null + ; + + switch (iDeleteType) + { + case Enums.FolderType.Spam: + oMoveFolder = Cache.getFolderFromCacheList(FolderStore.spamFolder()); + nSetSystemFoldersNotification = Enums.SetSystemFoldersNotification.Spam; + break; + case Enums.FolderType.NotSpam: + oMoveFolder = Cache.getFolderFromCacheList(Cache.getFolderInboxName()); + break; + case Enums.FolderType.Trash: + oMoveFolder = Cache.getFolderFromCacheList(FolderStore.trashFolder()); + nSetSystemFoldersNotification = Enums.SetSystemFoldersNotification.Trash; + break; + case Enums.FolderType.Archive: + oMoveFolder = Cache.getFolderFromCacheList(FolderStore.archiveFolder()); + nSetSystemFoldersNotification = Enums.SetSystemFoldersNotification.Archive; + break; + } + + bUseFolder = Utils.isUnd(bUseFolder) ? true : !!bUseFolder; + if (bUseFolder) + { + if ((Enums.FolderType.Spam === iDeleteType && Consts.Values.UnuseOptionValue === FolderStore.spamFolder()) || + (Enums.FolderType.Trash === iDeleteType && Consts.Values.UnuseOptionValue === FolderStore.trashFolder()) || + (Enums.FolderType.Archive === iDeleteType && Consts.Values.UnuseOptionValue === FolderStore.archiveFolder())) + { + bUseFolder = false; + } + } + + if (!oMoveFolder && bUseFolder) + { + kn.showScreenPopup(require('View/Popup/FolderSystem'), [nSetSystemFoldersNotification]); + } + else if (!bUseFolder || (Enums.FolderType.Trash === iDeleteType && + (sFromFolderFullNameRaw === FolderStore.spamFolder() || sFromFolderFullNameRaw === FolderStore.trashFolder()))) + { + kn.showScreenPopup(require('View/Popup/Ask'), [Translator.i18n('POPUPS_ASK/DESC_WANT_DELETE_MESSAGES'), function () { + + self.messagesDeleteHelper(sFromFolderFullNameRaw, aUidForRemove); + MessageStore.removeMessagesFromList(sFromFolderFullNameRaw, aUidForRemove); + + }]); + } + else if (oMoveFolder) + { + this.messagesMoveHelper(sFromFolderFullNameRaw, oMoveFolder.fullNameRaw, aUidForRemove); + MessageStore.removeMessagesFromList(sFromFolderFullNameRaw, aUidForRemove, oMoveFolder.fullNameRaw); + } + }; + + /** + * @param {string} sFromFolderFullNameRaw + * @param {Array} aUidForMove + * @param {string} sToFolderFullNameRaw + * @param {boolean=} bCopy = false + */ + AppUser.prototype.moveMessagesToFolder = function (sFromFolderFullNameRaw, aUidForMove, sToFolderFullNameRaw, bCopy) + { + if (sFromFolderFullNameRaw !== sToFolderFullNameRaw && Utils.isArray(aUidForMove) && 0 < aUidForMove.length) + { + var + oFromFolder = Cache.getFolderFromCacheList(sFromFolderFullNameRaw), + oToFolder = Cache.getFolderFromCacheList(sToFolderFullNameRaw) + ; + + if (oFromFolder && oToFolder) + { + if (Utils.isUnd(bCopy) ? false : !!bCopy) + { + this.messagesCopyHelper(oFromFolder.fullNameRaw, oToFolder.fullNameRaw, aUidForMove); + } + else + { + this.messagesMoveHelper(oFromFolder.fullNameRaw, oToFolder.fullNameRaw, aUidForMove); + } + + MessageStore.removeMessagesFromList(oFromFolder.fullNameRaw, aUidForMove, oToFolder.fullNameRaw, bCopy); + return true; + } + } + + return false; + }; + + /** + * @param {Function=} fCallback + */ + AppUser.prototype.foldersReload = function (fCallback) + { + Promises.foldersReload(FolderStore.foldersLoading).then(function (bValue) { + if (fCallback) + { + fCallback(!!bValue); + } + }).fail(function () { + if (fCallback) + { + fCallback(false); + } + }); + }; + + AppUser.prototype.foldersPromisesActionHelper = function (oPromise, iErrorDefCode) + { + Promises + .abort('Folders') + .fastResolve(true) + .then(function () { + return oPromise; + }) + .fail(function (iErrorCode) { + FolderStore.folderList.error(Translator.getNotification(iErrorCode, '', iErrorDefCode)); + }).fin(function () { + Promises.foldersReloadWithTimeout(FolderStore.foldersLoading); + }).done() + ; + }; + + AppUser.prototype.reloadOpenPgpKeys = function () + { + if (PgpStore.capaOpenPGP()) + { + var + aKeys = [], + oEmail = new EmailModel(), + oOpenpgpKeyring = PgpStore.openpgpKeyring, + oOpenpgpKeys = oOpenpgpKeyring ? oOpenpgpKeyring.getAllKeys() : [] + ; + + _.each(oOpenpgpKeys, function (oItem, iIndex) { + if (oItem && oItem.primaryKey) + { + var + + oPrimaryUser = oItem.getPrimaryUser(), + sUser = (oPrimaryUser && oPrimaryUser.user) ? oPrimaryUser.user.userId.userid + : (oItem.users && oItem.users[0] ? oItem.users[0].userId.userid : '') + ; + + oEmail.clear(); + oEmail.mailsoParse(sUser); + + if (oEmail.validate()) + { + aKeys.push(new OpenPgpKeyModel( + iIndex, + oItem.primaryKey.getFingerprint(), + oItem.primaryKey.getKeyId().toHex().toLowerCase(), + sUser, + oEmail.email, + oItem.isPrivate(), + oItem.armor()) + ); + } + } + }); + + Utils.delegateRunOnDestroy(PgpStore.openpgpkeys()); + PgpStore.openpgpkeys(aKeys); + } + }; + + AppUser.prototype.accountsCounts = function () + { + return false; +// AccountStore.accounts.loading(true); +// +// Remote.accountsCounts(function (sResult, oData) { +// +// AccountStore.accounts.loading(false); +// +// if (Enums.StorageResultType.Success === sResult && oData.Result && oData.Result['Counts']) +// { +// var +// sEmail = AccountStore.email(), +// aAcounts = AccountStore.accounts() +// ; +// +// _.each(oData.Result['Counts'], function (oItem) { +// +// var oAccount = _.find(aAcounts, function (oAccount) { +// return oAccount && oItem[0] === oAccount.email && sEmail !== oAccount.email; +// }); +// +// if (oAccount) +// { +// oAccount.count(Utils.pInt(oItem[1])); +// } +// }); +// } +// }); + }; + + AppUser.prototype.accountsAndIdentities = function (bBoot) + { + var self = this; + + AccountStore.accounts.loading(true); + IdentityStore.identities.loading(true); + + Remote.accountsAndIdentities(function (sResult, oData) { + + AccountStore.accounts.loading(false); + IdentityStore.identities.loading(false); + + if (Enums.StorageResultType.Success === sResult && oData.Result) + { + var + aCounts = {}, + sParentEmail = Settings.settingsGet('ParentEmail'), + sAccountEmail = AccountStore.email() + ; + + sParentEmail = '' === sParentEmail ? sAccountEmail : sParentEmail; + + if (Utils.isArray(oData.Result['Accounts'])) + { + _.each(AccountStore.accounts(), function (oAccount) { + aCounts[oAccount.email] = oAccount.count(); + }); + + Utils.delegateRunOnDestroy(AccountStore.accounts()); + + AccountStore.accounts(_.map(oData.Result['Accounts'], function (sValue) { + return new AccountModel(sValue, sValue !== sParentEmail, aCounts[sValue] || 0); + })); + } + + if (Utils.isUnd(bBoot) ? false : !!bBoot) + { + _.delay(function () { + self.accountsCounts(); + }, 1000 * 5); + + Events.sub('interval.10m-after5m', function () { + self.accountsCounts(); + }); + } + + if (Utils.isArray(oData.Result['Identities'])) + { + Utils.delegateRunOnDestroy(IdentityStore.identities()); + + IdentityStore.identities(_.map(oData.Result['Identities'], function (oIdentityData) { + + var + sId = Utils.pString(oIdentityData['Id']), + sEmail = Utils.pString(oIdentityData['Email']), + oIdentity = new IdentityModel(sId, sEmail) + ; + + oIdentity.name(Utils.pString(oIdentityData['Name'])); + oIdentity.replyTo(Utils.pString(oIdentityData['ReplyTo'])); + oIdentity.bcc(Utils.pString(oIdentityData['Bcc'])); + oIdentity.signature(Utils.pString(oIdentityData['Signature'])); + oIdentity.signatureInsertBefore(!!oIdentityData['SignatureInsertBefore']); + + return oIdentity; + })); + } + } + }); + }; + + AppUser.prototype.templates = function () + { + TemplateStore.templates.loading(true); + + Remote.templates(function (sResult, oData) { + + TemplateStore.templates.loading(false); + + if (Enums.StorageResultType.Success === sResult && oData.Result && + Utils.isArray(oData.Result['Templates'])) + { + Utils.delegateRunOnDestroy(TemplateStore.templates()); + + TemplateStore.templates(_.compact(_.map(oData.Result['Templates'], function (oTemplateData) { + var oTemplate = new TemplateModel(); + return oTemplate.parse(oTemplateData) ? oTemplate : null; + }))); + } + }); + }; + + AppUser.prototype.quota = function () + { + Remote.quota(function (sResult, oData) { + if (Enums.StorageResultType.Success === sResult && oData && oData.Result && + Utils.isArray(oData.Result) && 1 < oData.Result.length && + Utils.isPosNumeric(oData.Result[0], true) && Utils.isPosNumeric(oData.Result[1], true)) + { + require('Stores/User/Quota').populateData( + Utils.pInt(oData.Result[1]), Utils.pInt(oData.Result[0])); + } + }); + }; + + /** + * @param {string} sFolder + * @param {Array=} aList = [] + */ + AppUser.prototype.folderInformation = function (sFolder, aList) + { + if ('' !== Utils.trim(sFolder)) + { + var self = this; + Remote.folderInformation(function (sResult, oData) { + if (Enums.StorageResultType.Success === sResult) + { + if (oData && oData.Result && oData.Result.Hash && oData.Result.Folder) + { + var + iUtc = Momentor.momentNowUnix(), + sHash = Cache.getFolderHash(oData.Result.Folder), + oFolder = Cache.getFolderFromCacheList(oData.Result.Folder), + bCheck = false, + sUid = '', + aList = [], + bUnreadCountChange = false, + oFlags = null + ; + + if (oFolder) + { + oFolder.interval = iUtc; + + if (oData.Result.Hash) + { + Cache.setFolderHash(oData.Result.Folder, oData.Result.Hash); + } + + if (Utils.isNormal(oData.Result.MessageCount)) + { + oFolder.messageCountAll(oData.Result.MessageCount); + } + + if (Utils.isNormal(oData.Result.MessageUnseenCount)) + { + if (Utils.pInt(oFolder.messageCountUnread()) !== Utils.pInt(oData.Result.MessageUnseenCount)) + { + bUnreadCountChange = true; + } + + oFolder.messageCountUnread(oData.Result.MessageUnseenCount); + } + + if (bUnreadCountChange) + { + Cache.clearMessageFlagsFromCacheByFolder(oFolder.fullNameRaw); + } + + if (oData.Result.Flags) + { + for (sUid in oData.Result.Flags) + { + if (oData.Result.Flags.hasOwnProperty(sUid)) + { + bCheck = true; + oFlags = oData.Result.Flags[sUid]; + Cache.storeMessageFlagsToCacheByFolderAndUid(oFolder.fullNameRaw, sUid.toString(), [ + !oFlags['IsSeen'], !!oFlags['IsFlagged'], !!oFlags['IsAnswered'], !!oFlags['IsForwarded'], !!oFlags['IsReadReceipt'] + ]); + } + } + + if (bCheck) + { + self.reloadFlagsCurrentMessageListAndMessageFromCache(); + } + } + + MessageStore.initUidNextAndNewMessages(oFolder.fullNameRaw, oData.Result.UidNext, oData.Result.NewMessages); + + if (oData.Result.Hash !== sHash || '' === sHash) + { + if (oFolder.fullNameRaw === FolderStore.currentFolderFullNameRaw()) + { + self.reloadMessageList(); + } + else if (Cache.getFolderInboxName() === oFolder.fullNameRaw) + { + self.recacheInboxMessageList(); + } + } + else if (bUnreadCountChange) + { + if (oFolder.fullNameRaw === FolderStore.currentFolderFullNameRaw()) + { + aList = MessageStore.messageList(); + if (Utils.isNonEmptyArray(aList)) + { + self.folderInformation(oFolder.fullNameRaw, aList); + } + } + } + } + } + } + }, sFolder, aList); + } + }; + + /** + * @param {boolean=} bBoot = false + */ + AppUser.prototype.folderInformationMultiply = function (bBoot) + { + bBoot = Utils.isUnd(bBoot) ? false : !!bBoot; + + var + self = this, + iUtc = Momentor.momentNowUnix(), + aFolders = FolderStore.getNextFolderNames(bBoot) + ; + + if (Utils.isNonEmptyArray(aFolders)) + { + Remote.folderInformationMultiply(function (sResult, oData) { + if (Enums.StorageResultType.Success === sResult) + { + if (oData && oData.Result && oData.Result.List && Utils.isNonEmptyArray(oData.Result.List)) + { + _.each(oData.Result.List, function (oItem) { + + var + aList = [], + sHash = Cache.getFolderHash(oItem.Folder), + oFolder = Cache.getFolderFromCacheList(oItem.Folder), + bUnreadCountChange = false + ; + + if (oFolder) + { + oFolder.interval = iUtc; + + if (oItem.Hash) + { + Cache.setFolderHash(oItem.Folder, oItem.Hash); + } + + if (Utils.isNormal(oItem.MessageCount)) + { + oFolder.messageCountAll(oItem.MessageCount); + } + + if (Utils.isNormal(oItem.MessageUnseenCount)) + { + if (Utils.pInt(oFolder.messageCountUnread()) !== Utils.pInt(oItem.MessageUnseenCount)) + { + bUnreadCountChange = true; + } + + oFolder.messageCountUnread(oItem.MessageUnseenCount); + } + + if (bUnreadCountChange) + { + Cache.clearMessageFlagsFromCacheByFolder(oFolder.fullNameRaw); + } + + if (oItem.Hash !== sHash || '' === sHash) + { + if (oFolder.fullNameRaw === FolderStore.currentFolderFullNameRaw()) + { + self.reloadMessageList(); + } + } + else if (bUnreadCountChange) + { + if (oFolder.fullNameRaw === FolderStore.currentFolderFullNameRaw()) + { + aList = MessageStore.messageList(); + if (Utils.isNonEmptyArray(aList)) + { + self.folderInformation(oFolder.fullNameRaw, aList); + } + } + } + } + }); + + if (bBoot) + { _.delay(function () { + self.folderInformationMultiply(true); + }, 2000); + } + } + } + }, aFolders); + } + }; + + /** + * @param {string} sFolderFullNameRaw + * @param {string|bool} mUid + * @param {number} iSetAction + * @param {Array=} aMessages = null + */ + AppUser.prototype.messageListAction = function (sFolderFullNameRaw, mUid, iSetAction, aMessages) + { + var + bRoot = false, + aAllUids = [], + aRootUids = [], + oFolder = null, + iAlreadyUnread = 0 + ; + + if (Utils.isUnd(aMessages)) + { + aMessages = MessageStore.messageListChecked(); + } + + if (true === mUid) + { + bRoot = true; + } + else if (aMessages && aMessages[0] && mUid && + sFolderFullNameRaw === aMessages[0].folderFullNameRaw && mUid === aMessages[0].uid) + { + bRoot = true; + } + + _.each(aMessages, function (oMessage) { + if (oMessage && oMessage.uid && oMessage.threads) + { + aRootUids.push(oMessage.uid); + aAllUids = _.union(aAllUids, oMessage.threads(), [oMessage.uid]); + } + }); + + aAllUids = _.uniq(aAllUids); + aRootUids = _.uniq(aRootUids); + + if ('' !== sFolderFullNameRaw && 0 < aAllUids.length) + { + switch (iSetAction) { + case Enums.MessageSetAction.SetSeen: + + _.each(bRoot ? aAllUids : aRootUids, function (sSubUid) { + iAlreadyUnread += Cache.storeMessageFlagsToCacheBySetAction( + sFolderFullNameRaw, sSubUid, iSetAction); + }); + + oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw); + if (oFolder) + { + oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread); + } + + Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, bRoot ? aAllUids : aRootUids, true); + break; + + case Enums.MessageSetAction.UnsetSeen: + + _.each(aRootUids, function (sSubUid) { + iAlreadyUnread += Cache.storeMessageFlagsToCacheBySetAction( + sFolderFullNameRaw, sSubUid, iSetAction); + }); + + oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw); + if (oFolder) + { + oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread + aRootUids.length); + } + + Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, aRootUids, false); + break; + + case Enums.MessageSetAction.SetFlag: + + _.each(aRootUids, function (sSubUid) { + Cache.storeMessageFlagsToCacheBySetAction( + sFolderFullNameRaw, sSubUid, iSetAction); + }); + + Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, aRootUids, true); + break; + + case Enums.MessageSetAction.UnsetFlag: + + _.each(aRootUids, function (sSubUid) { +// _.each(bRoot ? aAllUids : aRootUids, function (sSubUid) { + Cache.storeMessageFlagsToCacheBySetAction( + sFolderFullNameRaw, sSubUid, iSetAction); + }); + + Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, aRootUids, false); +// Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, bRoot ? aAllUids : aRootUids, false); + break; + } + + this.reloadFlagsCurrentMessageListAndMessageFromCache(); + MessageStore.message.viewTrigger(!MessageStore.message.viewTrigger()); + } + }; + + AppUser.prototype.googleConnect = function () + { + window.open(Links.socialGoogle(), 'Google', 'left=200,top=100,width=650,height=600,menubar=no,status=no,resizable=yes,scrollbars=yes'); + }; + + AppUser.prototype.twitterConnect = function () + { + window.open(Links.socialTwitter(), 'Twitter', 'left=200,top=100,width=650,height=350,menubar=no,status=no,resizable=yes,scrollbars=yes'); + }; + + AppUser.prototype.facebookConnect = function () + { + window.open(Links.socialFacebook(), 'Facebook', 'left=200,top=100,width=650,height=335,menubar=no,status=no,resizable=yes,scrollbars=yes'); + }; + + /** + * @param {boolean=} bFireAllActions + */ + AppUser.prototype.socialUsers = function (bFireAllActions) + { + if (true === bFireAllActions) + { + SocialStore.google.loading(true); + SocialStore.facebook.loading(true); + SocialStore.twitter.loading(true); + } + + Remote.socialUsers(function (sResult, oData) { + + if (Enums.StorageResultType.Success === sResult && oData && oData.Result) + { + SocialStore.google.userName(oData.Result['Google'] || ''); + SocialStore.facebook.userName(oData.Result['Facebook'] || ''); + SocialStore.twitter.userName(oData.Result['Twitter'] || ''); + } + else + { + SocialStore.google.userName(''); + SocialStore.facebook.userName(''); + SocialStore.twitter.userName(''); + } + + SocialStore.google.loading(false); + SocialStore.facebook.loading(false); + SocialStore.twitter.loading(false); + }); + }; + + AppUser.prototype.googleDisconnect = function () + { + SocialStore.google.loading(true); + Remote.googleDisconnect(this.socialUsers); + }; + + AppUser.prototype.facebookDisconnect = function () + { + SocialStore.facebook.loading(true); + Remote.facebookDisconnect(this.socialUsers); + }; + + AppUser.prototype.twitterDisconnect = function () + { + SocialStore.twitter.loading(true); + Remote.twitterDisconnect(this.socialUsers); + }; + + /** + * @param {string} sQuery + * @param {Function} fCallback + */ + AppUser.prototype.getAutocomplete = function (sQuery, fCallback) + { + var + aData = [] + ; + + Remote.suggestions(function (sResult, oData) { + if (Enums.StorageResultType.Success === sResult && oData && Utils.isArray(oData.Result)) + { + aData = _.map(oData.Result, function (aItem) { + return aItem && aItem[0] ? new EmailModel(aItem[0], aItem[1]) : null; + }); + + fCallback(_.compact(aData)); + } + else if (Enums.StorageResultType.Abort !== sResult) + { + fCallback([]); + } + + }, sQuery); + }; + + /** + * @param {string} sFullNameHash + * @param {boolean} bExpanded + */ + AppUser.prototype.setExpandedFolder = function (sFullNameHash, bExpanded) + { + var aExpandedList = Local.get(Enums.ClientSideKeyName.ExpandedFolders); + if (!Utils.isArray(aExpandedList)) + { + aExpandedList = []; + } + + if (bExpanded) + { + aExpandedList.push(sFullNameHash); + aExpandedList = _.uniq(aExpandedList); + } + else + { + aExpandedList = _.without(aExpandedList, sFullNameHash); + } + + Local.set(Enums.ClientSideKeyName.ExpandedFolders, aExpandedList); + }; + + AppUser.prototype.initHorizontalLayoutResizer = function (sClientSideKeyName) + { + var + iMinHeight = 200, + iMaxHeight = 500, + oTop = null, + oBottom = null, + + fResizeFunction = function (oEvent, oObject) { + if (oObject && oObject.size && oObject.size.height) + { + Local.set(sClientSideKeyName, oObject.size.height); + + fSetHeight(oObject.size.height); + + Utils.windowResize(); + } + }, + + oOptions = { + 'helper': 'ui-resizable-helper-h', + 'minHeight': iMinHeight, + 'maxHeight': iMaxHeight, + 'handles': 's', + 'stop': fResizeFunction + }, + + fSetHeight = function (iHeight) { + if (iHeight) + { + if (oTop) + { + oTop.attr('style', 'height:' + iHeight + 'px'); + } + + if (oBottom) + { + oBottom.attr('style', 'top:' + (55 /* top toolbar */ + iHeight) + 'px'); + } + } + }, + + fDisable = function (bDisable) { + if (bDisable) + { + if (oTop && oTop.hasClass('ui-resizable')) + { + oTop + .resizable('destroy') + .removeAttr('style') + ; + } + + if (oBottom) + { + oBottom.removeAttr('style'); + } + } + else if (Globals.$html.hasClass('rl-bottom-preview-pane')) + { + oTop = $('.b-message-list-wrapper'); + oBottom = $('.b-message-view-wrapper'); + + if (!oTop.hasClass('ui-resizable')) + { + oTop.resizable(oOptions); + } + + var iHeight = Utils.pInt(Local.get(sClientSideKeyName)) || 300; + fSetHeight(iHeight > iMinHeight ? iHeight : iMinHeight); + } + } + ; + + fDisable(false); + + Events.sub('layout', function (sLayout) { + fDisable(Enums.Layout.BottomPreview !== sLayout); + }); + }; + + AppUser.prototype.initVerticalLayoutResizer = function (sClientSideKeyName) + { + var + iDisabledWidth = 60, + iMinWidth = 155, + oLeft = $('#rl-left'), + oRight = $('#rl-right'), + + mLeftWidth = Local.get(sClientSideKeyName) || null, + + fSetWidth = function (iWidth) { + if (iWidth) + { + oLeft.css({ + 'width': '' + iWidth + 'px' + }); + + oRight.css({ + 'left': '' + iWidth + 'px' + }); + } + }, + + fDisable = function (bDisable) { + if (bDisable) + { + oLeft.resizable('disable'); + fSetWidth(iDisabledWidth); + } + else + { + oLeft.resizable('enable'); + var iWidth = Utils.pInt(Local.get(sClientSideKeyName)) || iMinWidth; + fSetWidth(iWidth > iMinWidth ? iWidth : iMinWidth); + } + }, + + fResizeFunction = function (oEvent, oObject) { + if (oObject && oObject.size && oObject.size.width) + { + Local.set(sClientSideKeyName, oObject.size.width); + + oRight.css({ + 'left': '' + oObject.size.width + 'px' + }); + } + } + ; + + if (null !== mLeftWidth) + { + fSetWidth(mLeftWidth > iMinWidth ? mLeftWidth : iMinWidth); + } + + oLeft.resizable({ + 'helper': 'ui-resizable-helper-w', + 'minWidth': iMinWidth, + 'maxWidth': 350, + 'handles': 'e', + 'stop': fResizeFunction + }); + + Events.sub('left-panel.off', function () { + fDisable(true); + }); + + Events.sub('left-panel.on', function () { + fDisable(false); + }); + }; + + AppUser.prototype.logout = function () + { + var self = this; + Remote.logout(function () { + self.loginAndLogoutReload(false, true, + Settings.settingsGet('ParentEmail') && 0 < Settings.settingsGet('ParentEmail').length); + }); + }; + + AppUser.prototype.bootstartTwoFactorScreen = function () + { + kn.showScreenPopup(require('View/Popup/TwoFactorConfiguration'), [true]); + }; + + AppUser.prototype.bootstartWelcomePopup = function (sUrl) + { + kn.showScreenPopup(require('View/Popup/WelcomePage'), [sUrl]); + }; + + AppUser.prototype.bootstartLoginScreen = function () + { + Globals.$html.removeClass('rl-user-auth').addClass('rl-user-no-auth'); + + var sCustomLoginLink = Utils.pString(Settings.settingsGet('CustomLoginLink')); + if (!sCustomLoginLink) + { + kn.startScreens([ + require('Screen/User/Login') + ]); + + Plugins.runHook('rl-start-login-screens'); + Events.pub('rl.bootstart-login-screens'); + } + else + { + kn.routeOff(); + kn.setHash(Links.root(), true); + kn.routeOff(); + + _.defer(function () { + window.location.href = sCustomLoginLink; + }); + } + }; + + AppUser.prototype.bootend = function () + { + kn.hideLoading(); + + if (SimplePace) + { + SimplePace.set(100); + } + }; + + AppUser.prototype.bootstart = function () + { + AbstractApp.prototype.bootstart.call(this); + + require('Stores/User/App').populate(); + require('Stores/User/Settings').populate(); + require('Stores/User/Notification').populate(); + require('Stores/User/Account').populate(); + require('Stores/User/Contact').populate(); + + var + self = this, + $LAB = require('$LAB'), + sJsHash = Settings.settingsGet('JsHash'), + iContactsSyncInterval = Utils.pInt(Settings.settingsGet('ContactsSyncInterval')), + bGoogle = Settings.settingsGet('AllowGoogleSocial'), + bFacebook = Settings.settingsGet('AllowFacebookSocial'), + bTwitter = Settings.settingsGet('AllowTwitterSocial') + ; + + if (SimplePace) + { + SimplePace.set(70); + SimplePace.sleep(); + } + + Globals.leftPanelDisabled.subscribe(function (bValue) { + Events.pub('left-panel.' + (bValue ? 'off' : 'on')); + }); + + this.setWindowTitle(''); + if (!!Settings.settingsGet('Auth')) + { + Globals.$html.addClass('rl-user-auth'); + + if (Settings.capa(Enums.Capa.TwoFactor) && + Settings.capa(Enums.Capa.TwoFactorForce) && + Settings.settingsGet('RequireTwoFactor')) + { + + this.bootend(); + this.bootstartTwoFactorScreen(); + } + else + { + this.setWindowTitle(Translator.i18n('TITLES/LOADING')); + +//require.ensure([], function() { // require code splitting + + self.foldersReload(_.bind(function (bValue) { + + this.bootend(); + + if (bValue) + { + if ($LAB && window.crypto && window.crypto.getRandomValues && Settings.capa(Enums.Capa.OpenPGP)) + { + var fOpenpgpCallback = function (openpgp) { + PgpStore.openpgp = openpgp; + PgpStore.openpgpKeyring = new openpgp.Keyring(); + PgpStore.capaOpenPGP(true); + + Events.pub('openpgp.init'); + + self.reloadOpenPgpKeys(); + }; + + if (window.openpgp) + { + fOpenpgpCallback(window.openpgp); + } + else + { + $LAB.script(Links.openPgpJs()).wait(function () { + if (window.openpgp) + { + fOpenpgpCallback(window.openpgp); + } + }); + } + } + else + { + PgpStore.capaOpenPGP(false); + } + + kn.startScreens([ + require('Screen/User/MailBox'), + require('Screen/User/Settings'), + require('Screen/User/About') + ]); + + if (bGoogle || bFacebook || bTwitter) + { + self.socialUsers(true); + } + + Events.sub('interval.2m', function () { + self.folderInformation(Cache.getFolderInboxName()); + }); + + Events.sub('interval.3m', function () { + var sF = FolderStore.currentFolderFullNameRaw(); + if (Cache.getFolderInboxName() !== sF) + { + self.folderInformation(sF); + } + }); + + Events.sub('interval.5m-after5m', function () { + self.folderInformationMultiply(); + }); + + Events.sub('interval.15m', function () { + self.quota(); + }); + + Events.sub('interval.20m', function () { + self.foldersReload(); + }); + + iContactsSyncInterval = 5 <= iContactsSyncInterval ? iContactsSyncInterval : 20; + iContactsSyncInterval = 320 >= iContactsSyncInterval ? iContactsSyncInterval : 320; + + _.delay(function () { + self.contactsSync(); + }, 10000); + + _.delay(function () { + self.folderInformationMultiply(true); + }, 2000); + + window.setInterval(function () { + self.contactsSync(); + }, iContactsSyncInterval * 60000 + 5000); + + self.accountsAndIdentities(true); + + _.delay(function () { + var sF = FolderStore.currentFolderFullNameRaw(); + if (Cache.getFolderInboxName() !== sF) + { + self.folderInformation(sF); + } + }, 1000); + + _.delay(function () { + self.quota(); + }, 5000); + + _.delay(function () { + Remote.appDelayStart(Utils.emptyFunction); + }, 35000); + + Events.sub('rl.auto-logout', function () { + self.logout(); + }); + + Plugins.runHook('rl-start-user-screens'); + Events.pub('rl.bootstart-user-screens'); + + if (Settings.settingsGet('WelcomePageUrl')) + { + _.delay(function () { + self.bootstartWelcomePopup(Settings.settingsGet('WelcomePageUrl')); + }, 1000); + } + + if (!!Settings.settingsGet('AccountSignMe') && window.navigator.registerProtocolHandler) + { + _.delay(function () { + try { + window.navigator.registerProtocolHandler('mailto', + window.location.protocol + '//' + window.location.host + window.location.pathname + '?mailto&to=%s', + '' + (Settings.settingsGet('Title') || 'RainLoop')); + } catch(e) {} + + if (Settings.settingsGet('MailToEmail')) + { + Utils.mailToHelper(Settings.settingsGet('MailToEmail'), require('View/Popup/Compose')); + } + }, 500); + } + + if (!Globals.bMobileDevice) + { + _.defer(function () { + self.initVerticalLayoutResizer(Enums.ClientSideKeyName.FolderListSize); + }); + + if (Tinycon) + { + Tinycon.setOptions({ + fallback: false + }); + + Events.sub('mailbox.inbox-unread-count', function (iCount) { + Tinycon.setBubble(0 < iCount ? (99 < iCount ? 99 : iCount) : 0); + }); + } + } + } + else + { + this.bootstartLoginScreen(); + } + + }, self)); + +//}); // require code splitting + + } + } + else + { + this.bootend(); + this.bootstartLoginScreen(); + } + + if (bGoogle) + { + window['rl_' + sJsHash + '_google_service'] = function () { + SocialStore.google.loading(true); + self.socialUsers(); + }; + } + + if (bFacebook) + { + window['rl_' + sJsHash + '_facebook_service'] = function () { + SocialStore.facebook.loading(true); + self.socialUsers(); + }; + } + + if (bTwitter) + { + window['rl_' + sJsHash + '_twitter_service'] = function () { + SocialStore.twitter.loading(true); + self.socialUsers(); + }; + } + + Events.sub('interval.1m', function () { + Momentor.reload(); + }); + + Plugins.runHook('rl-start-screens'); + Events.pub('rl.bootstart-end'); + }; + + module.exports = new AppUser(); + +}()); \ No newline at end of file diff --git a/dev/Common/Audio.js b/dev/Common/Audio.js new file mode 100644 index 000000000..8e437fc88 --- /dev/null +++ b/dev/Common/Audio.js @@ -0,0 +1,186 @@ + +(function () { + + 'use strict'; + + var + window = require('window'), + $ = require('$'), + + Globals = require('Common/Globals'), + Utils = require('Common/Utils'), + Links = require('Common/Links'), + Events = require('Common/Events') + ; + + /** + * @constructor + */ + function Audio() + { + var self = this; + +// this.userMedia = window.navigator.getUserMedia || window.navigator.webkitGetUserMedia || +// window.navigator.mozGetUserMedia || window.navigator.msGetUserMedia; +// +// this.audioContext = window.AudioContext || window.webkitAudioContext; +// if (!this.audioContext || !window.Float32Array) +// { +// this.audioContext = null; +// this.userMedia = null; +// } + + this.player = this.createNewObject(); + + this.supported = !Globals.bMobileDevice && !Globals.bSafari && !!this.player && !!this.player.play; + if (this.supported && this.player.canPlayType) + { + this.supportedMp3 = '' !== this.player.canPlayType('audio/mpeg;').replace(/no/, ''); + this.supportedWav = '' !== this.player.canPlayType('audio/wav; codecs="1"').replace(/no/, ''); + this.supportedOgg = '' !== this.player.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''); + this.supportedNotification = this.supported && this.supportedMp3; + } + + if (!this.player || (!this.supportedMp3 && !this.supportedOgg && !this.supportedWav)) + { + this.supported = false; + this.supportedMp3 = false; + this.supportedOgg = false; + this.supportedWav = false; + this.supportedNotification = false; + } + + if (this.supported) + { + $(this.player).on('ended error', function () { + self.stop(); + }); + + Events.sub('audio.api.stop', function () { + self.stop(); + }); + } + } + + Audio.prototype.player = null; + Audio.prototype.notificator = null; + + Audio.prototype.supported = false; + Audio.prototype.supportedMp3 = false; + Audio.prototype.supportedOgg = false; + Audio.prototype.supportedWav = false; + Audio.prototype.supportedNotification = false; + +// Audio.prototype.record = function () +// { +// this.getUserMedia({audio:true}, function () { +// window.console.log(arguments); +// }, function(oError) { +// window.console.log(arguments); +// }); +// }; + + Audio.prototype.createNewObject = function () + { + var player = window.Audio ? new window.Audio() : null; + if (player && player.canPlayType && player.pause && player.play) + { + player.preload = 'none'; + player.loop = false; + player.autoplay = false; + player.muted = false; + } + + return player; + }; + + Audio.prototype.paused = function () + { + return this.supported ? !!this.player.paused : true; + }; + + Audio.prototype.stop = function () + { + if (this.supported && this.player.pause) + { + this.player.pause(); + } + + Events.pub('audio.stop'); + }; + + Audio.prototype.pause = Audio.prototype.stop; + + Audio.prototype.clearName = function (sName, sExt) + { + sExt = sExt || ''; + sName = Utils.isUnd(sName) ? '' : Utils.trim(sName); + if (sExt && '.' + sExt === sName.toLowerCase().substr((sExt.length + 1) * -1)) + { + sName = Utils.trim(sName.substr(0, sName.length - 4)); + } + + if ('' === sName) + { + sName = 'audio'; + } + + return sName; + }; + + Audio.prototype.playMp3 = function (sUrl, sName) + { + if (this.supported && this.supportedMp3) + { + this.player.src = sUrl; + this.player.play(); + + Events.pub('audio.start', [this.clearName(sName, 'mp3'), 'mp3']); + } + }; + + Audio.prototype.playOgg = function (sUrl, sName) + { + if (this.supported && this.supportedOgg) + { + this.player.src = sUrl; + this.player.play(); + + sName = this.clearName(sName, 'oga'); + sName = this.clearName(sName, 'ogg'); + + Events.pub('audio.start', [sName, 'ogg']); + } + }; + + Audio.prototype.playWav = function (sUrl, sName) + { + if (this.supported && this.supportedWav) + { + this.player.src = sUrl; + this.player.play(); + + Events.pub('audio.start', [this.clearName(sName, 'wav'), 'wav']); + } + }; + + Audio.prototype.playNotification = function () + { + if (this.supported && this.supportedMp3) + { + if (!this.notificator) + { + this.notificator = this.createNewObject(); + this.notificator.src = Links.sound('new-mail.mp3'); + } + + if (this.notificator && this.notificator.play) + { + this.notificator.play(); + } + } + }; + + module.exports = new Audio(); + +}()); diff --git a/dev/Common/Base64.js b/dev/Common/Base64.js index 2f0d355a1..322090ae1 100644 --- a/dev/Common/Base64.js +++ b/dev/Common/Base64.js @@ -1,3 +1,4 @@ + // Base64 encode / decode // http://www.webtoolkit.info/ diff --git a/dev/Storage/App/Cache.js b/dev/Common/Cache.js similarity index 55% rename from dev/Storage/App/Cache.js rename to dev/Common/Cache.js index 7c4671c1e..bacc24b80 100644 --- a/dev/Storage/App/Cache.js +++ b/dev/Common/Cache.js @@ -1,4 +1,3 @@ -/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */ (function () { @@ -9,7 +8,7 @@ Enums = require('Common/Enums'), Utils = require('Common/Utils'), - LinkBuilder = require('Common/LinkBuilder'), + Links = require('Common/Links'), Settings = require('Storage/Settings') ; @@ -17,7 +16,7 @@ /** * @constructor */ - function CacheAppStorage() + function CacheUserStorage() { this.oFoldersCache = {}; this.oFoldersNamesCache = {}; @@ -34,54 +33,49 @@ /** * @type {boolean} */ - CacheAppStorage.prototype.bCapaGravatar = false; + CacheUserStorage.prototype.bCapaGravatar = false; /** * @type {Object} */ - CacheAppStorage.prototype.oFoldersCache = {}; + CacheUserStorage.prototype.oFoldersCache = {}; /** * @type {Object} */ - CacheAppStorage.prototype.oFoldersNamesCache = {}; + CacheUserStorage.prototype.oFoldersNamesCache = {}; /** * @type {Object} */ - CacheAppStorage.prototype.oFolderHashCache = {}; + CacheUserStorage.prototype.oFolderHashCache = {}; /** * @type {Object} */ - CacheAppStorage.prototype.oFolderUidNextCache = {}; + CacheUserStorage.prototype.oFolderUidNextCache = {}; /** * @type {Object} */ - CacheAppStorage.prototype.oMessageListHashCache = {}; + CacheUserStorage.prototype.oMessageListHashCache = {}; /** * @type {Object} */ - CacheAppStorage.prototype.oMessageFlagsCache = {}; + CacheUserStorage.prototype.oMessageFlagsCache = {}; /** * @type {Object} */ - CacheAppStorage.prototype.oBodies = {}; + CacheUserStorage.prototype.oNewMessage = {}; /** * @type {Object} */ - CacheAppStorage.prototype.oNewMessage = {}; + CacheUserStorage.prototype.oRequestedMessage = {}; - /** - * @type {Object} - */ - CacheAppStorage.prototype.oRequestedMessage = {}; - - CacheAppStorage.prototype.clear = function () + CacheUserStorage.prototype.clear = function () { this.oFoldersCache = {}; this.oFoldersNamesCache = {}; @@ -89,19 +83,17 @@ this.oFolderUidNextCache = {}; this.oMessageListHashCache = {}; this.oMessageFlagsCache = {}; - this.oBodies = {}; }; - /** * @param {string} sEmail * @param {Function} fCallback * @return {string} */ - CacheAppStorage.prototype.getUserPic = function (sEmail, fCallback) + CacheUserStorage.prototype.getUserPic = function (sEmail, fCallback) { sEmail = Utils.trim(sEmail); - fCallback(this.bCapaGravatar && '' !== sEmail ? LinkBuilder.avatarLink(sEmail) : '', sEmail); + fCallback(this.bCapaGravatar && '' !== sEmail ? Links.avatarLink(sEmail) : '', sEmail); }; /** @@ -109,7 +101,7 @@ * @param {string} sUid * @return {string} */ - CacheAppStorage.prototype.getMessageKey = function (sFolderFullNameRaw, sUid) + CacheUserStorage.prototype.getMessageKey = function (sFolderFullNameRaw, sUid) { return sFolderFullNameRaw + '#' + sUid; }; @@ -118,7 +110,7 @@ * @param {string} sFolder * @param {string} sUid */ - CacheAppStorage.prototype.addRequestedMessage = function (sFolder, sUid) + CacheUserStorage.prototype.addRequestedMessage = function (sFolder, sUid) { this.oRequestedMessage[this.getMessageKey(sFolder, sUid)] = true; }; @@ -128,7 +120,7 @@ * @param {string} sUid * @return {boolean} */ - CacheAppStorage.prototype.hasRequestedMessage = function (sFolder, sUid) + CacheUserStorage.prototype.hasRequestedMessage = function (sFolder, sUid) { return true === this.oRequestedMessage[this.getMessageKey(sFolder, sUid)]; }; @@ -137,7 +129,7 @@ * @param {string} sFolderFullNameRaw * @param {string} sUid */ - CacheAppStorage.prototype.addNewMessageCache = function (sFolderFullNameRaw, sUid) + CacheUserStorage.prototype.addNewMessageCache = function (sFolderFullNameRaw, sUid) { this.oNewMessage[this.getMessageKey(sFolderFullNameRaw, sUid)] = true; }; @@ -146,7 +138,7 @@ * @param {string} sFolderFullNameRaw * @param {string} sUid */ - CacheAppStorage.prototype.hasNewMessageAndRemoveFromCache = function (sFolderFullNameRaw, sUid) + CacheUserStorage.prototype.hasNewMessageAndRemoveFromCache = function (sFolderFullNameRaw, sUid) { if (this.oNewMessage[this.getMessageKey(sFolderFullNameRaw, sUid)]) { @@ -157,16 +149,29 @@ return false; }; - CacheAppStorage.prototype.clearNewMessageCache = function () + CacheUserStorage.prototype.clearNewMessageCache = function () { this.oNewMessage = {}; }; + /** + * @type {string} + */ + CacheUserStorage.prototype.sInboxFolderName = ''; + + /** + * @return {string} + */ + CacheUserStorage.prototype.getFolderInboxName = function () + { + return '' === this.sInboxFolderName ? 'INBOX' : this.sInboxFolderName; + }; + /** * @param {string} sFolderHash * @return {string} */ - CacheAppStorage.prototype.getFolderFullNameRaw = function (sFolderHash) + CacheUserStorage.prototype.getFolderFullNameRaw = function (sFolderHash) { return '' !== sFolderHash && this.oFoldersNamesCache[sFolderHash] ? this.oFoldersNamesCache[sFolderHash] : ''; }; @@ -175,16 +180,20 @@ * @param {string} sFolderHash * @param {string} sFolderFullNameRaw */ - CacheAppStorage.prototype.setFolderFullNameRaw = function (sFolderHash, sFolderFullNameRaw) + CacheUserStorage.prototype.setFolderFullNameRaw = function (sFolderHash, sFolderFullNameRaw) { this.oFoldersNamesCache[sFolderHash] = sFolderFullNameRaw; + if ('INBOX' === sFolderFullNameRaw || '' === this.sInboxFolderName) + { + this.sInboxFolderName = sFolderFullNameRaw; + } }; /** * @param {string} sFolderFullNameRaw * @return {string} */ - CacheAppStorage.prototype.getFolderHash = function (sFolderFullNameRaw) + CacheUserStorage.prototype.getFolderHash = function (sFolderFullNameRaw) { return '' !== sFolderFullNameRaw && this.oFolderHashCache[sFolderFullNameRaw] ? this.oFolderHashCache[sFolderFullNameRaw] : ''; }; @@ -193,7 +202,7 @@ * @param {string} sFolderFullNameRaw * @param {string} sFolderHash */ - CacheAppStorage.prototype.setFolderHash = function (sFolderFullNameRaw, sFolderHash) + CacheUserStorage.prototype.setFolderHash = function (sFolderFullNameRaw, sFolderHash) { this.oFolderHashCache[sFolderFullNameRaw] = sFolderHash; }; @@ -202,7 +211,7 @@ * @param {string} sFolderFullNameRaw * @return {string} */ - CacheAppStorage.prototype.getFolderUidNext = function (sFolderFullNameRaw) + CacheUserStorage.prototype.getFolderUidNext = function (sFolderFullNameRaw) { return '' !== sFolderFullNameRaw && this.oFolderUidNextCache[sFolderFullNameRaw] ? this.oFolderUidNextCache[sFolderFullNameRaw] : ''; }; @@ -211,7 +220,7 @@ * @param {string} sFolderFullNameRaw * @param {string} sUidNext */ - CacheAppStorage.prototype.setFolderUidNext = function (sFolderFullNameRaw, sUidNext) + CacheUserStorage.prototype.setFolderUidNext = function (sFolderFullNameRaw, sUidNext) { this.oFolderUidNextCache[sFolderFullNameRaw] = sUidNext; }; @@ -220,7 +229,7 @@ * @param {string} sFolderFullNameRaw * @return {?FolderModel} */ - CacheAppStorage.prototype.getFolderFromCacheList = function (sFolderFullNameRaw) + CacheUserStorage.prototype.getFolderFromCacheList = function (sFolderFullNameRaw) { return '' !== sFolderFullNameRaw && this.oFoldersCache[sFolderFullNameRaw] ? this.oFoldersCache[sFolderFullNameRaw] : null; }; @@ -229,7 +238,7 @@ * @param {string} sFolderFullNameRaw * @param {?FolderModel} oFolder */ - CacheAppStorage.prototype.setFolderToCacheList = function (sFolderFullNameRaw, oFolder) + CacheUserStorage.prototype.setFolderToCacheList = function (sFolderFullNameRaw, oFolder) { this.oFoldersCache[sFolderFullNameRaw] = oFolder; }; @@ -237,7 +246,7 @@ /** * @param {string} sFolderFullNameRaw */ - CacheAppStorage.prototype.removeFolderFromCacheList = function (sFolderFullNameRaw) + CacheUserStorage.prototype.removeFolderFromCacheList = function (sFolderFullNameRaw) { this.setFolderToCacheList(sFolderFullNameRaw, null); }; @@ -247,7 +256,7 @@ * @param {string} sUid * @return {?Array} */ - CacheAppStorage.prototype.getMessageFlagsFromCache = function (sFolderFullName, sUid) + CacheUserStorage.prototype.getMessageFlagsFromCache = function (sFolderFullName, sUid) { return this.oMessageFlagsCache[sFolderFullName] && this.oMessageFlagsCache[sFolderFullName][sUid] ? this.oMessageFlagsCache[sFolderFullName][sUid] : null; @@ -258,7 +267,7 @@ * @param {string} sUid * @param {Array} aFlagsCache */ - CacheAppStorage.prototype.setMessageFlagsToCache = function (sFolderFullName, sUid, aFlagsCache) + CacheUserStorage.prototype.setMessageFlagsToCache = function (sFolderFullName, sUid, aFlagsCache) { if (!this.oMessageFlagsCache[sFolderFullName]) { @@ -271,7 +280,7 @@ /** * @param {string} sFolderFullName */ - CacheAppStorage.prototype.clearMessageFlagsFromCacheByFolder = function (sFolderFullName) + CacheUserStorage.prototype.clearMessageFlagsFromCacheByFolder = function (sFolderFullName) { this.oMessageFlagsCache[sFolderFullName] = {}; }; @@ -279,35 +288,47 @@ /** * @param {(MessageModel|null)} oMessage */ - CacheAppStorage.prototype.initMessageFlagsFromCache = function (oMessage) + CacheUserStorage.prototype.initMessageFlagsFromCache = function (oMessage) { if (oMessage) { var self = this, - aFlags = this.getMessageFlagsFromCache(oMessage.folderFullNameRaw, oMessage.uid), + sUid = oMessage.uid, + aFlags = this.getMessageFlagsFromCache(oMessage.folderFullNameRaw, sUid), mUnseenSubUid = null, mFlaggedSubUid = null ; if (aFlags && 0 < aFlags.length) { - oMessage.unseen(!!aFlags[0]); oMessage.flagged(!!aFlags[1]); - oMessage.answered(!!aFlags[2]); - oMessage.forwarded(!!aFlags[3]); - oMessage.isReadReceipt(!!aFlags[4]); + + if (!oMessage.__simple_message__) + { + oMessage.unseen(!!aFlags[0]); + oMessage.answered(!!aFlags[2]); + oMessage.forwarded(!!aFlags[3]); + oMessage.isReadReceipt(!!aFlags[4]); + oMessage.deletedMark(!!aFlags[5]); + } } if (0 < oMessage.threads().length) { - mUnseenSubUid = _.find(oMessage.threads(), function (iSubUid) { - var aFlags = self.getMessageFlagsFromCache(oMessage.folderFullNameRaw, iSubUid); + mUnseenSubUid = _.find(oMessage.threads(), function (sSubUid) { + if (sUid === sSubUid){ + return false; + } + var aFlags = self.getMessageFlagsFromCache(oMessage.folderFullNameRaw, sSubUid); return aFlags && 0 < aFlags.length && !!aFlags[0]; }); - mFlaggedSubUid = _.find(oMessage.threads(), function (iSubUid) { - var aFlags = self.getMessageFlagsFromCache(oMessage.folderFullNameRaw, iSubUid); + mFlaggedSubUid = _.find(oMessage.threads(), function (sSubUid) { + if (sUid === sSubUid){ + return false; + } + var aFlags = self.getMessageFlagsFromCache(oMessage.folderFullNameRaw, sSubUid); return aFlags && 0 < aFlags.length && !!aFlags[1]; }); @@ -320,23 +341,25 @@ /** * @param {(MessageModel|null)} oMessage */ - CacheAppStorage.prototype.storeMessageFlagsToCache = function (oMessage) + CacheUserStorage.prototype.storeMessageFlagsToCache = function (oMessage) { if (oMessage) { this.setMessageFlagsToCache( oMessage.folderFullNameRaw, oMessage.uid, - [oMessage.unseen(), oMessage.flagged(), oMessage.answered(), oMessage.forwarded(), oMessage.isReadReceipt()] + [oMessage.unseen(), oMessage.flagged(), oMessage.answered(), oMessage.forwarded(), + oMessage.isReadReceipt(), oMessage.deletedMark()] ); } }; + /** * @param {string} sFolder * @param {string} sUid * @param {Array} aFlags */ - CacheAppStorage.prototype.storeMessageFlagsToCacheByFolderAndUid = function (sFolder, sUid, aFlags) + CacheUserStorage.prototype.storeMessageFlagsToCacheByFolderAndUid = function (sFolder, sUid, aFlags) { if (Utils.isArray(aFlags) && 0 < aFlags.length) { @@ -344,6 +367,43 @@ } }; - module.exports = new CacheAppStorage(); + /** + * @param {string} sFolder + * @param {string} sUid + * @param {number} iSetAction + */ + CacheUserStorage.prototype.storeMessageFlagsToCacheBySetAction = function (sFolder, sUid, iSetAction) + { + var iUnread = 0, aFlags = this.getMessageFlagsFromCache(sFolder, sUid); + if (Utils.isArray(aFlags) && 0 < aFlags.length) + { + if (aFlags[0]) + { + iUnread = 1; + } + + switch (iSetAction) + { + case Enums.MessageSetAction.SetSeen: + aFlags[0] = false; + break; + case Enums.MessageSetAction.UnsetSeen: + aFlags[0] = true; + break; + case Enums.MessageSetAction.SetFlag: + aFlags[1] = true; + break; + case Enums.MessageSetAction.UnsetFlag: + aFlags[1] = false; + break; + } + + this.setMessageFlagsToCache(sFolder, sUid, aFlags); + } + + return iUnread; + }; + + module.exports = new CacheUserStorage(); }()); \ No newline at end of file diff --git a/dev/Storage/LocalDriver/Cookie.js b/dev/Common/ClientStorageDriver/Cookie.js similarity index 81% rename from dev/Storage/LocalDriver/Cookie.js rename to dev/Common/ClientStorageDriver/Cookie.js index 2f6a4f432..27c25861c 100644 --- a/dev/Storage/LocalDriver/Cookie.js +++ b/dev/Common/ClientStorageDriver/Cookie.js @@ -1,4 +1,3 @@ -/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */ (function () { @@ -15,7 +14,7 @@ /** * @constructor */ - function CookieLocalDriver() + function CookieDriver() { } @@ -23,7 +22,7 @@ * @static * @return {boolean} */ - CookieLocalDriver.supported = function () + CookieDriver.supported = function () { return !!(window.navigator && window.navigator.cookieEnabled); }; @@ -33,7 +32,7 @@ * @param {*} mData * @return {boolean} */ - CookieLocalDriver.prototype.set = function (sKey, mData) + CookieDriver.prototype.set = function (sKey, mData) { var mStorageValue = $.cookie(Consts.Values.ClientSideStorageIndexName), @@ -71,7 +70,7 @@ * @param {string} sKey * @return {*} */ - CookieLocalDriver.prototype.get = function (sKey) + CookieDriver.prototype.get = function (sKey) { var mStorageValue = $.cookie(Consts.Values.ClientSideStorageIndexName), @@ -95,6 +94,6 @@ return mResult; }; - module.exports = CookieLocalDriver; + module.exports = CookieDriver; }()); \ No newline at end of file diff --git a/dev/Storage/LocalDriver/LocalStorage.js b/dev/Common/ClientStorageDriver/LocalStorage.js similarity index 80% rename from dev/Storage/LocalDriver/LocalStorage.js rename to dev/Common/ClientStorageDriver/LocalStorage.js index e3acbbbf9..2ad03cea8 100644 --- a/dev/Storage/LocalDriver/LocalStorage.js +++ b/dev/Common/ClientStorageDriver/LocalStorage.js @@ -1,4 +1,3 @@ -/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */ (function () { @@ -15,7 +14,7 @@ /** * @constructor */ - function LocalStorageLocalDriver() + function LocalStorageDriver() { } @@ -23,7 +22,7 @@ * @static * @return {boolean} */ - LocalStorageLocalDriver.supported = function () + LocalStorageDriver.supported = function () { return !!window.localStorage; }; @@ -33,7 +32,7 @@ * @param {*} mData * @return {boolean} */ - LocalStorageLocalDriver.prototype.set = function (sKey, mData) + LocalStorageDriver.prototype.set = function (sKey, mData) { var mStorageValue = window.localStorage[Consts.Values.ClientSideStorageIndexName] || null, @@ -69,7 +68,7 @@ * @param {string} sKey * @return {*} */ - LocalStorageLocalDriver.prototype.get = function (sKey) + LocalStorageDriver.prototype.get = function (sKey) { var mStorageValue = window.localStorage[Consts.Values.ClientSideStorageIndexName] || null, @@ -93,6 +92,6 @@ return mResult; }; - module.exports = LocalStorageLocalDriver; + module.exports = LocalStorageDriver; }()); \ No newline at end of file diff --git a/dev/Common/Consts.js b/dev/Common/Consts.js index f5536279e..dab47f3cf 100644 --- a/dev/Common/Consts.js +++ b/dev/Common/Consts.js @@ -81,6 +81,12 @@ */ Consts.Values.ImapDefaulSecurePort = 993; + /** + * @const + * @type {number} + */ + Consts.Values.SieveDefaulPort = 4190; + /** * @const * @type {number} @@ -115,7 +121,14 @@ * @const * @type {string} */ - Consts.DataImages.UserDotPic = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2P8DwQACgAD/il4QJ8AAAAASUVORK5CYII='; + Consts.Values.RainLoopTrialKey = 'RAINLOOP-TRIAL-KEY'; + + /** + * @const + * @type {string} + */ +// Consts.DataImages.UserDotPic = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2P8DwQACgAD/il4QJ8AAAAASUVORK5CYII='; + Consts.DataImages.UserDotPic = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAYAAABXuSs3AAAHHklEQVRoQ7VZW08bVxCeXRuwIbTGXIwNtBBaqjwgVUiR8lDlbza9qe1DpVZ9aNQ/0KpPeaJK07SpcuEeCEmUAObm21bfrL9lONjexSYrWfbunj37zXdmvpkz9oIgCKTD0Wg0xPd94TDP83Q0zvWa50vzklSrdanVanqf4/D84GBGr+F+Op3S8fqoJxLOdnZgTvsO/nYhenHA+UC7CWF1uXwkb9++ldPTUwVerVbVqFQqpR8YPjQ0JCMjI5LNDijoRgP3PQVu5+5Eor2XGLg7IV4GkIdHJ/LmzRs5ODiIwNbrdR0O0GCcq4Xz4eFhmZyclP7+tDQaIik/BG5XKQn4SwG3zJTLZXn9+rUclI8UHD5YVoDDN8bSzXhONwL48fFxGR4eilzFZT1uFRIB5yT8BqCdnR3Z3d0VP9Un6XRawYJpggVrZBv38ME4XKtUKnLt2jUplUoy1PR/l3U7T6sVSAQcgMAkj8PDQ9ne3pajoyMRL7zeKsYZWHgWYDGmv78/mmdwcFA+mJlSgziHDWrERrsjEXDXegTi1tZW+DLxI2bxIrqFNYTXyDyCFweMAHCwb8e4RnTNuOsqe3t7sra21pTD0Kct666E8XlcZyzw9/RUUXK5nK5oUinUQI6TQ3cynO/v78vq6qrKXCNwlTiJJpyNGc3nZHp6uqV2dwrQWOCtZBDAV1ZWwsQk7f0wiQn5kffbAu/0/KWBYzIC1+XukfGx0RGZmppKlC2tIV0Bh4aDcZW7HhkfH8urLLZL7T2pihvlkMNnz56FiadHxicL41IsFpN41bkxsYxbRdFo9jwB8KdPn14J8KnSpBQKhQs63nPmbCVRcBUAR2Lq1VVmpksyMTFxAXjcEsQybiegESionjx5osCZOeNe1O4+EhCAX7bQSgQcxRHTMgAgcz5+/Dis/hL4uHU3/B4YGNASGHIKxuEql0k+l05AeIAF1vPnz5VxFFmdDlaJrMtZITJeSsXCOTlMunKxjLtMYOKNjQ158eJFuAuKkUOb5sEwgff19SkJUBVkThZUbnXZrtCKBQ6gbnWIkjZpyne3ejAWoGnA7Icz6irvBLgbOMicCM6TkxPx/LAkbXfgWcsazuE2kFRsKD5Z+CiqDumKncpZvieWcS6dDVD8xiYCNflpJdwcdwJOf9airLmVQ7DPzMxIYWLsXGXoVqLt5k0M3K3JUVPDZdbWNzsCp48TPFdvdnZWUz32nDha7bJ63kgAJPzSdRks9/Kf9xMJAQ1gq2NpaUmy2Yz4zar4nQC3xb99AQwCcGzLAAwuhG8YiWvcOKts+r4GOe5nMhm5efOm9lUA3E3vSZJRrKvE0fnPv//Jy5cvo5cTHIPQbSjhOoqq69evS19f6lxDKK4+sVhigZPtKJqbrQeqxd5+WR4+fKgqgT0k2XX3nhiPgETWXFhYkFzuPZ2yVq1GTSOXpE47/VjgNnD4m4GG7/LhsTx69EiwD4Vr2MwIIxgbAH18fKx1yfz8vEogNvGtWnCuhLZa9UTAreVWFsHy/b/+Vrbdl7E5REMQD2jDoUbByty+/ZnU64GkU2HzyJLhktU1cLv8nARgkYS2d3ajAgwG8qU2oLmDZ92CMaOjo7K4uCiZgbDWaRWgnZhPxLhrMUCvr69riwKZk1LHF7XqrWAO9hJxH6ozNzcnCx/PqztZg9mf6SQMscCtm2C5ke4BGMlHWTUp36036AJajDVrFMzBrhhWslQsSrFYiOqVpMriNYIgqFRq2j3FAb/zffT6zuxFXxsNzs3NTXn16lW4gYiW96w1FyedF+83xG/2FNGCRpU4NjamMsn+OZ9xE5RXqdaDdPpib6RWCzuwKF9RxqI2AVNQBwQYJoK0wdBejnqtEikP3pfP51XjUTESl12FqJEKxsEorARYDD44ONTeID7YpsEnrRvQfWAI2e8WfDaTUSIwJ0iBCmFOtOUAHvVMPp/TPwvYFVYFIuP8l+DBgwdaa2Miqwa0GgYwfeMltovbDfh6c1vIgMYcliSsKv4IWFr6VDHxvldvBAH+1sA+cnl5WYOPmmr9ir+1l9I0Cgz0yjhXjfJJ0JROnmezWbl165ayr/5fqwcBNr7IfhjMqKcvESSM4eRcCasQ3bDNObmKPLdGUGpZsN24cUNLBm9zazu4d++e6qpNBFaTuUS26U5dpuR1CxyA7J9ddrMRqlz4pwLLYawymPd++/2PADt2ugcGwq9gCCdhQ96C6xWwa6j1ceuq+I0EhW0i8MAIVJfeL3d/DVD8EKi12P6/2S2jV/EccVB54O/ejz/9HGCpoBBMta5rXMXLu53D1XAwjhXwvvv+h4BAXVe4bOu3O3ChxF08LiZFG3fel199G9CH3fLyqv24NcB44MRhpdK788U3CpyKwsCw590xmfSpzsBt0Fqc3ud3vtZigxWcVZCklVpSiN0w3q5E/h9TGMIUuA3+EQAAAABJRU5ErkJggg=='; /** * @const diff --git a/dev/Common/Enums.js b/dev/Common/Enums.js index ed0b73545..36c56ad5e 100644 --- a/dev/Common/Enums.js +++ b/dev/Common/Enums.js @@ -5,6 +5,26 @@ var Enums = {}; + /** + * @enum {string} + */ + Enums.FileType = { + 'Unknown': 'unknown', + 'Text': 'text', + 'Html': 'html', + 'Code': 'code', + 'Eml': 'eml', + 'WordText': 'word-text', + 'Pdf': 'pdf', + 'Image': 'image', + 'Audio': 'audio', + 'Video': 'video', + 'Sheet': 'sheet', + 'Presentation': 'presentation', + 'Certificate': 'certificate', + 'Archive': 'archive' + }; + /** * @enum {string} */ @@ -15,6 +35,16 @@ 'Unload': 'unload' }; + /** + * @enum {string} + */ + Enums.Focused = { + 'None': 'none', + 'MessageList': 'message-list', + 'MessageView': 'message-view', + 'FolderList': 'folder-list' + }; + /** * @enum {number} */ @@ -36,15 +66,19 @@ * @enum {string} */ Enums.Capa = { - 'Prem': 'PREM', 'TwoFactor': 'TWO_FACTOR', + 'TwoFactorForce': 'TWO_FACTOR_FORCE', 'OpenPGP': 'OPEN_PGP', 'Prefetch': 'PREFETCH', 'Gravatar': 'GRAVATAR', 'Themes': 'THEMES', + 'UserBackground': 'USER_BACKGROUND', + 'Sieve': 'SIEVE', 'Filters': 'FILTERS', - 'AdditionalAccounts': 'ADDITIONAL_ACCOUNTS', - 'AdditionalIdentities': 'ADDITIONAL_IDENTITIES' + 'AttachmentThumbnails': 'ATTACHMENT_THUMBNAILS', + 'Templates': 'TEMPLATES', + 'AutoLogout': 'AUTOLOGOUT', + 'AdditionalAccounts': 'ADDITIONAL_ACCOUNTS' }; /** @@ -144,7 +178,11 @@ 'MessagesInboxLastHash': 1, 'MailBoxListSize': 2, 'ExpandedFolders': 3, - 'FolderListSize': 4 + 'FolderListSize': 4, + 'MessageListSize': 5, + 'LastReplyAction': 6, + 'LastSignMe': 7, + 'ComposeLastIdentityID': 8 }; /** @@ -196,7 +234,7 @@ /** * @enum {number} */ - Enums.DesktopNotifications = { + Enums.DesktopNotification = { 'Allowed': 0, 'NotAllowed': 1, 'Denied': 2, @@ -217,15 +255,9 @@ */ Enums.EditorDefaultType = { 'Html': 'Html', - 'Plain': 'Plain' - }; - - /** - * @enum {string} - */ - Enums.CustomThemeType = { - 'Light': 'Light', - 'Dark': 'Dark' + 'Plain': 'Plain', + 'HtmlForced': 'HtmlForced', + 'PlainForced': 'PlainForced' }; /** @@ -257,22 +289,14 @@ 'FalseResult': 0 }; - /** - * @enum {string} - */ - Enums.InterfaceAnimation = { - 'None': 'None', - 'Normal': 'Normal', - 'Full': 'Full' - }; - /** * @enum {number} */ Enums.Layout = { 'NoPreview': 0, 'SidePreview': 1, - 'BottomPreview': 2 + 'BottomPreview': 2, + 'Mobile': 3 }; /** @@ -280,7 +304,6 @@ */ Enums.FilterConditionField = { 'From': 'From', - 'To': 'To', 'Recipient': 'Recipient', 'Subject': 'Subject' }; @@ -300,8 +323,10 @@ */ Enums.FiltersAction = { 'None': 'None', - 'Move': 'Move', + 'MoveTo': 'MoveTo', 'Discard': 'Discard', + 'Vacation': 'Vacation', + 'Reject': 'Reject', 'Forward': 'Forward' }; @@ -309,8 +334,8 @@ * @enum {string} */ Enums.FilterRulesType = { - 'And': 'And', - 'Or': 'Or' + 'All': 'All', + 'Any': 'Any' }; /** @@ -393,6 +418,10 @@ 'CantSendMessage': 302, 'InvalidRecipients': 303, + 'CantSaveFilters': 351, + 'CantGetFilters': 352, + 'FiltersAreNotCorrect': 355, + 'CantCreateFolder': 400, 'CantRenameFolder': 401, 'CantDeleteFolder': 402, @@ -415,12 +444,20 @@ 'LicensingBanned': 712, 'DemoSendMessageError': 750, + 'DemoAccountError': 751, 'AccountAlreadyExists': 801, + 'AccountDoesNotExist': 802, 'MailServerError': 901, 'ClientViewError': 902, 'InvalidInputArgument': 903, + + 'AjaxFalse': 950, + 'AjaxAbort': 951, + 'AjaxParse': 952, + 'AjaxTimeout': 953, + 'UnknownNotification': 999, 'UnknownError': 999 }; diff --git a/dev/Common/Events.js b/dev/Common/Events.js index b8198fb1f..14b71d3a0 100644 --- a/dev/Common/Events.js +++ b/dev/Common/Events.js @@ -28,12 +28,24 @@ */ Events.prototype.sub = function (sName, fFunc, oContext) { - if (Utils.isUnd(this.oSubs[sName])) + if (Utils.isObject(sName)) { - this.oSubs[sName] = []; - } + oContext = fFunc || null; + fFunc = null; - this.oSubs[sName].push([fFunc, oContext]); + _.each(sName, function (fSubFunc, sSubName) { + this.sub(sSubName, fSubFunc, oContext); + }, this); + } + else + { + if (Utils.isUnd(this.oSubs[sName])) + { + this.oSubs[sName] = []; + } + + this.oSubs[sName].push([fFunc, oContext]); + } return this; }; diff --git a/dev/Common/Globals.js b/dev/Common/Globals.js index 531322e82..3a4bfb5c1 100644 --- a/dev/Common/Globals.js +++ b/dev/Common/Globals.js @@ -18,33 +18,21 @@ Globals.$win = $(window); Globals.$doc = $(window.document); Globals.$html = $('html'); + Globals.$body = $('body'); Globals.$div = $('
'); - /** - * @type {?} - */ - Globals.now = (new window.Date()).getTime(); + Globals.$win.__sizes = [0, 0]; /** * @type {?} */ - Globals.momentTrigger = ko.observable(true); + Globals.startMicrotime = (new window.Date()).getTime(); /** * @type {?} */ Globals.dropdownVisibility = ko.observable(false).extend({'rateLimit': 0}); - /** - * @type {?} - */ - Globals.tooltipTrigger = ko.observable(false).extend({'rateLimit': 0}); - - /** - * @type {?} - */ - Globals.langChangeTrigger = ko.observable(true); - /** * @type {boolean} */ @@ -73,22 +61,34 @@ /** * @type {string} */ - Globals.sUserAgent = (window.navigator.userAgent || '').toLowerCase(); + Globals.sUserAgent = 'navigator' in window && 'userAgent' in window.navigator && + window.navigator.userAgent.toLowerCase() || ''; /** * @type {boolean} */ - Globals.bIsiOSDevice = -1 < Globals.sUserAgent.indexOf('iphone') || -1 < Globals.sUserAgent.indexOf('ipod') || -1 < Globals.sUserAgent.indexOf('ipad'); + Globals.bIE = Globals.sUserAgent.indexOf('msie') > -1; /** * @type {boolean} */ - Globals.bIsAndroidDevice = -1 < Globals.sUserAgent.indexOf('android'); + Globals.bChrome = Globals.sUserAgent.indexOf('chrome') > -1; /** * @type {boolean} */ - Globals.bMobileDevice = Globals.bIsiOSDevice || Globals.bIsAndroidDevice; + Globals.bSafari = !Globals.bChrome && Globals.sUserAgent.indexOf('safari') > -1; + + /** + * @type {boolean} + */ + Globals.bMobileDevice = + /android/i.test(Globals.sUserAgent) || + /iphone/i.test(Globals.sUserAgent) || + /ipod/i.test(Globals.sUserAgent) || + /ipad/i.test(Globals.sUserAgent) || + /blackberry/i.test(Globals.sUserAgent) + ; /** * @type {boolean} @@ -103,18 +103,14 @@ /** * @type {boolean} */ - Globals.bAnimationSupported = !Globals.bMobileDevice && Globals.$html.hasClass('csstransitions'); + Globals.bAnimationSupported = !Globals.bMobileDevice && Globals.$html.hasClass('csstransitions') && + Globals.$html.hasClass('cssanimations'); /** * @type {boolean} */ Globals.bXMLHttpRequestSupported = !!window.XMLHttpRequest; - /** - * @type {string} - */ - Globals.sAnimationType = ''; - /** * @type {*} */ @@ -131,23 +127,26 @@ 'toolbarGroups': [ {name: 'spec'}, {name: 'styles'}, - {name: 'basicstyles', groups: ['basicstyles', 'cleanup']}, + {name: 'basicstyles', groups: ['basicstyles', 'cleanup', 'bidi']}, {name: 'colors'}, {name: 'paragraph', groups: ['list', 'indent', 'blocks', 'align']}, {name: 'links'}, {name: 'insert'}, + {name: 'document', groups: ['mode', 'document', 'doctools']}, {name: 'others'} - // {name: 'document', groups: ['mode', 'document', 'doctools']} ], - 'removePlugins': 'liststyle,tabletools,contextmenu', //blockquote - 'removeButtons': 'Format,Undo,Redo,Cut,Copy,Paste,Anchor,Strike,Subscript,Superscript,Image,SelectAll', + 'removePlugins': 'liststyle', + 'removeButtons': 'Format,Undo,Redo,Cut,Copy,Paste,Anchor,Strike,Subscript,Superscript,Image,SelectAll,Source', 'removeDialogTabs': 'link:advanced;link:target;image:advanced;images:advanced', - 'extraPlugins': 'plain', + 'extraPlugins': 'plain,signature', 'allowedContent': true, - 'autoParagraph': false, + 'extraAllowedContent': true, + + 'fillEmptyBlocks': false, + 'ignoreEmptyParagraph': true, 'font_defaultLabel': 'Arial', 'fontSize_defaultLabel': '13', @@ -158,6 +157,7 @@ * @type {Object} */ Globals.oHtmlEditorLangsMap = { + 'bg': 'bg', 'de': 'de', 'es': 'es', 'fr': 'fr', @@ -168,6 +168,7 @@ 'ja-jp': 'ja', 'ko': 'ko', 'ko-kr': 'ko', + 'lt': 'lt', 'lv': 'lv', 'nl': 'nl', 'no': 'no', @@ -178,9 +179,11 @@ 'ro': 'ro', 'ru': 'ru', 'sk': 'sk', + 'sv': 'sv', 'tr': 'tr', 'ua': 'ru', 'zh': 'zh', + 'zh-tw': 'zh', 'zh-cn': 'zh-cn' }; @@ -191,10 +194,6 @@ }); } - Globals.oI18N = window['rainloopI18N'] || {}; - - Globals.oNotificationI18N = {}; - Globals.aBootstrapDropdowns = []; Globals.aViewModels = { @@ -212,6 +211,10 @@ return 0 < Globals.popupVisibilityNames().length; }, this); + Globals.popupVisibility.subscribe(function (bValue) { + Globals.$html.toggleClass('rl-modal', bValue); + }); + // keys Globals.keyScopeReal = ko.observable(Enums.KeyState.All); Globals.keyScopeFake = ko.observable(Enums.KeyState.All); @@ -266,14 +269,13 @@ }); Globals.keyScopeReal.subscribe(function (sValue) { -// window.console.log(sValue); +// window.console.log('keyScope=' + sValue); // DEBUG key.setScope(sValue); }); Globals.dropdownVisibility.subscribe(function (bValue) { if (bValue) { - Globals.tooltipTrigger(!Globals.tooltipTrigger()); Globals.keyScope(Enums.KeyState.Menu); } else if (Enums.KeyState.Menu === key.getScope()) diff --git a/dev/Common/HtmlEditor.js b/dev/Common/HtmlEditor.js index 15523eb77..8e9f25c69 100644 --- a/dev/Common/HtmlEditor.js +++ b/dev/Common/HtmlEditor.js @@ -8,6 +8,7 @@ _ = require('_'), Globals = require('Common/Globals'), + Settings = require('Storage/Settings') ; @@ -30,6 +31,9 @@ this.resize = _.throttle(_.bind(this.resize, this), 100); + this.__inited = false; + this.__initedData = null; + this.init(); } @@ -61,6 +65,23 @@ return this.editor ? 'wysiwyg' === this.editor.mode : false; }; + /** + * @param {string} sSignature + * @param {bool} bHtml + * @param {bool} bInsertBefore + */ + HtmlEditor.prototype.setSignature = function (sSignature, bHtml, bInsertBefore) + { + if (this.editor) + { + this.editor.execCommand('insertSignature', { + 'isHtml': bHtml, + 'insertBefore': bInsertBefore, + 'signature': sSignature + }); + } + }; + /** * @return {boolean} */ @@ -78,66 +99,122 @@ }; /** - * @param {boolean=} bWrapIsHtml = false + * @param {string} sText * @return {string} */ - HtmlEditor.prototype.getData = function (bWrapIsHtml) + HtmlEditor.prototype.clearSignatureSigns = function (sText) { - if (this.editor) - { - if ('plain' === this.editor.mode && this.editor.plugins.plain && this.editor.__plain) - { - return this.editor.__plain.getRawData(); - } - - return bWrapIsHtml ? - '
' + - this.editor.getData() + '
' : this.editor.getData(); - } - - return ''; + return sText.replace(/(\u0002|\u0003|\u200C|\u200D)/g, ''); }; - HtmlEditor.prototype.modeToggle = function (bPlain) + /** + * @param {boolean=} bWrapIsHtml = false + * @param {boolean=} bClearSignatureSigns = false + * @return {string} + */ + HtmlEditor.prototype.getData = function (bWrapIsHtml, bClearSignatureSigns) + { + var sResult = ''; + if (this.editor) + { + try + { + if ('plain' === this.editor.mode && this.editor.plugins.plain && this.editor.__plain) + { + sResult = this.editor.__plain.getRawData(); + } + else + { + sResult = bWrapIsHtml ? + '
' + + this.editor.getData() + '
' : this.editor.getData(); + } + } + catch (e) {} + + if (bClearSignatureSigns) + { + sResult = this.clearSignatureSigns(sResult); + } + } + + return sResult; + }; + + /** + * @param {boolean=} bWrapIsHtml = false + * @param {boolean=} bClearSignatureSigns = false + * @return {string} + */ + HtmlEditor.prototype.getDataWithHtmlMark = function (bWrapIsHtml, bClearSignatureSigns) + { + return (this.isHtml() ? ':HTML:' : '') + this.getData(bWrapIsHtml, bClearSignatureSigns); + }; + + HtmlEditor.prototype.modeToggle = function (bPlain, bResize) { if (this.editor) { - if (bPlain) - { - if ('plain' === this.editor.mode) + try { + if (bPlain) { - this.editor.setMode('wysiwyg'); + if ('plain' === this.editor.mode) + { + this.editor.setMode('wysiwyg'); + } } - } - else - { - if ('wysiwyg' === this.editor.mode) + else { - this.editor.setMode('plain'); + if ('wysiwyg' === this.editor.mode) + { + this.editor.setMode('plain'); + } } - } + } catch(e) {} - this.resize(); + if (bResize) + { + this.resize(); + } + } + }; + + HtmlEditor.prototype.setHtmlOrPlain = function (sText, bFocus) + { + if (':HTML:' === sText.substr(0, 6)) + { + this.setHtml(sText.substr(6), bFocus); + } + else + { + this.setPlain(sText, bFocus); } }; HtmlEditor.prototype.setHtml = function (sHtml, bFocus) { - if (this.editor) + if (this.editor && this.__inited) { this.modeToggle(true); - this.editor.setData(sHtml); + + try { + this.editor.setData(sHtml); + } catch (e) {} if (bFocus) { this.focus(); } } + else + { + this.__initedData = [true, sHtml, bFocus]; + } }; HtmlEditor.prototype.setPlain = function (sPlain, bFocus) { - if (this.editor) + if (this.editor && this.__inited) { this.modeToggle(false); if ('plain' === this.editor.mode && this.editor.plugins.plain && this.editor.__plain) @@ -146,7 +223,9 @@ } else { - this.editor.setData(sPlain); + try { + this.editor.setData(sPlain); + } catch (e) {} } if (bFocus) @@ -154,11 +233,15 @@ this.focus(); } } + else + { + this.__initedData = [false, sPlain, bFocus]; + } }; HtmlEditor.prototype.init = function () { - if (this.$element && this.$element[0]) + if (this.$element && this.$element[0] && !this.editor) { var self = this, @@ -167,13 +250,23 @@ var oConfig = Globals.oHtmlEditorDefaultConfig, sLanguage = Settings.settingsGet('Language'), - bSource = !!Settings.settingsGet('AllowHtmlEditorSourceButton') + bSource = !!Settings.settingsGet('AllowHtmlEditorSourceButton'), + bBiti = !!Settings.settingsGet('AllowHtmlEditorBitiButtons') ; - if (bSource && oConfig.toolbarGroups && !oConfig.toolbarGroups.__SourceInited) + if ((bSource || !bBiti) && !oConfig.toolbarGroups.__cfgInited) { - oConfig.toolbarGroups.__SourceInited = true; - oConfig.toolbarGroups.push({name: 'document', groups: ['mode', 'document', 'doctools']}); + oConfig.toolbarGroups.__cfgInited = true; + + if (bSource) + { + oConfig.removeButtons = oConfig.removeButtons.replace(',Source', ''); + } + + if (!bBiti) + { + oConfig.removePlugins += (oConfig.removePlugins ? ',' : '') + 'bidi'; + } } oConfig.enterMode = window.CKEDITOR.ENTER_BR; @@ -185,6 +278,19 @@ window.CKEDITOR.env.isCompatible = true; } +// oConfig.allowedContent = { +// $1: { +// elements: window.CKEDITOR.dtd, +// attributes: true, +// styles: true, +// classes: true +// } +// }; +// +// oConfig.disallowedContent = 'script; style; iframe; frame; *[on*]'; + + window.CKEDITOR.dtd.$removeEmpty['p'] = 1; + self.editor = window.CKEDITOR.appendTo(self.$element[0], oConfig); self.editor.on('key', function(oEvent) { @@ -216,12 +322,31 @@ { self.editor.on('instanceReady', function () { + if (self.editor.removeMenuItem) + { + self.editor.removeMenuItem('cut'); + self.editor.removeMenuItem('copy'); + self.editor.removeMenuItem('paste'); + } + self.editor.setKeystroke(window.CKEDITOR.CTRL + 65 /* A */, 'selectAll'); - self.editor.editable().addClass('cke_enable_context_menu'); self.fOnReady(); self.__resizable = true; + self.__inited = true; self.resize(); + + if (self.__initedData) + { + if (self.__initedData[0]) + { + self.setHtml(self.__initedData[1], self.__initedData[2]); + } + else + { + self.setPlain(self.__initedData[1], self.__initedData[2]); + } + } }); } } @@ -242,7 +367,9 @@ { if (this.editor) { - this.editor.focus(); + try { + this.editor.focus(); + } catch (e) {} } }; @@ -250,7 +377,9 @@ { if (this.editor) { - this.editor.focusManager.blur(true); + try { + this.editor.focusManager.blur(true); + } catch (e) {} } }; @@ -258,11 +387,19 @@ { if (this.editor && this.__resizable) { - try - { + try { this.editor.resize(this.$element.width(), this.$element.innerHeight()); - } - catch (e) {} + } catch (e) {} + } + }; + + HtmlEditor.prototype.setReadOnly = function (bValue) + { + if (this.editor) + { + try { + this.editor.setReadOnly(!!bValue); + } catch (e) {} } }; @@ -271,7 +408,6 @@ this.setHtml('', bFocus); }; - module.exports = HtmlEditor; }()); \ No newline at end of file diff --git a/dev/Common/LinkBuilder.js b/dev/Common/LinkBuilder.js deleted file mode 100644 index b49905454..000000000 --- a/dev/Common/LinkBuilder.js +++ /dev/null @@ -1,335 +0,0 @@ - -(function () { - - 'use strict'; - - var - Utils = require('Common/Utils') - ; - - /** - * @constructor - */ - function LinkBuilder() - { - var Settings = require('Storage/Settings'); - - this.sBase = '#/'; - this.sServer = './?'; - this.sVersion = Settings.settingsGet('Version'); - this.sSpecSuffix = Settings.settingsGet('AuthAccountHash') || '0'; - this.sStaticPrefix = Settings.settingsGet('StaticPrefix') || 'rainloop/v/' + this.sVersion + '/static/'; - } - - /** - * @return {string} - */ - LinkBuilder.prototype.root = function () - { - return this.sBase; - }; - - /** - * @param {string} sDownload - * @return {string} - */ - LinkBuilder.prototype.attachmentDownload = function (sDownload) - { - return this.sServer + '/Raw/' + this.sSpecSuffix + '/Download/' + sDownload; - }; - - /** - * @param {string} sDownload - * @return {string} - */ - LinkBuilder.prototype.attachmentPreview = function (sDownload) - { - return this.sServer + '/Raw/' + this.sSpecSuffix + '/View/' + sDownload; - }; - - /** - * @param {string} sDownload - * @return {string} - */ - LinkBuilder.prototype.attachmentPreviewAsPlain = function (sDownload) - { - return this.sServer + '/Raw/' + this.sSpecSuffix + '/ViewAsPlain/' + sDownload; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.upload = function () - { - return this.sServer + '/Upload/' + this.sSpecSuffix + '/'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.uploadContacts = function () - { - return this.sServer + '/UploadContacts/' + this.sSpecSuffix + '/'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.uploadBackground = function () - { - return this.sServer + '/UploadBackground/' + this.sSpecSuffix + '/'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.append = function () - { - return this.sServer + '/Append/' + this.sSpecSuffix + '/'; - }; - - /** - * @param {string} sEmail - * @return {string} - */ - LinkBuilder.prototype.change = function (sEmail) - { - return this.sServer + '/Change/' + this.sSpecSuffix + '/' + Utils.encodeURIComponent(sEmail) + '/'; - }; - - /** - * @param {string=} sAdd - * @return {string} - */ - LinkBuilder.prototype.ajax = function (sAdd) - { - return this.sServer + '/Ajax/' + this.sSpecSuffix + '/' + sAdd; - }; - - /** - * @param {string} sRequestHash - * @return {string} - */ - LinkBuilder.prototype.messageViewLink = function (sRequestHash) - { - return this.sServer + '/Raw/' + this.sSpecSuffix + '/ViewAsPlain/' + sRequestHash; - }; - - /** - * @param {string} sRequestHash - * @return {string} - */ - LinkBuilder.prototype.messageDownloadLink = function (sRequestHash) - { - return this.sServer + '/Raw/' + this.sSpecSuffix + '/Download/' + sRequestHash; - }; - - /** - * @param {string} sEmail - * @return {string} - */ - LinkBuilder.prototype.avatarLink = function (sEmail) - { - return this.sServer + '/Raw/0/Avatar/' + Utils.encodeURIComponent(sEmail) + '/'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.inbox = function () - { - return this.sBase + 'mailbox/Inbox'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.messagePreview = function () - { - return this.sBase + 'mailbox/message-preview'; - }; - - /** - * @param {string=} sScreenName - * @return {string} - */ - LinkBuilder.prototype.settings = function (sScreenName) - { - var sResult = this.sBase + 'settings'; - if (!Utils.isUnd(sScreenName) && '' !== sScreenName) - { - sResult += '/' + sScreenName; - } - - return sResult; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.about = function () - { - return this.sBase + 'about'; - }; - - /** - * @param {string} sScreenName - * @return {string} - */ - LinkBuilder.prototype.admin = function (sScreenName) - { - var sResult = this.sBase; - switch (sScreenName) { - case 'AdminDomains': - sResult += 'domains'; - break; - case 'AdminSecurity': - sResult += 'security'; - break; - case 'AdminLicensing': - sResult += 'licensing'; - break; - } - - return sResult; - }; - - /** - * @param {string} sFolder - * @param {number=} iPage = 1 - * @param {string=} sSearch = '' - * @return {string} - */ - LinkBuilder.prototype.mailBox = function (sFolder, iPage, sSearch) - { - iPage = Utils.isNormal(iPage) ? Utils.pInt(iPage) : 1; - sSearch = Utils.pString(sSearch); - - var sResult = this.sBase + 'mailbox/'; - if ('' !== sFolder) - { - sResult += encodeURI(sFolder); - } - if (1 < iPage) - { - sResult = sResult.replace(/[\/]+$/, ''); - sResult += '/p' + iPage; - } - if ('' !== sSearch) - { - sResult = sResult.replace(/[\/]+$/, ''); - sResult += '/' + encodeURI(sSearch); - } - - return sResult; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.phpInfo = function () - { - return this.sServer + 'Info'; - }; - - /** - * @param {string} sLang - * @return {string} - */ - LinkBuilder.prototype.langLink = function (sLang) - { - return this.sServer + '/Lang/0/' + encodeURI(sLang) + '/' + this.sVersion + '/'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.exportContactsVcf = function () - { - return this.sServer + '/Raw/' + this.sSpecSuffix + '/ContactsVcf/'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.exportContactsCsv = function () - { - return this.sServer + '/Raw/' + this.sSpecSuffix + '/ContactsCsv/'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.emptyContactPic = function () - { - return this.sStaticPrefix + 'css/images/empty-contact.png'; - }; - - /** - * @param {string} sFileName - * @return {string} - */ - LinkBuilder.prototype.sound = function (sFileName) - { - return this.sStaticPrefix + 'sounds/' + sFileName; - }; - - /** - * @param {string} sTheme - * @return {string} - */ - LinkBuilder.prototype.themePreviewLink = function (sTheme) - { - var sPrefix = 'rainloop/v/' + this.sVersion + '/'; - if ('@custom' === sTheme.substr(-7)) - { - sTheme = Utils.trim(sTheme.substring(0, sTheme.length - 7)); - sPrefix = ''; - } - - return sPrefix + 'themes/' + encodeURI(sTheme) + '/images/preview.png'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.notificationMailIcon = function () - { - return this.sStaticPrefix + 'css/images/icom-message-notification.png'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.openPgpJs = function () - { - return this.sStaticPrefix + 'js/min/openpgp.js'; - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.socialGoogle = function () - { - return this.sServer + 'SocialGoogle' + ('' !== this.sSpecSuffix ? '/' + this.sSpecSuffix + '/' : ''); - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.socialTwitter = function () - { - return this.sServer + 'SocialTwitter' + ('' !== this.sSpecSuffix ? '/' + this.sSpecSuffix + '/' : ''); - }; - - /** - * @return {string} - */ - LinkBuilder.prototype.socialFacebook = function () - { - return this.sServer + 'SocialFacebook' + ('' !== this.sSpecSuffix ? '/' + this.sSpecSuffix + '/' : ''); - }; - - module.exports = new LinkBuilder(); - -}()); \ No newline at end of file diff --git a/dev/Common/Links.js b/dev/Common/Links.js new file mode 100644 index 000000000..da26296b0 --- /dev/null +++ b/dev/Common/Links.js @@ -0,0 +1,394 @@ + +(function () { + + 'use strict'; + + var + window = require('window'), + Utils = require('Common/Utils') + ; + + /** + * @constructor + */ + function Links() + { + var Settings = require('Storage/Settings'); + + this.sBase = '#/'; + this.sServer = './?'; + + this.sVersion = Settings.settingsGet('Version'); + this.sSpecSuffix = Settings.settingsGet('AuthAccountHash') || '0'; + this.sWebPrefix = Settings.settingsGet('WebPath') || ''; + this.sVersionPrefix = Settings.settingsGet('WebVersionPath') || 'rainloop/v/' + this.sVersion + '/'; + this.sStaticPrefix = this.sVersionPrefix + 'static/'; + } + + /** + * @return {string} + */ + Links.prototype.subQueryPrefix = function () + { + return '&q[]='; + }; + + /** + * @return {string} + */ + Links.prototype.root = function () + { + return this.sBase; + }; + + /** + * @return {string} + */ + Links.prototype.rootAdmin = function () + { + return this.sServer + '/Admin/'; + }; + + /** + * @return {string} + */ + Links.prototype.rootUser = function () + { + return './'; + }; + + /** + * @param {string} sDownload + * @return {string} + */ + Links.prototype.attachmentDownload = function (sDownload) + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/Download/' + this.subQueryPrefix() + '/' + sDownload; + }; + + /** + * @param {string} sDownload + * @return {string} + */ + Links.prototype.attachmentPreview = function (sDownload) + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/View/' + this.subQueryPrefix() + '/' + sDownload; + }; + + /** + * @param {string} sDownload + * @return {string} + */ + Links.prototype.attachmentThumbnailPreview = function (sDownload) + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/ViewThumbnail/' + this.subQueryPrefix() + '/' + sDownload; + }; + + /** + * @param {string} sDownload + * @return {string} + */ + Links.prototype.attachmentPreviewAsPlain = function (sDownload) + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/ViewAsPlain/' + this.subQueryPrefix() + '/' + sDownload; + }; + + /** + * @param {string} sDownload + * @return {string} + */ + Links.prototype.attachmentFramed = function (sDownload) + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/FramedView/' + this.subQueryPrefix() + '/' + sDownload; + }; + + /** + * @return {string} + */ + Links.prototype.upload = function () + { + return this.sServer + '/Upload/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/'; + }; + + /** + * @return {string} + */ + Links.prototype.uploadContacts = function () + { + return this.sServer + '/UploadContacts/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/'; + }; + + /** + * @return {string} + */ + Links.prototype.uploadBackground = function () + { + return this.sServer + '/UploadBackground/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/'; + }; + + /** + * @return {string} + */ + Links.prototype.append = function () + { + return this.sServer + '/Append/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/'; + }; + + /** + * @param {string} sEmail + * @return {string} + */ + Links.prototype.change = function (sEmail) + { + return this.sServer + '/Change/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/' + Utils.encodeURIComponent(sEmail) + '/'; + }; + + /** + * @param {string=} sAdd + * @return {string} + */ + Links.prototype.ajax = function (sAdd) + { + return this.sServer + '/Ajax/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/' + sAdd; + }; + + /** + * @param {string} sRequestHash + * @return {string} + */ + Links.prototype.messageViewLink = function (sRequestHash) + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/ViewAsPlain/' + this.subQueryPrefix() + '/' + sRequestHash; + }; + + /** + * @param {string} sRequestHash + * @return {string} + */ + Links.prototype.messageDownloadLink = function (sRequestHash) + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/Download/' + this.subQueryPrefix() + '/' + sRequestHash; + }; + + /** + * @param {string} sEmail + * @return {string} + */ + Links.prototype.avatarLink = function (sEmail) + { + return this.sServer + '/Raw/0/Avatar/' + Utils.encodeURIComponent(sEmail) + '/'; + }; + + /** + * @param {string} sHash + * @return {string} + */ + Links.prototype.publicLink = function (sHash) + { + return this.sServer + '/Raw/0/Public/' + sHash + '/'; + }; + + /** + * @param {string} sHash + * @return {string} + */ + Links.prototype.userBackground = function (sHash) + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + + '/UserBackground/' + this.subQueryPrefix() + '/' + sHash; + }; + + /** + * @param {string} sInboxFolderName = 'INBOX' + * @return {string} + */ + Links.prototype.inbox = function (sInboxFolderName) + { + sInboxFolderName = Utils.isUnd(sInboxFolderName) ? 'INBOX' : sInboxFolderName; + return this.sBase + 'mailbox/' + sInboxFolderName; + }; + + /** + * @param {string=} sScreenName + * @return {string} + */ + Links.prototype.settings = function (sScreenName) + { + var sResult = this.sBase + 'settings'; + if (!Utils.isUnd(sScreenName) && '' !== sScreenName) + { + sResult += '/' + sScreenName; + } + + return sResult; + }; + + /** + * @return {string} + */ + Links.prototype.about = function () + { + return this.sBase + 'about'; + }; + + /** + * @param {string} sScreenName + * @return {string} + */ + Links.prototype.admin = function (sScreenName) + { + var sResult = this.sBase; + switch (sScreenName) { + case 'AdminDomains': + sResult += 'domains'; + break; + case 'AdminSecurity': + sResult += 'security'; + break; + case 'AdminLicensing': + sResult += 'licensing'; + break; + } + + return sResult; + }; + + /** + * @param {string} sFolder + * @param {number=} iPage = 1 + * @param {string=} sSearch = '' + * @return {string} + */ + Links.prototype.mailBox = function (sFolder, iPage, sSearch) + { + iPage = Utils.isNormal(iPage) ? Utils.pInt(iPage) : 1; + sSearch = Utils.pString(sSearch); + + var sResult = this.sBase + 'mailbox/'; + if ('' !== sFolder) + { + sResult += encodeURI(sFolder); + } + if (1 < iPage) + { + sResult = sResult.replace(/[\/]+$/, ''); + sResult += '/p' + iPage; + } + if ('' !== sSearch) + { + sResult = sResult.replace(/[\/]+$/, ''); + sResult += '/' + encodeURI(sSearch); + } + + return sResult; + }; + + /** + * @return {string} + */ + Links.prototype.phpInfo = function () + { + return this.sServer + 'Info'; + }; + + /** + * @param {string} sLang + * @return {string} + */ + Links.prototype.langLink = function (sLang, bAdmin) + { + return this.sServer + '/Lang/0/' + (bAdmin ? 'Admin' : 'App') + '/' + encodeURI(sLang) + '/' + this.sVersion + '/'; + }; + + /** + * @return {string} + */ + Links.prototype.exportContactsVcf = function () + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/ContactsVcf/'; + }; + + /** + * @return {string} + */ + Links.prototype.exportContactsCsv = function () + { + return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/ContactsCsv/'; + }; + + /** + * @return {string} + */ + Links.prototype.emptyContactPic = function () + { + return this.sStaticPrefix + 'css/images/empty-contact.png'; + }; + + /** + * @param {string} sFileName + * @return {string} + */ + Links.prototype.sound = function (sFileName) + { + return this.sStaticPrefix + 'sounds/' + sFileName; + }; + + /** + * @param {string} sTheme + * @return {string} + */ + Links.prototype.themePreviewLink = function (sTheme) + { + var sPrefix = this.sVersionPrefix; + if ('@custom' === sTheme.substr(-7)) + { + sTheme = Utils.trim(sTheme.substring(0, sTheme.length - 7)); + sPrefix = this.sWebPrefix; + } + + return sPrefix + 'themes/' + window.encodeURI(sTheme) + '/images/preview.png'; + }; + + /** + * @return {string} + */ + Links.prototype.notificationMailIcon = function () + { + return this.sStaticPrefix + 'css/images/icom-message-notification.png'; + }; + + /** + * @return {string} + */ + Links.prototype.openPgpJs = function () + { + return this.sStaticPrefix + 'js/min/openpgp.js'; + }; + + /** + * @return {string} + */ + Links.prototype.socialGoogle = function () + { + return this.sServer + 'SocialGoogle' + ('' !== this.sSpecSuffix ? '/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/' : ''); + }; + + /** + * @return {string} + */ + Links.prototype.socialTwitter = function () + { + return this.sServer + 'SocialTwitter' + ('' !== this.sSpecSuffix ? '/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/' : ''); + }; + + /** + * @return {string} + */ + Links.prototype.socialFacebook = function () + { + return this.sServer + 'SocialFacebook' + ('' !== this.sSpecSuffix ? '/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/' : ''); + }; + + module.exports = new Links(); + +}()); \ No newline at end of file diff --git a/dev/Common/Mime.js b/dev/Common/Mime.js new file mode 100644 index 000000000..a29e3ae36 --- /dev/null +++ b/dev/Common/Mime.js @@ -0,0 +1,168 @@ +(function () { + + 'use strict'; + + module.exports = { + + 'eml' : 'message/rfc822', + 'mime' : 'message/rfc822', + 'txt' : 'text/plain', + 'text' : 'text/plain', + 'def' : 'text/plain', + 'list' : 'text/plain', + 'in' : 'text/plain', + 'ini' : 'text/plain', + 'log' : 'text/plain', + 'sql' : 'text/plain', + 'cfg' : 'text/plain', + 'conf' : 'text/plain', + 'asc' : 'text/plain', + 'rtx' : 'text/richtext', + 'vcard' : 'text/vcard', + 'vcf' : 'text/vcard', + 'htm' : 'text/html', + 'html' : 'text/html', + 'csv' : 'text/csv', + 'ics' : 'text/calendar', + 'ifb' : 'text/calendar', + 'xml' : 'text/xml', + 'json' : 'application/json', + 'swf' : 'application/x-shockwave-flash', + 'hlp' : 'application/winhlp', + 'wgt' : 'application/widget', + 'chm' : 'application/vnd.ms-htmlhelp', + 'p10' : 'application/pkcs10', + 'p7c' : 'application/pkcs7-mime', + 'p7m' : 'application/pkcs7-mime', + 'p7s' : 'application/pkcs7-signature', + 'torrent' : 'application/x-bittorrent', + + // scripts + 'js' : 'application/javascript', + 'pl' : 'text/perl', + 'css' : 'text/css', + 'asp' : 'text/asp', + 'php' : 'application/x-httpd-php', + 'php3' : 'application/x-httpd-php', + 'php4' : 'application/x-httpd-php', + 'php5' : 'application/x-httpd-php', + 'phtml' : 'application/x-httpd-php', + + // images + 'png' : 'image/png', + 'jpg' : 'image/jpeg', + 'jpeg' : 'image/jpeg', + 'jpe' : 'image/jpeg', + 'jfif' : 'image/jpeg', + 'gif' : 'image/gif', + 'bmp' : 'image/bmp', + 'cgm' : 'image/cgm', + 'ief' : 'image/ief', + 'ico' : 'image/x-icon', + 'tif' : 'image/tiff', + 'tiff' : 'image/tiff', + 'svg' : 'image/svg+xml', + 'svgz' : 'image/svg+xml', + 'djv' : 'image/vnd.djvu', + 'djvu' : 'image/vnd.djvu', + 'webp' : 'image/webp', + + // archives + 'zip' : 'application/zip', + '7z' : 'application/x-7z-compressed', + 'rar' : 'application/x-rar-compressed', + 'exe' : 'application/x-msdownload', + 'dll' : 'application/x-msdownload', + 'scr' : 'application/x-msdownload', + 'com' : 'application/x-msdownload', + 'bat' : 'application/x-msdownload', + 'msi' : 'application/x-msdownload', + 'cab' : 'application/vnd.ms-cab-compressed', + 'gz' : 'application/x-gzip', + 'tgz' : 'application/x-gzip', + 'bz' : 'application/x-bzip', + 'bz2' : 'application/x-bzip2', + 'deb' : 'application/x-debian-package', + + // fonts + 'psf' : 'application/x-font-linux-psf', + 'otf' : 'application/x-font-otf', + 'pcf' : 'application/x-font-pcf', + 'snf' : 'application/x-font-snf', + 'ttf' : 'application/x-font-ttf', + 'ttc' : 'application/x-font-ttf', + + // audio + 'mp3' : 'audio/mpeg', + 'amr' : 'audio/amr', + 'aac' : 'audio/x-aac', + 'aif' : 'audio/x-aiff', + 'aifc' : 'audio/x-aiff', + 'aiff' : 'audio/x-aiff', + 'wav' : 'audio/x-wav', + 'wma' : 'audio/x-ms-wma', + 'wax' : 'audio/x-ms-wax', + 'midi' : 'audio/midi', + 'mp4a' : 'audio/mp4', + 'ogg' : 'audio/ogg', + 'weba' : 'audio/webm', + 'ra' : 'audio/x-pn-realaudio', + 'ram' : 'audio/x-pn-realaudio', + 'rmp' : 'audio/x-pn-realaudio-plugin', + 'm3u' : 'audio/x-mpegurl', + + // video + 'flv' : 'video/x-flv', + 'qt' : 'video/quicktime', + 'mov' : 'video/quicktime', + 'wmv' : 'video/windows-media', + 'avi' : 'video/x-msvideo', + 'mpg' : 'video/mpeg', + 'mpeg' : 'video/mpeg', + 'mpe' : 'video/mpeg', + 'm1v' : 'video/mpeg', + 'm2v' : 'video/mpeg', + '3gp' : 'video/3gpp', + '3g2' : 'video/3gpp2', + 'h261' : 'video/h261', + 'h263' : 'video/h263', + 'h264' : 'video/h264', + 'jpgv' : 'video/jpgv', + 'mp4' : 'video/mp4', + 'mp4v' : 'video/mp4', + 'mpg4' : 'video/mp4', + 'ogv' : 'video/ogg', + 'webm' : 'video/webm', + 'm4v' : 'video/x-m4v', + 'asf' : 'video/x-ms-asf', + 'asx' : 'video/x-ms-asf', + 'wm' : 'video/x-ms-wm', + 'wmx' : 'video/x-ms-wmx', + 'wvx' : 'video/x-ms-wvx', + 'movie' : 'video/x-sgi-movie', + + // adobe + 'pdf' : 'application/pdf', + 'psd' : 'image/vnd.adobe.photoshop', + 'ai' : 'application/postscript', + 'eps' : 'application/postscript', + 'ps' : 'application/postscript', + + // ms office + 'doc' : 'application/msword', + 'dot' : 'application/msword', + 'rtf' : 'application/rtf', + 'xls' : 'application/vnd.ms-excel', + 'ppt' : 'application/vnd.ms-powerpoint', + 'docx' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xlsx' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'dotx' : 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'pptx' : 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + + // open office + 'odt' : 'application/vnd.oasis.opendocument.text', + 'ods' : 'application/vnd.oasis.opendocument.spreadsheet' + + }; + +}()); diff --git a/dev/Common/Momentor.js b/dev/Common/Momentor.js new file mode 100644 index 000000000..5634f84f8 --- /dev/null +++ b/dev/Common/Momentor.js @@ -0,0 +1,188 @@ + +(function () { + + 'use strict'; + + var + window = require('window'), + $ = require('$'), + _ = require('_'), + moment = require('moment'), + + Translator = require('Common/Translator') + ; + + /** + * @constructor + */ + function Momentor() + { + this.format = _.bind(this.format, this); + + this.updateMomentNow = _.debounce(_.bind(function () { + this._moment = moment(); + }, this), 500, true); + + this.updateMomentNowUnix = _.debounce(_.bind(function () { + this._momentNow = moment().unix(); + }, this), 500, true); + } + + Momentor.prototype._moment = null; + Momentor.prototype._momentNow = 0; + + Momentor.prototype.momentNow = function () + { + this.updateMomentNow(); + return this._moment || moment(); + }; + + Momentor.prototype.momentNowUnix = function () + { + this.updateMomentNowUnix(); + return this._momentNow || 0; + }; + + Momentor.prototype.searchSubtractFormatDateHelper = function (iDate) + { + var oM = this.momentNow(); + return oM.clone().subtract('days', iDate).format('YYYY.MM.DD'); + }; + + /** + * @param {Object} oMoment + * @return {string} + */ + Momentor.prototype.formatCustomShortDate = function (oMoment) + { + var + sResult = '', + oMomentNow = this.momentNow() + ; + + if (oMoment && oMomentNow) + { + if (4 >= oMomentNow.diff(oMoment, 'hours')) + { + sResult = oMoment.fromNow(); + } + else if (oMomentNow.format('L') === oMoment.format('L')) + { + sResult = Translator.i18n('MESSAGE_LIST/TODAY_AT', { + 'TIME': oMoment.format('LT') + }); + } + else if (oMomentNow.clone().subtract('days', 1).format('L') === oMoment.format('L')) + { + sResult = Translator.i18n('MESSAGE_LIST/YESTERDAY_AT', { + 'TIME': oMoment.format('LT') + }); + } + else if (oMomentNow.year() === oMoment.year()) + { + sResult = oMoment.format('D MMM.'); + } + else + { + sResult = oMoment.format('LL'); + } + } + + return sResult; + }; + + /** + * @param {number} iTimeStampInUTC + * @param {string} sFormat + * @return {string} + */ + Momentor.prototype.format = function (iTimeStampInUTC, sFormat) + { + var + oM = null, + sResult = '', + iNow = this.momentNowUnix() + ; + + iTimeStampInUTC = 0 < iTimeStampInUTC ? iTimeStampInUTC : (0 === iTimeStampInUTC ? iNow : 0); + iTimeStampInUTC = iNow < iTimeStampInUTC ? iNow : iTimeStampInUTC; + + oM = 0 < iTimeStampInUTC ? moment.unix(iTimeStampInUTC) : null; + + if (oM && 1970 === oM.year()) + { + oM = null; + } + + if (oM) + { + switch (sFormat) + { + case 'FROMNOW': + sResult = oM.fromNow(); + break; + case 'SHORT': + sResult = this.formatCustomShortDate(oM); + break; + case 'FULL': + sResult = oM.format('LLL'); + break; + default: + sResult = oM.format(sFormat); + break; + } + } + + return sResult; + }; + + /** + * @param {Object} oElement + */ + Momentor.prototype.momentToNode = function (oElement) + { + var + sKey = '', + iTime = 0, + $oEl = $(oElement) + ; + + iTime = $oEl.data('moment-time'); + if (iTime) + { + sKey = $oEl.data('moment-format'); + + if (sKey) + { + $oEl.text(this.format(iTime, sKey)); + } + + sKey = $oEl.data('moment-format-title'); + if (sKey) + { + $oEl.attr('title', this.format(iTime, sKey)); + } + } + }; + + /** + * @param {Object} oElements + */ + Momentor.prototype.momentToNodes = function (oElements) + { + var self = this; + _.defer(function () { + $('.moment', oElements).each(function () { + self.momentToNode(this); + }); + }); + }; + + Momentor.prototype.reload = function () + { + this.momentToNodes(window.document); + }; + + module.exports = new Momentor(); + +}()); diff --git a/dev/Common/Plugins.js b/dev/Common/Plugins.js index fbb9f34bf..8a24e26b1 100644 --- a/dev/Common/Plugins.js +++ b/dev/Common/Plugins.js @@ -16,8 +16,10 @@ function Plugins() { this.oSettings = require('Storage/Settings'); - this.oViewModelsHooks = {}; this.oSimpleHooks = {}; + + this.aUserViewModelsHooks = []; + this.aAdminViewModelsHooks = []; } /** @@ -26,9 +28,14 @@ Plugins.prototype.oSettings = {}; /** - * @type {Object} + * @type {Array} */ - Plugins.prototype.oViewModelsHooks = {}; + Plugins.prototype.aUserViewModelsHooks = []; + + /** + * @type {Array} + */ + Plugins.prototype.aAdminViewModelsHooks = []; /** * @type {Object} @@ -82,17 +89,44 @@ * @param {string} sAction * @param {Object=} oParameters * @param {?number=} iTimeout - * @param {string=} sGetAdd = '' - * @param {Array=} aAbortActions = [] */ - Plugins.prototype.remoteRequest = function (fCallback, sAction, oParameters, iTimeout, sGetAdd, aAbortActions) + Plugins.prototype.remoteRequest = function (fCallback, sAction, oParameters, iTimeout) { if (Globals.__APP__) { - Globals.__APP__.remote().defaultRequest(fCallback, sAction, oParameters, iTimeout, sGetAdd, aAbortActions); + Globals.__APP__.remote().defaultRequest(fCallback, 'Plugin' + sAction, oParameters, iTimeout); } }; + /** + * @param {Function} SettingsViewModelClass + * @param {string} sLabelName + * @param {string} sTemplate + * @param {string} sRoute + */ + Plugins.prototype.addSettingsViewModel = function (SettingsViewModelClass, sTemplate, sLabelName, sRoute) + { + this.aUserViewModelsHooks.push([SettingsViewModelClass, sTemplate, sLabelName, sRoute]); + }; + + /** + * @param {Function} SettingsViewModelClass + * @param {string} sLabelName + * @param {string} sTemplate + * @param {string} sRoute + */ + Plugins.prototype.addSettingsViewModelForAdmin = function (SettingsViewModelClass, sTemplate, sLabelName, sRoute) + { + this.aAdminViewModelsHooks.push([SettingsViewModelClass, sTemplate, sLabelName, sRoute]); + }; + + Plugins.prototype.runSettingsViewModelHooks = function (bAdmin) + { + _.each(bAdmin ? this.aAdminViewModelsHooks : this.aUserViewModelsHooks, function (aView) { + require('Knoin/Knoin').addSettingsViewModel(aView[0], aView[1], aView[2], aView[3]); + }); + }; + /** * @param {string} sPluginSection * @param {string} sName diff --git a/dev/Common/Selector.js b/dev/Common/Selector.js index 98e7ff875..0918e42b1 100644 --- a/dev/Common/Selector.js +++ b/dev/Common/Selector.js @@ -17,12 +17,13 @@ * @constructor * @param {koProperty} oKoList * @param {koProperty} oKoSelectedItem + * @param {koProperty} oKoFocusedItem * @param {string} sItemSelector * @param {string} sItemSelectedSelector * @param {string} sItemCheckedSelector * @param {string} sItemFocusedSelector */ - function Selector(oKoList, oKoSelectedItem, + function Selector(oKoList, oKoSelectedItem, oKoFocusedItem, sItemSelector, sItemSelectedSelector, sItemCheckedSelector, sItemFocusedSelector) { this.list = oKoList; @@ -37,8 +38,8 @@ return 0 < this.listChecked().length; }, this); - this.focusedItem = ko.observable(null); - this.selectedItem = oKoSelectedItem; + this.focusedItem = oKoFocusedItem || ko.observable(null); + this.selectedItem = oKoSelectedItem || ko.observable(null); this.selectedItemUseCallback = true; this.itemSelectedThrottle = _.debounce(_.bind(this.itemSelected, this), 300); @@ -48,14 +49,17 @@ { if (null === this.selectedItem()) { - this.selectedItem.valueHasMutated(); + if (this.selectedItem.valueHasMutated) + { + this.selectedItem.valueHasMutated(); + } } else { this.selectedItem(null); } } - else if (this.bAutoSelect && this.focusedItem()) + else if (this.autoSelect() && this.focusedItem()) { this.selectedItem(this.focusedItem()); } @@ -84,7 +88,7 @@ }, this); - this.selectedItem.extend({'toggleSubscribe': [null, + this.selectedItem = this.selectedItem.extend({'toggleSubscribe': [null, function (oPrev) { if (oPrev) { @@ -98,7 +102,7 @@ } ]}); - this.focusedItem.extend({'toggleSubscribe': [null, + this.focusedItem = this.focusedItem.extend({'toggleSubscribe': [null, function (oPrev) { if (oPrev) { @@ -112,6 +116,8 @@ } ]}); + this.iSelectNextHelper = 0; + this.iFocusedNextHelper = 0; this.oContentVisible = null; this.oContentScrollable = null; @@ -121,10 +127,10 @@ this.sItemFocusedSelector = sItemFocusedSelector; this.sLastUid = ''; - this.bAutoSelect = true; this.oCallbacks = {}; this.emptyFunction = function () {}; + this.emptyTrueFunction = function () { return true; }; this.focusedItem.subscribe(function (oItem) { if (oItem) @@ -218,7 +224,7 @@ this.selectedItemUseCallback = true; - if (!bChecked && !bSelected && this.bAutoSelect) + if (!bChecked && !bSelected && this.autoSelect()) { if (self.focusedItem()) { @@ -253,6 +259,39 @@ self.focusedItem(self.selectedItem()); } } + + if ((0 !== this.iSelectNextHelper || 0 !== this.iFocusedNextHelper) && 0 < aItems.length && !self.focusedItem()) + { + oTemp = null; + if (0 !== this.iFocusedNextHelper) + { + oTemp = aItems[-1 === this.iFocusedNextHelper ? aItems.length - 1 : 0] || null; + } + + if (!oTemp && 0 !== this.iSelectNextHelper) + { + oTemp = aItems[-1 === this.iSelectNextHelper ? aItems.length - 1 : 0] || null; + } + + if (oTemp) + { + if (0 !== this.iSelectNextHelper) + { + self.selectedItem(oTemp || null); + } + + self.focusedItem(oTemp || null); + + self.scrollToFocused(); + + _.delay(function () { + self.scrollToFocused(); + }, 100); + } + + this.iSelectNextHelper = 0; + this.iFocusedNextHelper = 0; + } } aCache = []; @@ -291,6 +330,12 @@ this.newSelectPosition(Enums.EventKeyCode.Up, false, bForceSelect); }; + Selector.prototype.unselect = function () + { + this.selectedItem(null); + this.focusedItem(null); + }; + Selector.prototype.init = function (oContentVisible, oContentScrollable, sKeyScope) { this.oContentVisible = oContentVisible; @@ -348,7 +393,6 @@ key('up, shift+up, down, shift+down, home, end, pageup, pagedown, insert, space', sKeyScope, function (event, handler) { if (event && handler && handler.shortcut) { - // TODO var iKey = 0; switch (handler.shortcut) { @@ -390,14 +434,25 @@ } }; - Selector.prototype.autoSelect = function (bValue) + /** + * @return {boolean} + */ + Selector.prototype.autoSelect = function () { - this.bAutoSelect = !!bValue; + return !!(this.oCallbacks['onAutoSelect'] || this.emptyTrueFunction)(); + }; + + /** + * @param {boolean} + */ + Selector.prototype.doUpUpOrDownDown = function (bUp) + { + (this.oCallbacks['onUpUpOrDownDown'] || this.emptyTrueFunction)(!!bUp); }; /** * @param {Object} oItem - * @returns {string} + * @return {string} */ Selector.prototype.getItemUid = function (oItem) { @@ -478,6 +533,11 @@ } } }); + + if (!oResult && (Enums.EventKeyCode.Down === iEventKeyCode || Enums.EventKeyCode.Up === iEventKeyCode)) + { + this.doUpUpOrDownDown(Enums.EventKeyCode.Up === iEventKeyCode); + } } else if (Enums.EventKeyCode.Home === iEventKeyCode || Enums.EventKeyCode.End === iEventKeyCode) { @@ -538,7 +598,7 @@ } } - if ((this.bAutoSelect || !!bForceSelect) && + if ((this.autoSelect() || !!bForceSelect) && !this.isListChecked() && Enums.EventKeyCode.Space !== iEventKeyCode) { this.selectedItem(oResult); @@ -607,7 +667,7 @@ return false; } - if (bFast) + if (bFast || 50 > this.oContentScrollable.scrollTop()) { this.oContentScrollable.scrollTop(0); } @@ -682,7 +742,7 @@ if (oEvent) { - if (oEvent.shiftKey && !oEvent.ctrlKey && !oEvent.altKey) + if (oEvent.shiftKey && !(oEvent.ctrlKey || oEvent.metaKey) && !oEvent.altKey) { bClick = false; if ('' === this.sLastUid) @@ -695,7 +755,7 @@ this.focusedItem(oItem); } - else if (oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey) + else if ((oEvent.ctrlKey || oEvent.metaKey) && !oEvent.shiftKey && !oEvent.altKey) { bClick = false; this.focusedItem(oItem); diff --git a/dev/Common/Translator.js b/dev/Common/Translator.js new file mode 100644 index 000000000..b9fd187ea --- /dev/null +++ b/dev/Common/Translator.js @@ -0,0 +1,321 @@ + +(function () { + + 'use strict'; + + var + window = require('window'), + $ = require('$'), + _ = require('_'), + ko = require('ko'), + + Enums = require('Common/Enums'), + Globals = require('Common/Globals') + ; + + /** + * @constructor + */ + function Translator() + { + this.data = window['rainloopI18N'] || {}; + this.notificationI18N = {}; + + this.trigger = ko.observable(false); + + this.i18n = _.bind(this.i18n, this); + } + + Translator.prototype.data = {}; + Translator.prototype.notificationI18N = {}; + + /** + * @param {string} sKey + * @param {Object=} oValueList + * @param {string=} sDefaulValue + * @return {string} + */ + Translator.prototype.i18n = function (sKey, oValueList, sDefaulValue) + { + var + sValueName = '', + sResult = _.isUndefined(this.data[sKey]) ? (_.isUndefined(sDefaulValue) ? sKey : sDefaulValue) : this.data[sKey] + ; + + if (!_.isUndefined(oValueList) && !_.isNull(oValueList)) + { + for (sValueName in oValueList) + { + if (_.has(oValueList, sValueName)) + { + sResult = sResult.replace('%' + sValueName + '%', oValueList[sValueName]); + } + } + } + + return sResult; + }; + + /** + * @param {Object} oElement + */ + Translator.prototype.i18nToNode = function (oElement) + { + var + sKey = '', + $oEl = $(oElement) + ; + + sKey = $oEl.data('i18n'); + if (sKey) + { + if ('[' === sKey.substr(0, 1)) + { + switch (sKey.substr(0, 6)) + { + case '[html]': + $oEl.html(this.i18n(sKey.substr(6))); + break; + case '[place': + $oEl.attr('placeholder', this.i18n(sKey.substr(13))); + break; + case '[title': + $oEl.attr('title', this.i18n(sKey.substr(7))); + break; + } + } + else + { + $oEl.text(this.i18n(sKey)); + } + } + }; + + /** + * @param {Object} oElements + * @param {boolean=} bAnimate = false + */ + Translator.prototype.i18nToNodes = function (oElements, bAnimate) + { + var self = this; + _.defer(function () { + + $('[data-i18n]', oElements).each(function () { + self.i18nToNode(this); + }); + + if (bAnimate && Globals.bAnimationSupported) + { + $('.i18n-animation[data-i18n]', oElements).letterfx({ + 'fx': 'fall fade', 'backwards': false, 'timing': 50, 'fx_duration': '50ms', 'letter_end': 'restore', 'element_end': 'restore' + }); + } + }); + }; + + Translator.prototype.reloadData = function () + { + if (window['rainloopI18N']) + { + this.data = window['rainloopI18N'] || {}; + + this.i18nToNodes(window.document, true); + + require('Common/Momentor').reload(); + this.trigger(!this.trigger()); + } + + window['rainloopI18N'] = null; + }; + + Translator.prototype.initNotificationLanguage = function () + { + var oN = this.notificationI18N || {}; + + oN[Enums.Notification.InvalidToken] = this.i18n('NOTIFICATIONS/INVALID_TOKEN'); + oN[Enums.Notification.AuthError] = this.i18n('NOTIFICATIONS/AUTH_ERROR'); + oN[Enums.Notification.AccessError] = this.i18n('NOTIFICATIONS/ACCESS_ERROR'); + oN[Enums.Notification.ConnectionError] = this.i18n('NOTIFICATIONS/CONNECTION_ERROR'); + oN[Enums.Notification.CaptchaError] = this.i18n('NOTIFICATIONS/CAPTCHA_ERROR'); + oN[Enums.Notification.SocialFacebookLoginAccessDisable] = this.i18n('NOTIFICATIONS/SOCIAL_FACEBOOK_LOGIN_ACCESS_DISABLE'); + oN[Enums.Notification.SocialTwitterLoginAccessDisable] = this.i18n('NOTIFICATIONS/SOCIAL_TWITTER_LOGIN_ACCESS_DISABLE'); + oN[Enums.Notification.SocialGoogleLoginAccessDisable] = this.i18n('NOTIFICATIONS/SOCIAL_GOOGLE_LOGIN_ACCESS_DISABLE'); + oN[Enums.Notification.DomainNotAllowed] = this.i18n('NOTIFICATIONS/DOMAIN_NOT_ALLOWED'); + oN[Enums.Notification.AccountNotAllowed] = this.i18n('NOTIFICATIONS/ACCOUNT_NOT_ALLOWED'); + + oN[Enums.Notification.AccountTwoFactorAuthRequired] = this.i18n('NOTIFICATIONS/ACCOUNT_TWO_FACTOR_AUTH_REQUIRED'); + oN[Enums.Notification.AccountTwoFactorAuthError] = this.i18n('NOTIFICATIONS/ACCOUNT_TWO_FACTOR_AUTH_ERROR'); + + oN[Enums.Notification.CouldNotSaveNewPassword] = this.i18n('NOTIFICATIONS/COULD_NOT_SAVE_NEW_PASSWORD'); + oN[Enums.Notification.CurrentPasswordIncorrect] = this.i18n('NOTIFICATIONS/CURRENT_PASSWORD_INCORRECT'); + oN[Enums.Notification.NewPasswordShort] = this.i18n('NOTIFICATIONS/NEW_PASSWORD_SHORT'); + oN[Enums.Notification.NewPasswordWeak] = this.i18n('NOTIFICATIONS/NEW_PASSWORD_WEAK'); + oN[Enums.Notification.NewPasswordForbidden] = this.i18n('NOTIFICATIONS/NEW_PASSWORD_FORBIDDENT'); + + oN[Enums.Notification.ContactsSyncError] = this.i18n('NOTIFICATIONS/CONTACTS_SYNC_ERROR'); + + oN[Enums.Notification.CantGetMessageList] = this.i18n('NOTIFICATIONS/CANT_GET_MESSAGE_LIST'); + oN[Enums.Notification.CantGetMessage] = this.i18n('NOTIFICATIONS/CANT_GET_MESSAGE'); + oN[Enums.Notification.CantDeleteMessage] = this.i18n('NOTIFICATIONS/CANT_DELETE_MESSAGE'); + oN[Enums.Notification.CantMoveMessage] = this.i18n('NOTIFICATIONS/CANT_MOVE_MESSAGE'); + oN[Enums.Notification.CantCopyMessage] = this.i18n('NOTIFICATIONS/CANT_MOVE_MESSAGE'); + + oN[Enums.Notification.CantSaveMessage] = this.i18n('NOTIFICATIONS/CANT_SAVE_MESSAGE'); + oN[Enums.Notification.CantSendMessage] = this.i18n('NOTIFICATIONS/CANT_SEND_MESSAGE'); + oN[Enums.Notification.InvalidRecipients] = this.i18n('NOTIFICATIONS/INVALID_RECIPIENTS'); + + oN[Enums.Notification.CantSaveFilters] = this.i18n('NOTIFICATIONS/CANT_SAVE_FILTERS'); + oN[Enums.Notification.CantGetFilters] = this.i18n('NOTIFICATIONS/CANT_GET_FILTERS'); + oN[Enums.Notification.FiltersAreNotCorrect] = this.i18n('NOTIFICATIONS/FILTERS_ARE_NOT_CORRECT'); + + oN[Enums.Notification.CantCreateFolder] = this.i18n('NOTIFICATIONS/CANT_CREATE_FOLDER'); + oN[Enums.Notification.CantRenameFolder] = this.i18n('NOTIFICATIONS/CANT_RENAME_FOLDER'); + oN[Enums.Notification.CantDeleteFolder] = this.i18n('NOTIFICATIONS/CANT_DELETE_FOLDER'); + oN[Enums.Notification.CantDeleteNonEmptyFolder] = this.i18n('NOTIFICATIONS/CANT_DELETE_NON_EMPTY_FOLDER'); + oN[Enums.Notification.CantSubscribeFolder] = this.i18n('NOTIFICATIONS/CANT_SUBSCRIBE_FOLDER'); + oN[Enums.Notification.CantUnsubscribeFolder] = this.i18n('NOTIFICATIONS/CANT_UNSUBSCRIBE_FOLDER'); + + oN[Enums.Notification.CantSaveSettings] = this.i18n('NOTIFICATIONS/CANT_SAVE_SETTINGS'); + oN[Enums.Notification.CantSavePluginSettings] = this.i18n('NOTIFICATIONS/CANT_SAVE_PLUGIN_SETTINGS'); + + oN[Enums.Notification.DomainAlreadyExists] = this.i18n('NOTIFICATIONS/DOMAIN_ALREADY_EXISTS'); + + oN[Enums.Notification.CantInstallPackage] = this.i18n('NOTIFICATIONS/CANT_INSTALL_PACKAGE'); + oN[Enums.Notification.CantDeletePackage] = this.i18n('NOTIFICATIONS/CANT_DELETE_PACKAGE'); + oN[Enums.Notification.InvalidPluginPackage] = this.i18n('NOTIFICATIONS/INVALID_PLUGIN_PACKAGE'); + oN[Enums.Notification.UnsupportedPluginPackage] = this.i18n('NOTIFICATIONS/UNSUPPORTED_PLUGIN_PACKAGE'); + + oN[Enums.Notification.LicensingServerIsUnavailable] = this.i18n('NOTIFICATIONS/LICENSING_SERVER_IS_UNAVAILABLE'); + oN[Enums.Notification.LicensingExpired] = this.i18n('NOTIFICATIONS/LICENSING_EXPIRED'); + oN[Enums.Notification.LicensingBanned] = this.i18n('NOTIFICATIONS/LICENSING_BANNED'); + + oN[Enums.Notification.DemoSendMessageError] = this.i18n('NOTIFICATIONS/DEMO_SEND_MESSAGE_ERROR'); + oN[Enums.Notification.DemoAccountError] = this.i18n('NOTIFICATIONS/DEMO_ACCOUNT_ERROR'); + + oN[Enums.Notification.AccountAlreadyExists] = this.i18n('NOTIFICATIONS/ACCOUNT_ALREADY_EXISTS'); + oN[Enums.Notification.AccountDoesNotExist] = this.i18n('NOTIFICATIONS/ACCOUNT_DOES_NOT_EXIST'); + + oN[Enums.Notification.MailServerError] = this.i18n('NOTIFICATIONS/MAIL_SERVER_ERROR'); + oN[Enums.Notification.InvalidInputArgument] = this.i18n('NOTIFICATIONS/INVALID_INPUT_ARGUMENT'); + oN[Enums.Notification.UnknownNotification] = this.i18n('NOTIFICATIONS/UNKNOWN_ERROR'); + oN[Enums.Notification.UnknownError] = this.i18n('NOTIFICATIONS/UNKNOWN_ERROR'); + }; + + /** + * @param {Function} fCallback + * @param {Object} oScope + * @param {Function=} fLangCallback + */ + Translator.prototype.initOnStartOrLangChange = function (fCallback, oScope, fLangCallback) + { + if (fCallback) + { + fCallback.call(oScope); + } + + if (fLangCallback) + { + this.trigger.subscribe(function () { + if (fCallback) + { + fCallback.call(oScope); + } + + fLangCallback.call(oScope); + }); + } + else if (fCallback) + { + this.trigger.subscribe(fCallback, oScope); + } + }; + + /** + * @param {number} iCode + * @param {*=} mMessage = '' + * @param {*=} mDefCode = null + * @return {string} + */ + Translator.prototype.getNotification = function (iCode, mMessage, mDefCode) + { + iCode = window.parseInt(iCode, 10) || 0; + if (Enums.Notification.ClientViewError === iCode && mMessage) + { + return mMessage; + } + + return _.isUndefined(this.notificationI18N[iCode]) ? ( + mDefCode && _.isUndefined(this.notificationI18N[mDefCode]) ? this.notificationI18N[mDefCode] : '' + ) : this.notificationI18N[iCode]; + }; + + /** + * @param {*} mCode + * @return {string} + */ + Translator.prototype.getUploadErrorDescByCode = function (mCode) + { + var sResult = ''; + switch (window.parseInt(mCode, 10) || 0) { + case Enums.UploadErrorCode.FileIsTooBig: + sResult = this.i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG'); + break; + case Enums.UploadErrorCode.FilePartiallyUploaded: + sResult = this.i18n('UPLOAD/ERROR_FILE_PARTIALLY_UPLOADED'); + break; + case Enums.UploadErrorCode.FileNoUploaded: + sResult = this.i18n('UPLOAD/ERROR_NO_FILE_UPLOADED'); + break; + case Enums.UploadErrorCode.MissingTempFolder: + sResult = this.i18n('UPLOAD/ERROR_MISSING_TEMP_FOLDER'); + break; + case Enums.UploadErrorCode.FileOnSaveingError: + sResult = this.i18n('UPLOAD/ERROR_ON_SAVING_FILE'); + break; + case Enums.UploadErrorCode.FileType: + sResult = this.i18n('UPLOAD/ERROR_FILE_TYPE'); + break; + default: + sResult = this.i18n('UPLOAD/ERROR_UNKNOWN'); + break; + } + + return sResult; + }; + + /** + * @param {boolean} bAdmin + * @param {string} sLanguage + * @param {Function=} fDone + * @param {Function=} fFail + */ + Translator.prototype.reload = function (bAdmin, sLanguage, fDone, fFail) + { + var + self = this, + $html = $('html'), + fEmptyFunction = function () {}, + iStart = (new Date()).getTime() + ; + + $html.addClass('rl-changing-language'); + + $.ajax({ + 'url': require('Common/Links').langLink(sLanguage, bAdmin), + 'dataType': 'script', + 'cache': true + }) + .fail(fFail || fEmptyFunction) + .done(function () { + _.delay(function () { + self.reloadData(); + (fDone || fEmptyFunction)(); + $html.removeClass('rl-changing-language'); + }, 500 < (new Date()).getTime() - iStart ? 1 : 500); + }) + ; + }; + + module.exports = new Translator(); + +}()); diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js index b7a5a32d8..daf84d133 100644 --- a/dev/Common/Utils.js +++ b/dev/Common/Utils.js @@ -15,14 +15,16 @@ Autolinker = require('Autolinker'), JSEncrypt = require('JSEncrypt'), + Mime = require('Common/Mime'), + Enums = require('Common/Enums'), - Consts = require('Common/Consts'), Globals = require('Common/Globals') ; Utils.trim = $.trim; Utils.inArray = $.inArray; Utils.isArray = _.isArray; + Utils.isObject = _.isObject; Utils.isFunc = _.isFunction; Utils.isUnd = _.isUndefined; Utils.isNull = _.isNull; @@ -50,6 +52,10 @@ } }, 50); + Utils.windowResizeCallback = function () { + Utils.windowResize(); + }; + /** * @param {(string|number)} mValue * @param {boolean=} bIncludeZero @@ -84,6 +90,15 @@ return Utils.isNormal(mValue) ? '' + mValue : ''; }; + /** + * @param {*} mValue + * @return {boolean} + */ + Utils.pBool = function (mValue) + { + return !!mValue; + }; + /** * @param {string} sComponent * @return {string} @@ -102,14 +117,6 @@ return Utils.isArray(aValue) && 0 < aValue.length; }; - /** - * @return {*|null} - */ - Utils.notificationClass = function () - { - return window.Notification && window.Notification.requestPermission ? window.Notification : null; - }; - /** * @param {string} sQueryString * @return {Object} @@ -137,7 +144,7 @@ /** * @param {string} sMailToUrl * @param {Function} PopupComposeVoreModel - * @returns {boolean} + * @return {boolean} */ Utils.mailToHelper = function (sMailToUrl, PopupComposeVoreModel) { @@ -146,26 +153,44 @@ sMailToUrl = sMailToUrl.toString().substr(7); var + aTo = [], + aCc = null, + aBcc = null, oParams = {}, - oEmailModel = null, + EmailModel = require('Model/Email'), sEmail = sMailToUrl.replace(/\?.+$/, ''), sQueryString = sMailToUrl.replace(/^[^\?]*\?/, ''), - EmailModel = require('Model/Email') + fParseEmailLine = function (sLine) { + return sLine ? _.compact(_.map(window.decodeURIComponent(sLine).split(/[,]/), function (sItem) { + var oEmailModel = new EmailModel(); + oEmailModel.mailsoParse(sItem); + return '' !== oEmailModel.email ? oEmailModel : null; + })) : null; + } ; - oEmailModel = new EmailModel(); - oEmailModel.parse(window.decodeURIComponent(sEmail)); + aTo = fParseEmailLine(sEmail); - if (oEmailModel && oEmailModel.email) + oParams = Utils.simpleQueryParser(sQueryString); + + if (!Utils.isUnd(oParams.cc)) { - oParams = Utils.simpleQueryParser(sQueryString); - - require('Knoin/Knoin').showScreenPopup(PopupComposeVoreModel, [Enums.ComposeType.Empty, null, [oEmailModel], - Utils.isUnd(oParams.subject) ? null : Utils.pString(oParams.subject), - Utils.isUnd(oParams.body) ? null : Utils.plainToHtml(Utils.pString(oParams.body)) - ]); + aCc = fParseEmailLine(window.decodeURIComponent(oParams.cc)); } + if (!Utils.isUnd(oParams.bcc)) + { + aBcc = fParseEmailLine(window.decodeURIComponent(oParams.bcc)); + } + + require('Knoin/Knoin').showScreenPopup(PopupComposeVoreModel, [Enums.ComposeType.Empty, null, + aTo, aCc, aBcc, + Utils.isUnd(oParams.subject) ? null : + Utils.pString(window.decodeURIComponent(oParams.subject)), + Utils.isUnd(oParams.body) ? null : + Utils.plainToHtml(Utils.pString(window.decodeURIComponent(oParams.body))) + ]); + return true; } @@ -220,7 +245,7 @@ return sValue; }; - Utils.rsaEncode.supported = !!(window.crypto && window.crypto.getRandomValues && JSEncrypt); + Utils.rsaEncode.supported = !!(window.crypto && window.crypto.getRandomValues && false && JSEncrypt); /** * @param {string} sText @@ -228,9 +253,7 @@ */ Utils.encodeHtml = function (sText) { - return Utils.isNormal(sText) ? sText.toString() - .replace(/&/g, '&').replace(//g, '>') - .replace(/"/g, '"').replace(/'/g, ''') : ''; + return Utils.isNormal(sText) ? _.escape(sText.toString()) : ''; }; /** @@ -309,61 +332,6 @@ }; }()); - Utils.audio = (function () { - - var - oAudio = false - ; - - return function (sMp3File, sOggFile) { - - if (false === oAudio) - { - if (Globals.bIsiOSDevice) - { - oAudio = null; - } - else - { - var - bCanPlayMp3 = false, - bCanPlayOgg = false, - oAudioLocal = window.Audio ? new window.Audio() : null - ; - - if (oAudioLocal && oAudioLocal.canPlayType && oAudioLocal.play) - { - bCanPlayMp3 = '' !== oAudioLocal.canPlayType('audio/mpeg; codecs="mp3"'); - if (!bCanPlayMp3) - { - bCanPlayOgg = '' !== oAudioLocal.canPlayType('audio/ogg; codecs="vorbis"'); - } - - if (bCanPlayMp3 || bCanPlayOgg) - { - oAudio = oAudioLocal; - oAudio.preload = 'none'; - oAudio.loop = false; - oAudio.autoplay = false; - oAudio.muted = false; - oAudio.src = bCanPlayMp3 ? sMp3File : sOggFile; - } - else - { - oAudio = null; - } - } - else - { - oAudio = null; - } - } - } - - return oAudio; - }; - }()); - /** * @param {(Object|null|undefined)} oObject * @param {string} sProp @@ -374,117 +342,6 @@ return oObject && window.Object && window.Object.hasOwnProperty ? window.Object.hasOwnProperty.call(oObject, sProp) : false; }; - /** - * @param {string} sKey - * @param {Object=} oValueList - * @param {string=} sDefaulValue - * @return {string} - */ - Utils.i18n = function (sKey, oValueList, sDefaulValue) - { - var - sValueName = '', - sResult = Utils.isUnd(Globals.oI18N[sKey]) ? (Utils.isUnd(sDefaulValue) ? sKey : sDefaulValue) : Globals.oI18N[sKey] - ; - - if (!Utils.isUnd(oValueList) && !Utils.isNull(oValueList)) - { - for (sValueName in oValueList) - { - if (Utils.hos(oValueList, sValueName)) - { - sResult = sResult.replace('%' + sValueName + '%', oValueList[sValueName]); - } - } - } - - return sResult; - }; - - /** - * @param {Object} oElement - */ - Utils.i18nToNode = function (oElement) - { - _.defer(function () { - $('.i18n', oElement).each(function () { - var - jqThis = $(this), - sKey = '' - ; - - sKey = jqThis.data('i18n-text'); - if (sKey) - { - jqThis.text(Utils.i18n(sKey)); - } - else - { - sKey = jqThis.data('i18n-html'); - if (sKey) - { - jqThis.html(Utils.i18n(sKey)); - } - - sKey = jqThis.data('i18n-placeholder'); - if (sKey) - { - jqThis.attr('placeholder', Utils.i18n(sKey)); - } - - sKey = jqThis.data('i18n-title'); - if (sKey) - { - jqThis.attr('title', Utils.i18n(sKey)); - } - } - }); - }); - }; - - Utils.i18nReload = function () - { - if (window['rainloopI18N']) - { - Globals.oI18N = window['rainloopI18N'] || {}; - - Utils.i18nToNode(Globals.$doc); - - Globals.langChangeTrigger(!Globals.langChangeTrigger()); - } - - window['rainloopI18N'] = null; - }; - - /** - * @param {Function} fCallback - * @param {Object} oScope - * @param {Function=} fLangCallback - */ - Utils.initOnStartOrLangChange = function (fCallback, oScope, fLangCallback) - { - if (fCallback) - { - fCallback.call(oScope); - } - - if (fLangCallback) - { - Globals.langChangeTrigger.subscribe(function () { - if (fCallback) - { - fCallback.call(oScope); - } - - fLangCallback.call(oScope); - }); - } - else if (fCallback) - { - Globals.langChangeTrigger.subscribe(fCallback, oScope); - } - }; - /** * @return {boolean} */ @@ -635,122 +492,6 @@ } }; - /** - * @param {number} iCode - * @param {*=} mMessage = '' - * @return {string} - */ - Utils.getNotification = function (iCode, mMessage) - { - iCode = Utils.pInt(iCode); - if (Enums.Notification.ClientViewError === iCode && mMessage) - { - return mMessage; - } - - return Utils.isUnd(Globals.oNotificationI18N[iCode]) ? '' : Globals.oNotificationI18N[iCode]; - }; - - Utils.initNotificationLanguage = function () - { - var oN = Globals.oNotificationI18N || {}; - oN[Enums.Notification.InvalidToken] = Utils.i18n('NOTIFICATIONS/INVALID_TOKEN'); - oN[Enums.Notification.AuthError] = Utils.i18n('NOTIFICATIONS/AUTH_ERROR'); - oN[Enums.Notification.AccessError] = Utils.i18n('NOTIFICATIONS/ACCESS_ERROR'); - oN[Enums.Notification.ConnectionError] = Utils.i18n('NOTIFICATIONS/CONNECTION_ERROR'); - oN[Enums.Notification.CaptchaError] = Utils.i18n('NOTIFICATIONS/CAPTCHA_ERROR'); - oN[Enums.Notification.SocialFacebookLoginAccessDisable] = Utils.i18n('NOTIFICATIONS/SOCIAL_FACEBOOK_LOGIN_ACCESS_DISABLE'); - oN[Enums.Notification.SocialTwitterLoginAccessDisable] = Utils.i18n('NOTIFICATIONS/SOCIAL_TWITTER_LOGIN_ACCESS_DISABLE'); - oN[Enums.Notification.SocialGoogleLoginAccessDisable] = Utils.i18n('NOTIFICATIONS/SOCIAL_GOOGLE_LOGIN_ACCESS_DISABLE'); - oN[Enums.Notification.DomainNotAllowed] = Utils.i18n('NOTIFICATIONS/DOMAIN_NOT_ALLOWED'); - oN[Enums.Notification.AccountNotAllowed] = Utils.i18n('NOTIFICATIONS/ACCOUNT_NOT_ALLOWED'); - - oN[Enums.Notification.AccountTwoFactorAuthRequired] = Utils.i18n('NOTIFICATIONS/ACCOUNT_TWO_FACTOR_AUTH_REQUIRED'); - oN[Enums.Notification.AccountTwoFactorAuthError] = Utils.i18n('NOTIFICATIONS/ACCOUNT_TWO_FACTOR_AUTH_ERROR'); - - oN[Enums.Notification.CouldNotSaveNewPassword] = Utils.i18n('NOTIFICATIONS/COULD_NOT_SAVE_NEW_PASSWORD'); - oN[Enums.Notification.CurrentPasswordIncorrect] = Utils.i18n('NOTIFICATIONS/CURRENT_PASSWORD_INCORRECT'); - oN[Enums.Notification.NewPasswordShort] = Utils.i18n('NOTIFICATIONS/NEW_PASSWORD_SHORT'); - oN[Enums.Notification.NewPasswordWeak] = Utils.i18n('NOTIFICATIONS/NEW_PASSWORD_WEAK'); - oN[Enums.Notification.NewPasswordForbidden] = Utils.i18n('NOTIFICATIONS/NEW_PASSWORD_FORBIDDENT'); - - oN[Enums.Notification.ContactsSyncError] = Utils.i18n('NOTIFICATIONS/CONTACTS_SYNC_ERROR'); - - oN[Enums.Notification.CantGetMessageList] = Utils.i18n('NOTIFICATIONS/CANT_GET_MESSAGE_LIST'); - oN[Enums.Notification.CantGetMessage] = Utils.i18n('NOTIFICATIONS/CANT_GET_MESSAGE'); - oN[Enums.Notification.CantDeleteMessage] = Utils.i18n('NOTIFICATIONS/CANT_DELETE_MESSAGE'); - oN[Enums.Notification.CantMoveMessage] = Utils.i18n('NOTIFICATIONS/CANT_MOVE_MESSAGE'); - oN[Enums.Notification.CantCopyMessage] = Utils.i18n('NOTIFICATIONS/CANT_MOVE_MESSAGE'); - - oN[Enums.Notification.CantSaveMessage] = Utils.i18n('NOTIFICATIONS/CANT_SAVE_MESSAGE'); - oN[Enums.Notification.CantSendMessage] = Utils.i18n('NOTIFICATIONS/CANT_SEND_MESSAGE'); - oN[Enums.Notification.InvalidRecipients] = Utils.i18n('NOTIFICATIONS/INVALID_RECIPIENTS'); - - oN[Enums.Notification.CantCreateFolder] = Utils.i18n('NOTIFICATIONS/CANT_CREATE_FOLDER'); - oN[Enums.Notification.CantRenameFolder] = Utils.i18n('NOTIFICATIONS/CANT_RENAME_FOLDER'); - oN[Enums.Notification.CantDeleteFolder] = Utils.i18n('NOTIFICATIONS/CANT_DELETE_FOLDER'); - oN[Enums.Notification.CantDeleteNonEmptyFolder] = Utils.i18n('NOTIFICATIONS/CANT_DELETE_NON_EMPTY_FOLDER'); - oN[Enums.Notification.CantSubscribeFolder] = Utils.i18n('NOTIFICATIONS/CANT_SUBSCRIBE_FOLDER'); - oN[Enums.Notification.CantUnsubscribeFolder] = Utils.i18n('NOTIFICATIONS/CANT_UNSUBSCRIBE_FOLDER'); - - oN[Enums.Notification.CantSaveSettings] = Utils.i18n('NOTIFICATIONS/CANT_SAVE_SETTINGS'); - oN[Enums.Notification.CantSavePluginSettings] = Utils.i18n('NOTIFICATIONS/CANT_SAVE_PLUGIN_SETTINGS'); - - oN[Enums.Notification.DomainAlreadyExists] = Utils.i18n('NOTIFICATIONS/DOMAIN_ALREADY_EXISTS'); - - oN[Enums.Notification.CantInstallPackage] = Utils.i18n('NOTIFICATIONS/CANT_INSTALL_PACKAGE'); - oN[Enums.Notification.CantDeletePackage] = Utils.i18n('NOTIFICATIONS/CANT_DELETE_PACKAGE'); - oN[Enums.Notification.InvalidPluginPackage] = Utils.i18n('NOTIFICATIONS/INVALID_PLUGIN_PACKAGE'); - oN[Enums.Notification.UnsupportedPluginPackage] = Utils.i18n('NOTIFICATIONS/UNSUPPORTED_PLUGIN_PACKAGE'); - - oN[Enums.Notification.LicensingServerIsUnavailable] = Utils.i18n('NOTIFICATIONS/LICENSING_SERVER_IS_UNAVAILABLE'); - oN[Enums.Notification.LicensingExpired] = Utils.i18n('NOTIFICATIONS/LICENSING_EXPIRED'); - oN[Enums.Notification.LicensingBanned] = Utils.i18n('NOTIFICATIONS/LICENSING_BANNED'); - - oN[Enums.Notification.DemoSendMessageError] = Utils.i18n('NOTIFICATIONS/DEMO_SEND_MESSAGE_ERROR'); - - oN[Enums.Notification.AccountAlreadyExists] = Utils.i18n('NOTIFICATIONS/ACCOUNT_ALREADY_EXISTS'); - - oN[Enums.Notification.MailServerError] = Utils.i18n('NOTIFICATIONS/MAIL_SERVER_ERROR'); - oN[Enums.Notification.InvalidInputArgument] = Utils.i18n('NOTIFICATIONS/INVALID_INPUT_ARGUMENT'); - oN[Enums.Notification.UnknownNotification] = Utils.i18n('NOTIFICATIONS/UNKNOWN_ERROR'); - oN[Enums.Notification.UnknownError] = Utils.i18n('NOTIFICATIONS/UNKNOWN_ERROR'); - }; - - /** - * @param {*} mCode - * @return {string} - */ - Utils.getUploadErrorDescByCode = function (mCode) - { - var sResult = ''; - switch (Utils.pInt(mCode)) { - case Enums.UploadErrorCode.FileIsTooBig: - sResult = Utils.i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG'); - break; - case Enums.UploadErrorCode.FilePartiallyUploaded: - sResult = Utils.i18n('UPLOAD/ERROR_FILE_PARTIALLY_UPLOADED'); - break; - case Enums.UploadErrorCode.FileNoUploaded: - sResult = Utils.i18n('UPLOAD/ERROR_NO_FILE_UPLOADED'); - break; - case Enums.UploadErrorCode.MissingTempFolder: - sResult = Utils.i18n('UPLOAD/ERROR_MISSING_TEMP_FOLDER'); - break; - case Enums.UploadErrorCode.FileOnSaveingError: - sResult = Utils.i18n('UPLOAD/ERROR_ON_SAVING_FILE'); - break; - case Enums.UploadErrorCode.FileType: - sResult = Utils.i18n('UPLOAD/ERROR_FILE_TYPE'); - break; - default: - sResult = Utils.i18n('UPLOAD/ERROR_UNKNOWN'); - break; - } - - return sResult; - }; - /** * @param {?} oObject * @param {string} sMethodName @@ -778,7 +519,7 @@ /** * @param {?} oEvent */ - Utils.killCtrlAandS = function (oEvent) + Utils.kill_CtrlA_CtrlS = function (oEvent) { oEvent = oEvent || window.event; if (oEvent && oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey) @@ -853,313 +594,11 @@ return fResult; }; - /** - * @param {Object} oData - */ - Utils.initDataConstructorBySettings = function (oData) - { - oData.editorDefaultType = ko.observable(Enums.EditorDefaultType.Html); - oData.showImages = ko.observable(false); - oData.interfaceAnimation = ko.observable(Enums.InterfaceAnimation.Full); - oData.contactsAutosave = ko.observable(false); - - Globals.sAnimationType = Enums.InterfaceAnimation.Full; - - oData.capaThemes = ko.observable(false); - oData.allowLanguagesOnSettings = ko.observable(true); - oData.allowLanguagesOnLogin = ko.observable(true); - - oData.useLocalProxyForExternalImages = ko.observable(false); - - oData.desktopNotifications = ko.observable(false); - oData.useThreads = ko.observable(true); - oData.replySameFolder = ko.observable(true); - oData.useCheckboxesInList = ko.observable(true); - - oData.layout = ko.observable(Enums.Layout.SidePreview); - oData.usePreviewPane = ko.computed(function () { - return Enums.Layout.NoPreview !== oData.layout(); - }); - - oData.interfaceAnimation.subscribe(function (sValue) { - if (Globals.bMobileDevice || sValue === Enums.InterfaceAnimation.None) - { - Globals.$html.removeClass('rl-anim rl-anim-full').addClass('no-rl-anim'); - - Globals.sAnimationType = Enums.InterfaceAnimation.None; - } - else - { - switch (sValue) - { - case Enums.InterfaceAnimation.Full: - Globals.$html.removeClass('no-rl-anim').addClass('rl-anim rl-anim-full'); - Globals.sAnimationType = sValue; - break; - case Enums.InterfaceAnimation.Normal: - Globals.$html.removeClass('no-rl-anim rl-anim-full').addClass('rl-anim'); - Globals.sAnimationType = sValue; - break; - } - } - }); - - oData.interfaceAnimation.valueHasMutated(); - - oData.desktopNotificationsPermisions = ko.computed(function () { - - oData.desktopNotifications(); - - var - NotificationClass = Utils.notificationClass(), - iResult = Enums.DesktopNotifications.NotSupported - ; - - if (NotificationClass && NotificationClass.permission) - { - switch (NotificationClass.permission.toLowerCase()) - { - case 'granted': - iResult = Enums.DesktopNotifications.Allowed; - break; - case 'denied': - iResult = Enums.DesktopNotifications.Denied; - break; - case 'default': - iResult = Enums.DesktopNotifications.NotAllowed; - break; - } - } - else if (window.webkitNotifications && window.webkitNotifications.checkPermission) - { - iResult = window.webkitNotifications.checkPermission(); - } - - return iResult; - }); - - oData.useDesktopNotifications = ko.computed({ - 'read': function () { - return oData.desktopNotifications() && - Enums.DesktopNotifications.Allowed === oData.desktopNotificationsPermisions(); - }, - 'write': function (bValue) { - if (bValue) - { - var - NotificationClass = Utils.notificationClass(), - iPermission = oData.desktopNotificationsPermisions() - ; - - if (NotificationClass && Enums.DesktopNotifications.Allowed === iPermission) - { - oData.desktopNotifications(true); - } - else if (NotificationClass && Enums.DesktopNotifications.NotAllowed === iPermission) - { - NotificationClass.requestPermission(function () { - oData.desktopNotifications.valueHasMutated(); - if (Enums.DesktopNotifications.Allowed === oData.desktopNotificationsPermisions()) - { - if (oData.desktopNotifications()) - { - oData.desktopNotifications.valueHasMutated(); - } - else - { - oData.desktopNotifications(true); - } - } - else - { - if (oData.desktopNotifications()) - { - oData.desktopNotifications(false); - } - else - { - oData.desktopNotifications.valueHasMutated(); - } - } - }); - } - else - { - oData.desktopNotifications(false); - } - } - else - { - oData.desktopNotifications(false); - } - } - }); - - oData.language = ko.observable(''); - oData.languages = ko.observableArray([]); - - oData.mainLanguage = ko.computed({ - 'read': oData.language, - 'write': function (sValue) { - if (sValue !== oData.language()) - { - if (-1 < Utils.inArray(sValue, oData.languages())) - { - oData.language(sValue); - } - else if (0 < oData.languages().length) - { - oData.language(oData.languages()[0]); - } - } - else - { - oData.language.valueHasMutated(); - } - } - }); - - oData.theme = ko.observable(''); - oData.themes = ko.observableArray([]); - - oData.mainTheme = ko.computed({ - 'read': oData.theme, - 'write': function (sValue) { - if (sValue !== oData.theme()) - { - var aThemes = oData.themes(); - if (-1 < Utils.inArray(sValue, aThemes)) - { - oData.theme(sValue); - } - else if (0 < aThemes.length) - { - oData.theme(aThemes[0]); - } - } - else - { - oData.theme.valueHasMutated(); - } - } - }); - - oData.capaAdditionalAccounts = ko.observable(false); - oData.capaAdditionalIdentities = ko.observable(false); - oData.capaGravatar = ko.observable(false); - oData.determineUserLanguage = ko.observable(false); - oData.determineUserDomain = ko.observable(false); - - oData.messagesPerPage = ko.observable(Consts.Defaults.MessagesPerPage);//.extend({'throttle': 200}); - - oData.mainMessagesPerPage = oData.messagesPerPage; - oData.mainMessagesPerPage = ko.computed({ - 'read': oData.messagesPerPage, - 'write': function (iValue) { - if (-1 < Utils.inArray(Utils.pInt(iValue), Consts.Defaults.MessagesPerPageArray)) - { - if (iValue !== oData.messagesPerPage()) - { - oData.messagesPerPage(iValue); - } - } - else - { - oData.messagesPerPage.valueHasMutated(); - } - } - }); - - oData.facebookSupported = ko.observable(false); - oData.facebookEnable = ko.observable(false); - oData.facebookAppID = ko.observable(''); - oData.facebookAppSecret = ko.observable(''); - - oData.twitterEnable = ko.observable(false); - oData.twitterConsumerKey = ko.observable(''); - oData.twitterConsumerSecret = ko.observable(''); - - oData.googleEnable = ko.observable(false); - oData.googleClientID = ko.observable(''); - oData.googleClientSecret = ko.observable(''); - oData.googleApiKey = ko.observable(''); - - oData.dropboxEnable = ko.observable(false); - oData.dropboxApiKey = ko.observable(''); - - oData.contactsIsAllowed = ko.observable(false); - }; - - /** - * @param {{moment:Function}} oObject - */ - Utils.createMomentDate = function (oObject) - { - if (Utils.isUnd(oObject.moment)) - { - oObject.moment = ko.observable(moment()); - } - - return ko.computed(function () { - Globals.momentTrigger(); - var oMoment = this.moment(); - return 1970 === oMoment.year() ? '' : oMoment.fromNow(); - }, oObject); - }; - - /** - * @param {{moment:Function, momentDate:Function}} oObject - */ - Utils.createMomentShortDate = function (oObject) - { - return ko.computed(function () { - - var - sResult = '', - oMomentNow = moment(), - oMoment = this.moment(), - sMomentDate = this.momentDate() - ; - - if (1970 === oMoment.year()) - { - sResult = ''; - } - else if (4 >= oMomentNow.diff(oMoment, 'hours')) - { - sResult = sMomentDate; - } - else if (oMomentNow.format('L') === oMoment.format('L')) - { - sResult = Utils.i18n('MESSAGE_LIST/TODAY_AT', { - 'TIME': oMoment.format('LT') - }); - } - else if (oMomentNow.clone().subtract('days', 1).format('L') === oMoment.format('L')) - { - sResult = Utils.i18n('MESSAGE_LIST/YESTERDAY_AT', { - 'TIME': oMoment.format('LT') - }); - } - else if (oMomentNow.year() === oMoment.year()) - { - sResult = oMoment.format('D MMM.'); - } - else - { - sResult = oMoment.format('LL'); - } - - return sResult; - - }, oObject); - }; - /** * @param {string} sTheme * @return {string} */ - Utils.convertThemeName = function (sTheme) + Utils.convertThemeName = _.memoize(function (sTheme) { if ('@custom' === sTheme.substr(-7)) { @@ -1167,7 +606,7 @@ } return Utils.trim(sTheme.replace(/[^a-zA-Z0-9]+/g, ' ').replace(/([A-Z])/g, ' $1').replace(/[\s]+/g, ' ')); - }; + }); /** * @param {string} sName @@ -1183,7 +622,7 @@ */ Utils.microtime = function () { - return (new Date()).getTime(); + return (new window.Date()).getTime(); }; /** @@ -1202,7 +641,7 @@ */ Utils.convertLangName = function (sLanguage, bEng) { - return Utils.i18n('LANGS_NAMES' + (true === bEng ? '_EN' : '') + '/LANG_' + + return require('Common/Translator').i18n('LANGS_NAMES' + (true === bEng ? '_EN' : '') + '/LANG_' + sLanguage.toUpperCase().replace(/[^a-zA-Z0-9]+/g, '_'), null, sLanguage); }; @@ -1227,20 +666,11 @@ return sResult; }; - /** - * @param {string} sPlain - * @return {string} - */ - Utils.convertPlainTextToHtml = function (sPlain) + Utils.draggablePlace = function () { - return sPlain.toString() - .replace(/&/g, '&').replace(/>/g, '>').replace(/'); - }; - - Utils.draggeblePlace = function () - { - return $('
 
').appendTo('#rl-hidden'); + return $('
' + + ' ' + + '
').appendTo('#rl-hidden'); }; Utils.defautOptionsAfterRender = function (oDomOption, oItem) @@ -1278,7 +708,7 @@ $('#rl-content', oBody).html(oTemplate.html()); $('html', oWin.document).addClass('external ' + $('html').attr('class')); - Utils.i18nToNode(oBody); + require('Common/Translator').i18nToNodes(oBody); if (oViewModel && $('#rl-content', oBody)[0]) { @@ -1337,6 +767,46 @@ return Utils.settingsSaveHelperFunction(null, koTrigger, oContext, 1000); }; + Utils.settingsSaveHelperSubscribeFunction = function (oRemote, sSettingName, sType, fTriggerFunction) + { + return function (mValue) { + + if (oRemote) + { + switch (sType) + { + default: + mValue = Utils.pString(mValue); + break; + case 'bool': + case 'boolean': + mValue = mValue ? '1' : '0'; + break; + case 'int': + case 'integer': + case 'number': + mValue = Utils.pInt(mValue); + break; + case 'trim': + mValue = Utils.trim(mValue); + break; + } + + var oData = {}; + oData[sSettingName] = mValue; + + if (oRemote.saveAdminConfig) + { + oRemote.saveAdminConfig(fTriggerFunction || null, oData); + } + else if (oRemote.saveSettings) + { + oRemote.saveSettings(fTriggerFunction || null, oData); + } + } + }; + }; + /** * @param {string} sHtml * @return {string} @@ -1352,42 +822,8 @@ sText = '', - splitPlainText = function (sText) - { - var - iLen = 100, - sPrefix = '', - sSubText = '', - sResult = sText, - iSpacePos = 0, - iNewLinePos = 0 - ; - - while (sResult.length > iLen) - { - sSubText = sResult.substring(0, iLen); - iSpacePos = sSubText.lastIndexOf(' '); - iNewLinePos = sSubText.lastIndexOf('\n'); - - if (-1 !== iNewLinePos) - { - iSpacePos = iNewLinePos; - } - - if (-1 === iSpacePos) - { - iSpacePos = iLen; - } - - sPrefix += sSubText.substring(0, iSpacePos) + '\n'; - sResult = sResult.substring(iSpacePos + 1); - } - - return sPrefix + sResult; - }, - convertBlockquote = function (sText) { - sText = splitPlainText($.trim(sText)); + sText = Utils.trim(sText); sText = '> ' + sText.replace(/\n/gm, '\n> '); return sText.replace(/(^|\n)([> ]+)/gm, function () { return (arguments && 2 < arguments.length) ? arguments[1] + $.trim(arguments[2].replace(/[\s]/g, '')) + ' ' : ''; @@ -1416,7 +852,7 @@ fixAttibuteValue = function () { return (arguments && 1 < arguments.length) ? - '' + arguments[1] + arguments[2].replace(//g, '>') : ''; + '' + arguments[1] + _.escape(arguments[2]) : ''; }, convertLinks = function () { @@ -1425,6 +861,10 @@ ; sText = sHtml + // specials for signature + .replace(/\u0002\u0002/g, '\u200C\u200C') + .replace(/\u0003\u0003/g, '\u200D\u200D') + .replace(/]*>([\s\S\r\n]*)<\/pre>/gmi, convertPre) .replace(/[\s]+/gm, ' ') .replace(/((?:href|data)\s?=\s?)("[^"]+?"|'[^']+?')/gmi, fixAttibuteValue) @@ -1455,8 +895,10 @@ .replace(/&/gi, '&') ; + sText = Utils.splitPlainText(Utils.trim(sText)); + iPos = 0; - iLimit = 100; + iLimit = 800; while (0 < iLimit) { @@ -1507,6 +949,8 @@ { sPlain = sPlain.toString().replace(/\r/g, ''); + bFindEmailAndLinks = Utils.isUnd(bFindEmailAndLinks) ? false : !!bFindEmailAndLinks; + var bIn = false, bDo = true, @@ -1568,13 +1012,18 @@ sPlain = aText.join("\n"); sPlain = sPlain + + // specials for signature + .replace(/\u200C\u200C/g, '\u0002\u0002') + .replace(/\u200D\u200D/g, '\u0003\u0003') + // .replace(/~~~\/blockquote~~~\n~~~blockquote~~~/g, '\n') .replace(/&/g, '&') .replace(/>/g, '>').replace(/') .replace(/[\s]*~~~\/blockquote~~~/g, '') - .replace(/[\-_~]{10,}/g, '
') - .replace(/\n/g, '
'); + .replace(/\n/g, '
') + ; return bFindEmailAndLinks ? Utils.findEmailAndLinks(sPlain) : sPlain; }; @@ -1588,24 +1037,18 @@ */ Utils.findEmailAndLinks = function (sHtml) { +// return sHtml; sHtml = Autolinker.link(sHtml, { 'newWindow': true, 'stripPrefix': false, 'urls': true, 'email': true, - 'twitter': false + 'twitter': false, + 'replaceFn': function (autolinker, match) { + return !(autolinker && match && 'url' === match.getType() && match.matchedText && 0 !== match.matchedText.indexOf('http')); + } }); -// if ($.fn && $.fn.linkify) -// { -// sHtml = Globals.$div.html(sHtml.replace(/&/ig, 'amp_amp_12345_amp_amp')) -// .linkify() -// .find('.linkified').removeClass('linkified').end() -// .html() -// .replace(/amp_amp_12345_amp_amp/g, '&') -// ; -// } - return sHtml; }; @@ -1736,11 +1179,12 @@ for (iIndex = 0, iLen = aList.length; iIndex < iLen; iIndex++) { oItem = aList[iIndex]; - if (oItem.subScribed() || !oItem.existen || bBuildUnvisible) +// if (oItem.subScribed() || !oItem.existen || bBuildUnvisible) + if ((oItem.subScribed() || !oItem.existen || bBuildUnvisible) && (oItem.selectable || oItem.hasSubScribedSubfolders())) { if (fVisibleCallback ? fVisibleCallback.call(null, oItem) : true) { - if (Enums.FolderType.User === oItem.type() || !bSystem || 0 < oItem.subFolders().length) + if (Enums.FolderType.User === oItem.type() || !bSystem || oItem.hasSubScribedSubfolders()) { if (bSep && 0 < aResult.length) { @@ -1936,29 +1380,6 @@ } }; - /** - * @param {string} sLanguage - * @param {Function=} fDone - * @param {Function=} fFail - */ - Utils.reloadLanguage = function (sLanguage, fDone, fFail) - { - var iStart = Utils.microtime(); - $.ajax({ - 'url': require('Common/LinkBuilder').langLink(sLanguage), - 'dataType': 'script', - 'cache': true - }) - .fail(fFail || Utils.emptyFunction) - .done(function () { - _.delay(function () { - Utils.i18nReload(); - (fDone || Utils.emptyFunction)(); - }, 500 < Utils.microtime() - iStart ? 1 : 500); - }) - ; - }; - /** * @param {Object} oParams */ @@ -1972,6 +1393,184 @@ $('#rl-head-viewport').attr('content', aContent.join(', ')); }; + /** + * @param {string} sFileName + * @return {string} + */ + Utils.getFileExtension = function (sFileName) + { + sFileName = Utils.trim(sFileName).toLowerCase(); + + var sResult = sFileName.split('.').pop(); + return (sResult === sFileName) ? '' : sResult; + }; + + /** + * @param {string} sFileName + * @return {string} + */ + Utils.mimeContentType = function (sFileName) + { + var + sExt = '', + sResult = 'application/octet-stream' + ; + + sFileName = Utils.trim(sFileName).toLowerCase(); + + if ('winmail.dat' === sFileName) + { + return 'application/ms-tnef'; + } + + sExt = Utils.getFileExtension(sFileName); + if (sExt && 0 < sExt.length && !Utils.isUnd(Mime[sExt])) + { + sResult = Mime[sExt]; + } + + return sResult; + }; + + /** + * @param {mixed} mPropOrValue + * @param {mixed} mValue + */ + Utils.disposeOne = function (mPropOrValue, mValue) + { + var mDisposable = mValue || mPropOrValue; + if (mDisposable && typeof mDisposable.dispose === 'function') + { + mDisposable.dispose(); + } + }; + + /** + * @param {Object} oObject + */ + Utils.disposeObject = function (oObject) + { + if (oObject) + { + if (Utils.isArray(oObject.disposables)) + { + _.each(oObject.disposables, Utils.disposeOne); + } + + ko.utils.objectForEach(oObject, Utils.disposeOne); + } + }; + + /** + * @param {Object|Array} mObjectOrObjects + */ + Utils.delegateRunOnDestroy = function (mObjectOrObjects) + { + if (mObjectOrObjects) + { + if (Utils.isArray(mObjectOrObjects)) + { + _.each(mObjectOrObjects, function (oItem) { + Utils.delegateRunOnDestroy(oItem); + }); + } + else if (mObjectOrObjects && mObjectOrObjects.onDestroy) + { + mObjectOrObjects.onDestroy(); + } + } + }; + + Utils.__themeTimer = 0; + Utils.__themeAjax = null; + + Utils.changeTheme = function (sValue, themeTrigger) + { + var + oThemeLink = $('#rlThemeLink'), + oThemeStyle = $('#rlThemeStyle'), + sUrl = oThemeLink.attr('href') + ; + + if (!sUrl) + { + sUrl = oThemeStyle.attr('data-href'); + } + + if (sUrl) + { + sUrl = sUrl.toString().replace(/\/-\/[^\/]+\/\-\//, '/-/' + sValue + '/-/'); + sUrl = sUrl.toString().replace(/\/Css\/[^\/]+\/User\//, '/Css/0/User/'); + sUrl = sUrl.toString().replace(/\/Hash\/[^\/]+\//, '/Hash/-/'); + + if ('Json/' !== sUrl.substring(sUrl.length - 5, sUrl.length)) + { + sUrl += 'Json/'; + } + + window.clearTimeout(Utils.__themeTimer); + themeTrigger(Enums.SaveSettingsStep.Animate); + + if (Utils.__themeAjax && Utils.__themeAjax.abort) + { + Utils.__themeAjax.abort(); + } + + Utils.__themeAjax = $.ajax({ + 'url': sUrl, + 'dataType': 'json' + }).done(function(aData) { + + if (aData && Utils.isArray(aData) && 2 === aData.length) + { + if (oThemeLink && oThemeLink[0] && (!oThemeStyle || !oThemeStyle[0])) + { + oThemeStyle = $(''); + oThemeLink.after(oThemeStyle); + oThemeLink.remove(); + } + + if (oThemeStyle && oThemeStyle[0]) + { + oThemeStyle.attr('data-href', sUrl).attr('data-theme', aData[0]); + if (oThemeStyle[0].styleSheet && !Utils.isUnd(oThemeStyle[0].styleSheet.cssText)) + { + oThemeStyle[0].styleSheet.cssText = aData[1]; + } + else + { + oThemeStyle.text(aData[1]); + } + } + + themeTrigger(Enums.SaveSettingsStep.TrueResult); + } + + }).always(function() { + + Utils.__themeTimer = window.setTimeout(function () { + themeTrigger(Enums.SaveSettingsStep.Idle); + }, 1000); + + Utils.__themeAjax = null; + }); + } + }; + + Utils.substr = window.String.substr; + if ('ab'.substr(-1) !== 'b') + { + Utils.substr = function(sStr, iStart, iLength) + { + if (iStart < 0) + { + iStart = sStr.length + iStart; + } + + return sStr.substr(iStart, iLength); + }; + } + module.exports = Utils; }()); \ No newline at end of file diff --git a/dev/Component/AbstracCheckbox.js b/dev/Component/AbstracCheckbox.js new file mode 100644 index 000000000..7bb6533b5 --- /dev/null +++ b/dev/Component/AbstracCheckbox.js @@ -0,0 +1,66 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + ko = require('ko'), + + Utils = require('Common/Utils'), + + AbstractComponent = require('Component/Abstract') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstractComponent + */ + function AbstracCheckbox(oParams) + { + AbstractComponent.call(this); + + this.value = oParams.value; + if (Utils.isUnd(this.value) || !this.value.subscribe) + { + this.value = ko.observable(Utils.isUnd(this.value) ? false : !!this.value); + } + + this.enable = oParams.enable; + if (Utils.isUnd(this.enable) || !this.enable.subscribe) + { + this.enable = ko.observable(Utils.isUnd(this.enable) ? true : !!this.enable); + } + + this.disable = oParams.disable; + if (Utils.isUnd(this.disable) || !this.disable.subscribe) + { + this.disable = ko.observable(Utils.isUnd(this.disable) ? false : !!this.disable); + } + + this.label = oParams.label || ''; + this.inline = Utils.isUnd(oParams.inline) ? false : oParams.inline; + + this.readOnly = Utils.isUnd(oParams.readOnly) ? false : !!oParams.readOnly; + this.inverted = Utils.isUnd(oParams.inverted) ? false : !!oParams.inverted; + + this.labeled = !Utils.isUnd(oParams.label); + } + + _.extend(AbstracCheckbox.prototype, AbstractComponent.prototype); + + AbstracCheckbox.prototype.click = function() { + if (!this.readOnly && this.enable() && !this.disable()) + { + this.value(!this.value()); + } + }; + + AbstracCheckbox.componentExportHelper = AbstractComponent.componentExportHelper; + + module.exports = AbstracCheckbox; + +}()); diff --git a/dev/Component/AbstracRadio.js b/dev/Component/AbstracRadio.js new file mode 100644 index 000000000..cbe8eefb8 --- /dev/null +++ b/dev/Component/AbstracRadio.js @@ -0,0 +1,65 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + ko = require('ko'), + + Utils = require('Common/Utils'), + + AbstractComponent = require('Component/Abstract') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstractComponent + */ + function AbstracRadio(oParams) + { + AbstractComponent.call(this); + + this.values = ko.observableArray([]); + + this.value = oParams.value; + if (Utils.isUnd(this.value) || !this.value.subscribe) + { + this.value = ko.observable(''); + } + + this.inline = Utils.isUnd(oParams.inline) ? false : oParams.inline; + this.readOnly = Utils.isUnd(oParams.readOnly) ? false : !!oParams.readOnly; + + if (oParams.values) + { + var aValues = _.map(oParams.values, function (sLabel, sValue) { + return { + 'label': sLabel, + 'value': sValue + }; + }); + + this.values(aValues); + } + + this.click = _.bind(this.click, this); + } + + AbstracRadio.prototype.click = function(oValue) { + if (!this.readOnly && oValue) + { + this.value(oValue.value); + } + }; + + _.extend(AbstracRadio.prototype, AbstractComponent.prototype); + + AbstracRadio.componentExportHelper = AbstractComponent.componentExportHelper; + + module.exports = AbstracRadio; + +}()); diff --git a/dev/Component/Abstract.js b/dev/Component/Abstract.js new file mode 100644 index 000000000..4b5dfb91b --- /dev/null +++ b/dev/Component/Abstract.js @@ -0,0 +1,76 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + ko = require('ko'), + + Utils = require('Common/Utils') + ; + + /** + * @constructor + */ + function AbstractComponent() + { + this.disposable = []; + } + + /** + * @type {Array} + */ + AbstractComponent.prototype.disposable = []; + + AbstractComponent.prototype.dispose = function () + { + _.each(this.disposable, function (fFuncToDispose) { + if (fFuncToDispose && fFuncToDispose.dispose) + { + fFuncToDispose.dispose(); + } + }); + }; + + /** + * @param {*} ClassObject + * @param {string} sTemplateID + * @return {Object} + */ + AbstractComponent.componentExportHelper = function (ClassObject, sTemplateID) { + var oComponent = { + viewModel: { + createViewModel: function(oParams, oComponentInfo) { + + oParams = oParams || {}; + oParams.element = null; + + if (oComponentInfo.element) + { + oParams.component = oComponentInfo; + oParams.element = $(oComponentInfo.element); + + require('Common/Translator').i18nToNodes(oParams.element); + + if (!Utils.isUnd(oParams.inline) && ko.unwrap(oParams.inline)) + { + oParams.element.css('display', 'inline-block'); + } + } + + return new ClassObject(oParams); + } + } + }; + + oComponent['template'] = sTemplateID ? { + element: sTemplateID + } : ''; + + return oComponent; + }; + + module.exports = AbstractComponent; + +}()); diff --git a/dev/Component/AbstractInput.js b/dev/Component/AbstractInput.js new file mode 100644 index 000000000..16e9a82ed --- /dev/null +++ b/dev/Component/AbstractInput.js @@ -0,0 +1,92 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + ko = require('ko'), + + Enums = require('Common/Enums'), + Utils = require('Common/Utils'), + + AbstractComponent = require('Component/Abstract') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstractComponent + */ + function AbstractInput(oParams) + { + AbstractComponent.call(this); + + this.value = oParams.value || ''; + this.size = oParams.size || 0; + this.label = oParams.label || ''; + this.preLabel = oParams.preLabel || ''; + this.enable = Utils.isUnd(oParams.enable) ? true : oParams.enable; + this.trigger = oParams.trigger && oParams.trigger.subscribe ? oParams.trigger : null; + this.placeholder = oParams.placeholder || ''; + + this.labeled = !Utils.isUnd(oParams.label); + this.preLabeled = !Utils.isUnd(oParams.preLabel); + this.triggered = !Utils.isUnd(oParams.trigger) && !!this.trigger; + + this.classForTrigger = ko.observable(''); + + this.className = ko.computed(function () { + + var + iSize = ko.unwrap(this.size), + sSuffixValue = this.trigger ? + ' ' + Utils.trim('settings-saved-trigger-input ' + this.classForTrigger()) : '' + ; + + return (0 < iSize ? 'span' + iSize : '') + sSuffixValue; + + }, this); + + if (!Utils.isUnd(oParams.width) && oParams.element) + { + oParams.element.find('input,select,textarea').css('width', oParams.width); + } + + this.disposable.push(this.className); + + if (this.trigger) + { + this.setTriggerState(this.trigger()); + + this.disposable.push( + this.trigger.subscribe(this.setTriggerState, this) + ); + } + } + + AbstractInput.prototype.setTriggerState = function (nValue) + { + switch (Utils.pInt(nValue)) + { + case Enums.SaveSettingsStep.TrueResult: + this.classForTrigger('success'); + break; + case Enums.SaveSettingsStep.FalseResult: + this.classForTrigger('error'); + break; + default: + this.classForTrigger(''); + break; + } + }; + + _.extend(AbstractInput.prototype, AbstractComponent.prototype); + + AbstractInput.componentExportHelper = AbstractComponent.componentExportHelper; + + module.exports = AbstractInput; + +}()); diff --git a/dev/Component/Checkbox.js b/dev/Component/Checkbox.js new file mode 100644 index 000000000..5e1b6e9db --- /dev/null +++ b/dev/Component/Checkbox.js @@ -0,0 +1,29 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + AbstracCheckbox = require('Component/AbstracCheckbox') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstracCheckbox + */ + function CheckboxComponent(oParams) + { + AbstracCheckbox.call(this, oParams); + } + + _.extend(CheckboxComponent.prototype, AbstracCheckbox.prototype); + + module.exports = AbstracCheckbox.componentExportHelper( + CheckboxComponent, 'CheckboxComponent'); + +}()); diff --git a/dev/Component/Classic/Checkbox.js b/dev/Component/Classic/Checkbox.js new file mode 100644 index 000000000..1f9e9ce72 --- /dev/null +++ b/dev/Component/Classic/Checkbox.js @@ -0,0 +1,29 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + AbstracCheckbox = require('Component/AbstracCheckbox') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstracCheckbox + */ + function ClassicCheckboxComponent(oParams) + { + AbstracCheckbox.call(this, oParams); + } + + _.extend(ClassicCheckboxComponent.prototype, AbstracCheckbox.prototype); + + module.exports = AbstracCheckbox.componentExportHelper( + ClassicCheckboxComponent, 'CheckboxClassicComponent'); + +}()); diff --git a/dev/Component/Input.js b/dev/Component/Input.js new file mode 100644 index 000000000..cb144a9f1 --- /dev/null +++ b/dev/Component/Input.js @@ -0,0 +1,29 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + AbstractInput = require('Component/AbstractInput') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstractInput + */ + function InputComponent(oParams) + { + AbstractInput.call(this, oParams); + } + + _.extend(InputComponent.prototype, AbstractInput.prototype); + + module.exports = AbstractInput.componentExportHelper( + InputComponent, 'InputComponent'); + +}()); diff --git a/dev/Component/MaterialDesign/Checkbox.js b/dev/Component/MaterialDesign/Checkbox.js new file mode 100644 index 000000000..fa48c1274 --- /dev/null +++ b/dev/Component/MaterialDesign/Checkbox.js @@ -0,0 +1,66 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + ko = require('ko'), + + AbstracCheckbox = require('Component/AbstracCheckbox') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstracCheckbox + */ + function CheckboxMaterialDesignComponent(oParams) + { + AbstracCheckbox.call(this, oParams); + + this.animationBox = ko.observable(false).extend({'falseTimeout': 200}); + this.animationCheckmark = ko.observable(false).extend({'falseTimeout': 200}); + + this.animationBoxSetTrue = _.bind(this.animationBoxSetTrue, this); + this.animationCheckmarkSetTrue = _.bind(this.animationCheckmarkSetTrue, this); + + this.disposable.push( + this.value.subscribe(function (bValue) { + this.triggerAnimation(bValue); + }, this) + ); + } + + _.extend(CheckboxMaterialDesignComponent.prototype, AbstracCheckbox.prototype); + + CheckboxMaterialDesignComponent.prototype.animationBoxSetTrue = function() + { + this.animationBox(true); + }; + + CheckboxMaterialDesignComponent.prototype.animationCheckmarkSetTrue = function() + { + this.animationCheckmark(true); + }; + + CheckboxMaterialDesignComponent.prototype.triggerAnimation = function(bBox) + { + if (bBox) + { + this.animationBoxSetTrue(); + _.delay(this.animationCheckmarkSetTrue, 200); + } + else + { + this.animationCheckmarkSetTrue(); + _.delay(this.animationBoxSetTrue, 200); + } + }; + + module.exports = AbstracCheckbox.componentExportHelper( + CheckboxMaterialDesignComponent, 'CheckboxMaterialDesignComponent'); + +}()); diff --git a/dev/Component/Radio.js b/dev/Component/Radio.js new file mode 100644 index 000000000..36fdb9200 --- /dev/null +++ b/dev/Component/Radio.js @@ -0,0 +1,29 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + AbstracRadio = require('Component/AbstracRadio') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstracRadio + */ + function RadioComponent(oParams) + { + AbstracRadio.call(this, oParams); + } + + _.extend(RadioComponent.prototype, AbstracRadio.prototype); + + module.exports = AbstracRadio.componentExportHelper( + RadioComponent, 'RadioComponent'); + +}()); diff --git a/dev/Component/SaveTrigger.js b/dev/Component/SaveTrigger.js new file mode 100644 index 000000000..6a7a1edf9 --- /dev/null +++ b/dev/Component/SaveTrigger.js @@ -0,0 +1,94 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + Enums = require('Common/Enums'), + Utils = require('Common/Utils'), + + AbstractComponent = require('Component/Abstract') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstractComponent + */ + function SaveTriggerComponent(oParams) + { + AbstractComponent.call(this); + + this.element = oParams.element || null; + this.value = oParams.value && oParams.value.subscribe ? oParams.value : null; + + if (this.element) + { + if (this.value) + { + this.element.css('display', 'inline-block'); + + if (oParams.verticalAlign) + { + this.element.css('vertical-align', oParams.verticalAlign); + } + + this.setState(this.value()); + + this.disposable.push( + this.value.subscribe(this.setState, this) + ); + } + else + { + this.element.hide(); + } + } + } + + SaveTriggerComponent.prototype.setState = function (nValue) + { + switch (Utils.pInt(nValue)) + { + case Enums.SaveSettingsStep.TrueResult: + this.element + .find('.animated,.error').hide().removeClass('visible') + .end() + .find('.success').show().addClass('visible') + ; + break; + case Enums.SaveSettingsStep.FalseResult: + this.element + .find('.animated,.success').hide().removeClass('visible') + .end() + .find('.error').show().addClass('visible') + ; + break; + case Enums.SaveSettingsStep.Animate: + this.element + .find('.error,.success').hide().removeClass('visible') + .end() + .find('.animated').show().addClass('visible') + ; + break; + default: + case Enums.SaveSettingsStep.Idle: + this.element + .find('.animated').hide() + .end() + .find('.error,.success').removeClass('visible') + ; + break; + } + }; + + _.extend(SaveTriggerComponent.prototype, AbstractComponent.prototype); + + module.exports = AbstractComponent.componentExportHelper( + SaveTriggerComponent, 'SaveTriggerComponent'); + +}()); diff --git a/dev/Component/Script.js b/dev/Component/Script.js new file mode 100644 index 000000000..9a1b01d33 --- /dev/null +++ b/dev/Component/Script.js @@ -0,0 +1,52 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + $ = require('$'), + + AbstractComponent = require('Component/Abstract') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstractComponent + */ + function ScriptComponent(oParams) + { + AbstractComponent.call(this); + + if (oParams.component && oParams.component.templateNodes && oParams.element && + oParams.element[0] && oParams.element[0].outerHTML) + { + var sScript = oParams.element[0].outerHTML; + sScript = sScript + .replace(/<\/b><\/x-script>/i, '') + ; + + if (sScript) + { + oParams.element.text(''); + oParams.element.replaceWith( + $(sScript).text(oParams.component.templateNodes[0] && + oParams.component.templateNodes[0].nodeValue ? + oParams.component.templateNodes[0].nodeValue : '')); + } + else + { + oParams.element.remove(); + } + } + } + + _.extend(ScriptComponent.prototype, AbstractComponent.prototype); + + module.exports = AbstractComponent.componentExportHelper(ScriptComponent); + +}()); diff --git a/dev/Component/Select.js b/dev/Component/Select.js new file mode 100644 index 000000000..a987e7669 --- /dev/null +++ b/dev/Component/Select.js @@ -0,0 +1,38 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + Utils = require('Common/Utils'), + + AbstractInput = require('Component/AbstractInput') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstractInput + */ + function SelectComponent(oParams) + { + AbstractInput.call(this, oParams); + + this.options = oParams.options || ''; + + this.optionsText = oParams.optionsText || null; + this.optionsValue = oParams.optionsValue || null; + + this.defautOptionsAfterRender = Utils.defautOptionsAfterRender; + } + + _.extend(SelectComponent.prototype, AbstractInput.prototype); + + module.exports = AbstractInput.componentExportHelper( + SelectComponent, 'SelectComponent'); + +}()); diff --git a/dev/Component/TextArea.js b/dev/Component/TextArea.js new file mode 100644 index 000000000..ed86289e2 --- /dev/null +++ b/dev/Component/TextArea.js @@ -0,0 +1,34 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + Utils = require('Common/Utils'), + + AbstractInput = require('Component/AbstractInput') + ; + + /** + * @constructor + * + * @param {Object} oParams + * + * @extends AbstractInput + */ + function TextAreaComponent(oParams) + { + AbstractInput.call(this, oParams); + + this.rows = oParams.rows || 5; + this.spellcheck = Utils.isUnd(oParams.spellcheck) ? false : !!oParams.spellcheck; + } + + _.extend(TextAreaComponent.prototype, AbstractInput.prototype); + + module.exports = AbstractInput.componentExportHelper( + TextAreaComponent, 'TextAreaComponent'); + +}()); diff --git a/dev/External/Opentip.js b/dev/External/Opentip.js new file mode 100644 index 000000000..fb6123067 --- /dev/null +++ b/dev/External/Opentip.js @@ -0,0 +1,51 @@ + +(function () { + + 'use strict'; + + var + window = require('window'), + Opentip = window.Opentip + ; + + Opentip.styles.rainloop = { + + 'extends': 'standard', + + 'fixed': true, + 'target': true, + + 'delay': 0.2, + 'hideDelay': 0, + + 'hideEffect': 'fade', + 'hideEffectDuration': 0.2, + + 'showEffect': 'fade', + 'showEffectDuration': 0.2, + + 'showOn': 'mouseover click', + 'removeElementsOnHide': true, + + 'background': '#fff', + 'shadow': false, + + 'borderColor': '#999', + 'borderRadius': 2, + 'borderWidth': 1 + }; + + Opentip.styles.rainloopTip = { + 'extends': 'rainloop', + 'delay': 0.4, + 'group': 'rainloopTips' + }; + + Opentip.styles.rainloopErrorTip = { + 'extends': 'rainloop', + 'className': 'rainloopErrorTip' + }; + + module.exports = Opentip; + +}()); diff --git a/dev/External/ko.js b/dev/External/ko.js index a549085e2..b2e1e4712 100644 --- a/dev/External/ko.js +++ b/dev/External/ko.js @@ -1,118 +1,212 @@ -(function (module, ko) { +(function (ko) { 'use strict'; var window = require('window'), _ = require('_'), - $ = require('$') + $ = require('$'), + Opentip = require('Opentip'), + + fDisposalTooltipHelper = function (oElement) { + ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () { + + if (oElement && oElement.__opentip) + { + oElement.__opentip.deactivate(); + } + }); + } ; + ko.bindingHandlers.editor = { + 'init': function (oElement, fValueAccessor) { + + var + fValue = fValueAccessor(), + oEditor = null, + fUpdateEditorValue = function () { + if (oEditor) + { + oEditor.setHtmlOrPlain(fValue()); + } + }, + fUpdateKoValue = function () { + if (oEditor && oEditor.__inited) + { + fValue(oEditor.getDataWithHtmlMark()); + } + }, + HtmlEditor = require('Common/HtmlEditor') + ; + + if (fValue) + { + oEditor = new HtmlEditor(oElement, fUpdateKoValue, fUpdateEditorValue, fUpdateKoValue); + fValue.__editor = oEditor; + fValue.__fetchEditorValue = fUpdateKoValue; + fValue.__updateEditorValue = fUpdateEditorValue; + + fValue.subscribe(fUpdateEditorValue); + fUpdateEditorValue(); + } + } + }; + ko.bindingHandlers.tooltip = { 'init': function (oElement, fValueAccessor) { var - Globals = require('Common/Globals'), - Utils = require('Common/Utils') - ; - - if (!Globals.bMobileDevice) - { - var - $oEl = $(oElement), - sClass = $oEl.data('tooltip-class') || '', - sPlacement = $oEl.data('tooltip-placement') || 'top' - ; - - $oEl.tooltip({ - 'delay': { - 'show': 500, - 'hide': 100 - }, - 'html': true, - 'container': 'body', - 'placement': sPlacement, - 'trigger': 'hover', - 'title': function () { - return $oEl.is('.disabled') || Globals.dropdownVisibility() ? '' : '' + - Utils.i18n(ko.utils.unwrapObservable(fValueAccessor())) + ''; - } - }).click(function () { - $oEl.tooltip('hide'); - }); - - Globals.tooltipTrigger.subscribe(function () { - $oEl.tooltip('hide'); - }); - } - } - }; - - ko.bindingHandlers.tooltip2 = { - 'init': function (oElement, fValueAccessor) { - var - Globals = require('Common/Globals'), - $oEl = $(oElement), - sClass = $oEl.data('tooltip-class') || '', - sPlacement = $oEl.data('tooltip-placement') || 'top' - ; - - $oEl.tooltip({ - 'delay': { - 'show': 500, - 'hide': 100 - }, - 'html': true, - 'container': 'body', - 'placement': sPlacement, - 'title': function () { - return $oEl.is('.disabled') || Globals.dropdownVisibility() ? '' : - '' + fValueAccessor()() + ''; - } - }).click(function () { - $oEl.tooltip('hide'); - }); - - Globals.tooltipTrigger.subscribe(function () { - $oEl.tooltip('hide'); - }); - } - }; - - ko.bindingHandlers.tooltip3 = { - 'init': function (oElement) { - - var + bi18n = true, + sValue = '', + Translator = null, $oEl = $(oElement), + fValue = fValueAccessor(), + bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'), Globals = require('Common/Globals') ; - $oEl.tooltip({ - 'container': 'body', - 'trigger': 'hover manual', - 'title': function () { - return $oEl.data('tooltip3-data') || ''; + if (!Globals.bMobileDevice || bMobile) + { + bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on'); + sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue); + + oElement.__opentip = new Opentip(oElement, { + 'style': 'rainloopTip', + 'element': oElement, + 'tipJoint': $oEl.data('tooltip-join') || 'bottom' + }); + + Globals.dropdownVisibility.subscribe(function (bV) { + if (bV) { + oElement.__opentip.hide(); + } + }); + + if ('' === sValue) + { + oElement.__opentip.hide(); + oElement.__opentip.deactivate(); + oElement.__opentip.setContent(''); + } + else + { + oElement.__opentip.activate(); + } + + if (bi18n) + { + Translator = require('Common/Translator'); + + oElement.__opentip.setContent(Translator.i18n(sValue)); + + Translator.trigger.subscribe(function () { + oElement.__opentip.setContent(Translator.i18n(sValue)); + }); + + Globals.dropdownVisibility.subscribe(function () { + if (oElement && oElement.__opentip) + { + oElement.__opentip.setContent(require('Common/Translator').i18n(sValue)); + } + }); + } + else + { + oElement.__opentip.setContent(sValue); + } + } + }, + 'update': function (oElement, fValueAccessor) { + + var + bi18n = true, + sValue = '', + $oEl = $(oElement), + fValue = fValueAccessor(), + bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'), + Globals = require('Common/Globals') + ; + + if ((!Globals.bMobileDevice || bMobile) && oElement.__opentip) + { + bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on'); + sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue); + + if (sValue) + { + oElement.__opentip.setContent( + bi18n ? require('Common/Translator').i18n(sValue) : sValue); + oElement.__opentip.activate(); + } + else + { + oElement.__opentip.hide(); + oElement.__opentip.deactivate(); + oElement.__opentip.setContent(''); + } + } + } + }; + + ko.bindingHandlers.tooltipErrorTip = { + 'init': function (oElement) { + + var $oEl = $(oElement); + + oElement.__opentip = new Opentip(oElement, { + 'style': 'rainloopErrorTip', + 'hideOn': 'mouseout click', + 'element': oElement, + 'tipJoint': $oEl.data('tooltip-join') || 'top' + }); + + oElement.__opentip.deactivate(); + + $(window.document).on('click', function () { + if (oElement && oElement.__opentip) + { + oElement.__opentip.hide(); } }); - $(window.document).click(function () { - $oEl.tooltip('hide'); - }); - - Globals.tooltipTrigger.subscribe(function () { - $oEl.tooltip('hide'); - }); + fDisposalTooltipHelper(oElement); }, 'update': function (oElement, fValueAccessor) { - var sValue = ko.utils.unwrapObservable(fValueAccessor()); - if ('' === sValue) + + var + $oEl = $(oElement), + fValue = fValueAccessor(), + sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue), + oOpenTips = oElement.__opentip + ; + + if (oOpenTips) { - $(oElement).data('tooltip3-data', '').tooltip('hide'); - } - else - { - $(oElement).data('tooltip3-data', sValue).tooltip('show'); + if ('' === sValue) + { + oOpenTips.hide(); + oOpenTips.deactivate(); + oOpenTips.setContent(''); + } + else + { + _.delay(function () { + if ($oEl.is(':visible')) + { + oOpenTips.setContent(sValue); + oOpenTips.activate(); + oOpenTips.show(); + } + else + { + oOpenTips.hide(); + oOpenTips.deactivate(); + oOpenTips.setContent(''); + } + }, 100); + } } } }; @@ -120,25 +214,33 @@ ko.bindingHandlers.registrateBootstrapDropdown = { 'init': function (oElement) { var Globals = require('Common/Globals'); - Globals.aBootstrapDropdowns.push($(oElement)); + if (Globals && Globals.aBootstrapDropdowns) + { + Globals.aBootstrapDropdowns.push($(oElement)); + + $(oElement).click(function () { + require('Common/Utils').detectDropdownVisibility(); + }); + +// ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () { +// }); + } } }; ko.bindingHandlers.openDropdownTrigger = { 'update': function (oElement, fValueAccessor) { - if (ko.utils.unwrapObservable(fValueAccessor())) + if (ko.unwrap(fValueAccessor())) { - var - $el = $(oElement), - Utils = require('Common/Utils') - ; - - if (!$el.hasClass('open')) + var $oEl = $(oElement); + if (!$oEl.hasClass('open')) { - $el.find('.dropdown-toggle').dropdown('toggle'); - Utils.detectDropdownVisibility(); + $oEl.find('.dropdown-toggle').dropdown('toggle'); } + $oEl.find('.dropdown-toggle').focus(); + + require('Common/Utils').detectDropdownVisibility(); fValueAccessor()(false); } } @@ -154,7 +256,11 @@ ko.bindingHandlers.popover = { 'init': function (oElement, fValueAccessor) { - $(oElement).popover(ko.utils.unwrapObservable(fValueAccessor())); + $(oElement).popover(ko.unwrap(fValueAccessor())); + + ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () { + $(oElement).popover('destroy'); + }); } }; @@ -163,22 +269,22 @@ var Utils = require('Common/Utils'); if (oElement && oElement.styleSheet && !Utils.isUnd(oElement.styleSheet.cssText)) { - oElement.styleSheet.cssText = ko.utils.unwrapObservable(fValueAccessor()); + oElement.styleSheet.cssText = ko.unwrap(fValueAccessor()); } else { - $(oElement).text(ko.utils.unwrapObservable(fValueAccessor())); + $(oElement).text(ko.unwrap(fValueAccessor())); } }, 'update': function (oElement, fValueAccessor) { var Utils = require('Common/Utils'); if (oElement && oElement.styleSheet && !Utils.isUnd(oElement.styleSheet.cssText)) { - oElement.styleSheet.cssText = ko.utils.unwrapObservable(fValueAccessor()); + oElement.styleSheet.cssText = ko.unwrap(fValueAccessor()); } else { - $(oElement).text(ko.utils.unwrapObservable(fValueAccessor())); + $(oElement).text(ko.unwrap(fValueAccessor())); } } }; @@ -204,31 +310,39 @@ ko.bindingHandlers.onEnter = { 'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) { - $(oElement).on('keypress', function (oEvent) { + $(oElement).on('keypress.koOnEnter', function (oEvent) { if (oEvent && 13 === window.parseInt(oEvent.keyCode, 10)) { $(oElement).trigger('change'); fValueAccessor().call(oViewModel); } }); + + ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () { + $(oElement).off('keypress.koOnEnter'); + }); } }; ko.bindingHandlers.onEsc = { 'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) { - $(oElement).on('keypress', function (oEvent) { + $(oElement).on('keypress.koOnEsc', function (oEvent) { if (oEvent && 27 === window.parseInt(oEvent.keyCode, 10)) { $(oElement).trigger('change'); fValueAccessor().call(oViewModel); } }); + + ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () { + $(oElement).off('keypress.koOnEsc'); + }); } }; ko.bindingHandlers.clickOnTrue = { 'update': function (oElement, fValueAccessor) { - if (ko.utils.unwrapObservable(fValueAccessor())) + if (ko.unwrap(fValueAccessor())) { $(oElement).click(); } @@ -245,51 +359,85 @@ $(oElement).toggleClass('fade', !Globals.bMobileDevice).modal({ 'keyboard': false, - 'show': ko.utils.unwrapObservable(fValueAccessor()) + 'show': ko.unwrap(fValueAccessor()) }) - .on('shown', function () { - Utils.windowResize(); - }) - .find('.close').click(function () { + .on('shown.koModal', Utils.windowResizeCallback) + .find('.close').on('click.koModal', function () { fValueAccessor()(false); }); + ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () { + $(oElement) + .off('shown.koModal') + .find('.close') + .off('click.koModal') + ; + }); }, 'update': function (oElement, fValueAccessor) { - $(oElement).modal(ko.utils.unwrapObservable(fValueAccessor()) ? 'show' : 'hide'); + + var Globals = require('Common/Globals'); + + $(oElement).modal(!!ko.unwrap(fValueAccessor()) ? 'show' : 'hide'); + + if (Globals.$html.hasClass('rl-anim')) + { + Globals.$html.addClass('rl-modal-animation'); + _.delay(function () { + Globals.$html.removeClass('rl-modal-animation'); + }, 400); + } + + } + }; + + ko.bindingHandlers.moment = { + 'init': function (oElement, fValueAccessor) { + require('Common/Momentor').momentToNode( + $(oElement).addClass('moment').data('moment-time', ko.unwrap(fValueAccessor())) + ); + }, + 'update': function (oElement, fValueAccessor) { + require('Common/Momentor').momentToNode( + $(oElement).data('moment-time', ko.unwrap(fValueAccessor())) + ); } }; ko.bindingHandlers.i18nInit = { 'init': function (oElement) { - var Utils = require('Common/Utils'); - Utils.i18nToNode(oElement); + require('Common/Translator').i18nToNodes(oElement); + } + }; + + ko.bindingHandlers.translatorInit = { + 'init': function (oElement) { + require('Common/Translator').i18nToNodes(oElement); } }; ko.bindingHandlers.i18nUpdate = { 'update': function (oElement, fValueAccessor) { - var Utils = require('Common/Utils'); - ko.utils.unwrapObservable(fValueAccessor()); - Utils.i18nToNode(oElement); + ko.unwrap(fValueAccessor()); + require('Common/Translator').i18nToNodes(oElement); } }; ko.bindingHandlers.link = { 'update': function (oElement, fValueAccessor) { - $(oElement).attr('href', ko.utils.unwrapObservable(fValueAccessor())); + $(oElement).attr('href', ko.unwrap(fValueAccessor())); } }; ko.bindingHandlers.title = { 'update': function (oElement, fValueAccessor) { - $(oElement).attr('title', ko.utils.unwrapObservable(fValueAccessor())); + $(oElement).attr('title', ko.unwrap(fValueAccessor())); } }; ko.bindingHandlers.textF = { 'init': function (oElement, fValueAccessor) { - $(oElement).text(ko.utils.unwrapObservable(fValueAccessor())); + $(oElement).text(ko.unwrap(fValueAccessor())); } }; @@ -299,9 +447,36 @@ } }; + ko.bindingHandlers.initFixedTrigger = { + 'init': function (oElement, fValueAccessor) { + var + aValues = ko.unwrap(fValueAccessor()), + $oContainer = null, + $oElement = $(oElement), + oOffset = null, + + iTop = aValues[1] || 0 + ; + + $oContainer = $(aValues[0] || null); + $oContainer = $oContainer[0] ? $oContainer : null; + + if ($oContainer) + { + $(window).resize(function () { + oOffset = $oContainer.offset(); + if (oOffset && oOffset.top) + { + $oElement.css('top', oOffset.top + iTop); + } + }); + } + } + }; + ko.bindingHandlers.initResizeTrigger = { 'init': function (oElement, fValueAccessor) { - var aValues = ko.utils.unwrapObservable(fValueAccessor()); + var aValues = ko.unwrap(fValueAccessor()); $(oElement).css({ 'height': aValues[1], 'min-height': aValues[1] @@ -312,7 +487,7 @@ var Utils = require('Common/Utils'), Globals = require('Common/Globals'), - aValues = ko.utils.unwrapObservable(fValueAccessor()), + aValues = ko.unwrap(fValueAccessor()), iValue = Utils.pInt(aValues[1]), iSize = 0, iOffset = $(oElement).offset().top @@ -338,16 +513,18 @@ ko.bindingHandlers.appendDom = { 'update': function (oElement, fValueAccessor) { - $(oElement).hide().empty().append(ko.utils.unwrapObservable(fValueAccessor())).show(); + $(oElement).hide().empty().append(ko.unwrap(fValueAccessor())).show(); } }; ko.bindingHandlers.draggable = { 'init': function (oElement, fValueAccessor, fAllBindingsAccessor) { + var Globals = require('Common/Globals'), Utils = require('Common/Utils') ; + if (!Globals.bMobileDevice) { var @@ -419,9 +596,16 @@ return fValueAccessor()(oEvent && oEvent.target ? ko.dataFor(oEvent.target) : null); }; - $(oElement).draggable(oConf).on('mousedown', function () { + $(oElement).draggable(oConf).on('mousedown.koDraggable', function () { Utils.removeInFocus(); }); + + ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () { + $(oElement) + .off('mousedown.koDraggable') + .draggable('destroy') + ; + }); } } }; @@ -463,6 +647,10 @@ } $(oElement).droppable(oConf); + + ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () { + $(oElement).droppable('destroy'); + }); } } } @@ -504,7 +692,7 @@ }, 'update': function (oElement, fValueAccessor) { var - mValue = ko.utils.unwrapObservable(fValueAccessor()), + mValue = ko.unwrap(fValueAccessor()), $oEl = $(oElement) ; @@ -575,9 +763,9 @@ fAllBindings = fAllBindingsAccessor(), fAutoCompleteSource = fAllBindings['autoCompleteSource'] || null, fFocusCallback = function (bValue) { - if (fValue && fValue.focusTrigger) + if (fValue && fValue.focused) { - fValue.focusTrigger(bValue); + fValue.focused(!!bValue); } } ; @@ -588,6 +776,13 @@ 'focusCallback': fFocusCallback, 'inputDelimiters': [',', ';'], 'autoCompleteSource': fAutoCompleteSource, +// 'elementHook': function (oEl, oItem) { +// if (oEl && oItem) +// { +// oEl.addClass('pgp'); +// window.console.log(arguments); +// } +// }, 'parseHook': function (aInput) { return _.map(aInput, function (sInputValue) { @@ -612,14 +807,20 @@ fValue(oEvent.target.value); }, this) }); + + if (fValue && fValue.focused && fValue.focused.subscribe) + { + fValue.focused.subscribe(function (bValue) { + $oEl.inputosaurus(!!bValue ? 'focus' : 'blur'); + }); + } }, - 'update': function (oElement, fValueAccessor, fAllBindingsAccessor) { + 'update': function (oElement, fValueAccessor) { var $oEl = $(oElement), - fAllValueFunc = fAllBindingsAccessor(), - fEmailsTagsFilter = fAllValueFunc['emailsTagsFilter'] || null, - sValue = ko.utils.unwrapObservable(fValueAccessor()) + fValue = fValueAccessor(), + sValue = ko.unwrap(fValue) ; if ($oEl.data('EmailsTagsValue') !== sValue) @@ -628,85 +829,6 @@ $oEl.data('EmailsTagsValue', sValue); $oEl.inputosaurus('refresh'); } - - if (fEmailsTagsFilter && ko.utils.unwrapObservable(fEmailsTagsFilter)) - { - $oEl.inputosaurus('focus'); - } - } - }; - - ko.bindingHandlers.contactTags = { - 'init': function(oElement, fValueAccessor, fAllBindingsAccessor) { - - var - Utils = require('Common/Utils'), - ContactTagModel = require('Model/ContactTag'), - - $oEl = $(oElement), - fValue = fValueAccessor(), - fAllBindings = fAllBindingsAccessor(), - fAutoCompleteSource = fAllBindings['autoCompleteSource'] || null, - fFocusCallback = function (bValue) { - if (fValue && fValue.focusTrigger) - { - fValue.focusTrigger(bValue); - } - } - ; - - $oEl.inputosaurus({ - 'parseOnBlur': true, - 'allowDragAndDrop': false, - 'focusCallback': fFocusCallback, - 'inputDelimiters': [',', ';'], - 'outputDelimiter': ',', - 'autoCompleteSource': fAutoCompleteSource, - 'parseHook': function (aInput) { - return _.map(aInput, function (sInputValue) { - - var - sValue = Utils.trim(sInputValue), - oTag = null - ; - - if ('' !== sValue) - { - oTag = new ContactTagModel(); - oTag.name(sValue); - return [oTag.toLine(false), oTag]; - } - - return [sValue, null]; - - }); - }, - 'change': _.bind(function (oEvent) { - $oEl.data('ContactTagsValue', oEvent.target.value); - fValue(oEvent.target.value); - }, this) - }); - }, - 'update': function (oElement, fValueAccessor, fAllBindingsAccessor) { - - var - $oEl = $(oElement), - fAllValueFunc = fAllBindingsAccessor(), - fContactTagsFilter = fAllValueFunc['contactTagsFilter'] || null, - sValue = ko.utils.unwrapObservable(fValueAccessor()) - ; - - if ($oEl.data('ContactTagsValue') !== sValue) - { - $oEl.val(sValue); - $oEl.data('ContactTagsValue', sValue); - $oEl.inputosaurus('refresh'); - } - - if (fContactTagsFilter && ko.utils.unwrapObservable(fContactTagsFilter)) - { - $oEl.inputosaurus('focus'); - } } }; @@ -752,6 +874,8 @@ } }; + // extenders + ko.extenders.trimmer = function (oTarget) { var @@ -796,6 +920,56 @@ return oResult; }; + ko.extenders.limitedList = function (oTarget, mList) + { + var + Utils = require('Common/Utils'), + oResult = ko.computed({ + 'read': oTarget, + 'write': function (sNewValue) { + + var + sCurrentValue = ko.unwrap(oTarget), + aList = ko.unwrap(mList) + ; + + if (Utils.isNonEmptyArray(aList)) + { + if (-1 < Utils.inArray(sNewValue, aList)) + { + oTarget(sNewValue); + } + else if (-1 < Utils.inArray(sCurrentValue, aList)) + { + oTarget(sCurrentValue + ' '); + oTarget(sCurrentValue); + } + else + { + oTarget(aList[0] + ' '); + oTarget(aList[0]); + } + } + else + { + oTarget(''); + } + } + }).extend({'notify': 'always'}) + ; + + oResult(oTarget()); + + if (!oResult.valueHasMutated) + { + oResult.valueHasMutated = function () { + oTarget.valueHasMutated(); + }; + } + + return oResult; + }; + ko.extenders.reversible = function (oTarget) { var mValue = oTarget(); @@ -826,25 +1000,87 @@ return oTarget; }; + ko.extenders.toggleSubscribeProperty = function (oTarget, oOptions) + { + var sProp = oOptions[1]; + + if (sProp) + { + oTarget.subscribe(function (oPrev) { + if (oPrev && oPrev[sProp]) + { + oPrev[sProp](false); + } + }, oOptions[0], 'beforeChange'); + + oTarget.subscribe(function (oNext) { + if (oNext && oNext[sProp]) + { + oNext[sProp](true); + } + }, oOptions[0]); + } + + return oTarget; + }; + ko.extenders.falseTimeout = function (oTarget, iOption) { - var Utils = require('Common/Utils'); - - oTarget.iTimeout = 0; + oTarget.iFalseTimeoutTimeout = 0; oTarget.subscribe(function (bValue) { if (bValue) { - window.clearTimeout(oTarget.iTimeout); - oTarget.iTimeout = window.setTimeout(function () { + window.clearTimeout(oTarget.iFalseTimeoutTimeout); + oTarget.iFalseTimeoutTimeout = window.setTimeout(function () { oTarget(false); - oTarget.iTimeout = 0; - }, Utils.pInt(iOption)); + oTarget.iFalseTimeoutTimeout = 0; + }, require('Common/Utils').pInt(iOption)); } }); return oTarget; }; + ko.extenders.specialThrottle = function (oTarget, iOption) + { + oTarget.iSpecialThrottleTimeoutValue = require('Common/Utils').pInt(iOption); + if (0 < oTarget.iSpecialThrottleTimeoutValue) + { + oTarget.iSpecialThrottleTimeout = 0; + oTarget.valueForRead = ko.observable(!!oTarget()).extend({'throttle': 10}); + + return ko.computed({ + 'read': oTarget.valueForRead, + 'write': function (bValue) { + + if (bValue) + { + oTarget.valueForRead(bValue); + } + else + { + if (oTarget.valueForRead()) + { + window.clearTimeout(oTarget.iSpecialThrottleTimeout); + oTarget.iSpecialThrottleTimeout = window.setTimeout(function () { + oTarget.valueForRead(false); + oTarget.iSpecialThrottleTimeout = 0; + }, oTarget.iSpecialThrottleTimeoutValue); + } + else + { + oTarget.valueForRead(bValue); + } + } + } + }); + } + + return oTarget; + }; + + // functions + ko.observable.fn.validateNone = function () { this.hasError = ko.observable(false); @@ -881,6 +1117,25 @@ return this; }; + ko.observable.fn.deleteAccessHelper = function () + { + this.extend({'falseTimeout': 3000}).extend({'toggleSubscribe': [null, + function (oPrev) { + if (oPrev && oPrev.deleteAccess) + { + oPrev.deleteAccess(false); + } + }, function (oNext) { + if (oNext && oNext.deleteAccess) + { + oNext.deleteAccess(true); + } + } + ]}); + + return this; + }; + ko.observable.fn.validateFunc = function (fFunc) { var Utils = require('Common/Utils'); @@ -901,4 +1156,4 @@ module.exports = ko; -}(module, ko)); +}(ko)); diff --git a/dev/Helper/Message.js b/dev/Helper/Message.js new file mode 100644 index 000000000..1be2badb3 --- /dev/null +++ b/dev/Helper/Message.js @@ -0,0 +1,123 @@ + +(function () { + + 'use strict'; + + var + Utils = require('Common/Utils'), + + EmailModel = require('Model/Email') + ; + + /** + * @constructor + */ + function MessageHelper() {} + + /** + * @param {Array.} aEmail + * @param {boolean=} bFriendlyView + * @param {boolean=} bWrapWithLink = false + * @return {string} + */ + MessageHelper.prototype.emailArrayToString = function (aEmail, bFriendlyView, bWrapWithLink) + { + var + aResult = [], + iIndex = 0, + iLen = 0 + ; + + if (Utils.isNonEmptyArray(aEmail)) + { + for (iIndex = 0, iLen = aEmail.length; iIndex < iLen; iIndex++) + { + aResult.push(aEmail[iIndex].toLine(bFriendlyView, bWrapWithLink)); + } + } + + return aResult.join(', '); + }; + + /** + * @param {Array.} aEmails + * @return {string} + */ + MessageHelper.prototype.emailArrayToStringClear = function (aEmails) + { + var + aResult = [], + iIndex = 0, + iLen = 0 + ; + + if (Utils.isNonEmptyArray(aEmails)) + { + for (iIndex = 0, iLen = aEmails.length; iIndex < iLen; iIndex++) + { + if (aEmails[iIndex] && aEmails[iIndex].email && '' !== aEmails[iIndex].name) + { + aResult.push(aEmails[iIndex].email); + } + } + } + + return aResult.join(', '); + }; + + /** + * @param {?Array} aJson + * @return {Array.} + */ + MessageHelper.prototype.emailArrayFromJson = function (aJson) + { + var + iIndex = 0, + iLen = 0, + oEmailModel = null, + aResult = [] + ; + + if (Utils.isNonEmptyArray(aJson)) + { + for (iIndex = 0, iLen = aJson.length; iIndex < iLen; iIndex++) + { + oEmailModel = EmailModel.newInstanceFromJson(aJson[iIndex]); + if (oEmailModel) + { + aResult.push(oEmailModel); + } + } + } + + return aResult; + }; + + /** + * @param {Array.} aInputEmails + * @param {Object} oUnic + * @param {Array} aLocalEmails + */ + MessageHelper.prototype.replyHelper = function (aInputEmails, oUnic, aLocalEmails) + { + if (aInputEmails && 0 < aInputEmails.length) + { + var + iIndex = 0, + iLen = aInputEmails.length + ; + + for (; iIndex < iLen; iIndex++) + { + if (Utils.isUnd(oUnic[aInputEmails[iIndex].email])) + { + oUnic[aInputEmails[iIndex].email] = true; + aLocalEmails.push(aInputEmails[iIndex]); + } + } + } + }; + + module.exports = new MessageHelper(); + +}()); \ No newline at end of file diff --git a/dev/Knoin/AbstractModel.js b/dev/Knoin/AbstractModel.js new file mode 100644 index 000000000..fa3eedce2 --- /dev/null +++ b/dev/Knoin/AbstractModel.js @@ -0,0 +1,49 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + Utils = require('Common/Utils') + ; + + /** + * @constructor + * + * @param {string} sModelName + */ + function AbstractModel(sModelName) + { + this.sModelName = sModelName || ''; + this.disposables = []; + } + + /** + * @param {Array|Object} mInputValue + */ + AbstractModel.prototype.regDisposables = function (mInputValue) + { + if (Utils.isArray(mInputValue)) + { + _.each(mInputValue, function (mValue) { + this.disposables.push(mValue); + }, this); + } + else if (mInputValue) + { + this.disposables.push(mInputValue); + } + + }; + + AbstractModel.prototype.onDestroy = function () + { + Utils.disposeObject(this); + }; + + module.exports = AbstractModel; + +}()); + diff --git a/dev/Knoin/Knoin.js b/dev/Knoin/Knoin.js index ea089f220..c8052eda5 100644 --- a/dev/Knoin/Knoin.js +++ b/dev/Knoin/Knoin.js @@ -139,6 +139,9 @@ ViewModelClass.__builded = true; ViewModelClass.__vm = oViewModel; + oViewModel.onShowTrigger = ko.observable(false); + oViewModel.onHideTrigger = ko.observable(false); + oViewModel.viewModelName = ViewModelClass.__name; oViewModel.viewModelNames = ViewModelClass.__names; @@ -167,11 +170,25 @@ Globals.popupVisibilityNames.push(this.viewModelName); oViewModel.viewModelDom.css('z-index', 3000 + Globals.popupVisibilityNames().length + 10); - Utils.delegateRun(this, 'onFocus', [], 500); +// Utils.delegateRun(this, 'onShow'); // moved to showScreenPopup function (for parameters) + + if (this.onShowTrigger) + { + this.onShowTrigger(!this.onShowTrigger()); + } + + Utils.delegateRun(this, 'onShowWithDelay', [], 500); } else { Utils.delegateRun(this, 'onHide'); + Utils.delegateRun(this, 'onHideWithDelay', [], 500); + + if (this.onHideTrigger) + { + this.onHideTrigger(!this.onHideTrigger()); + } + this.restoreKeyScope(); _.each(this.viewModelNames, function (sName) { @@ -181,8 +198,6 @@ Globals.popupVisibilityNames.remove(this.viewModelName); oViewModel.viewModelDom.css('z-index', 2000); - Globals.tooltipTrigger(!Globals.tooltipTrigger()); - _.delay(function () { self.viewModelDom.hide(); }, 300); @@ -196,7 +211,7 @@ }); ko.applyBindingAccessorsToNode(oViewModelDom[0], { - 'i18nInit': true, + 'translatorInit': true, 'template': function () { return {'name': oViewModel.viewModelTemplate()};} }, oViewModel); @@ -243,6 +258,7 @@ if (ViewModelClassToShow.__vm && ViewModelClassToShow.__dom) { ViewModelClassToShow.__vm.modalVisibility(true); + Utils.delegateRun(ViewModelClassToShow.__vm, 'onShow', aParameters || []); _.each(ViewModelClassToShow.__names, function (sName) { @@ -270,6 +286,7 @@ var self = this, oScreen = null, + bSameScreen= false, oCross = null ; @@ -293,6 +310,8 @@ if (oScreen && oScreen.__started) { + bSameScreen = this.oCurrentScreen && oScreen === this.oCurrentScreen; + if (!oScreen.__builded) { oScreen.__builded = true; @@ -310,9 +329,15 @@ _.defer(function () { // hide screen - if (self.oCurrentScreen) + if (self.oCurrentScreen && !bSameScreen) { Utils.delegateRun(self.oCurrentScreen, 'onHide'); + Utils.delegateRun(self.oCurrentScreen, 'onHideWithDelay', [], 500); + + if (self.oCurrentScreen.onHideTrigger) + { + self.oCurrentScreen.onHideTrigger(!self.oCurrentScreen.onHideTrigger()); + } if (Utils.isNonEmptyArray(self.oCurrentScreen.viewModels())) { @@ -323,7 +348,14 @@ { ViewModelClass.__dom.hide(); ViewModelClass.__vm.viewModelVisibility(false); + Utils.delegateRun(ViewModelClass.__vm, 'onHide'); + Utils.delegateRun(ViewModelClass.__vm, 'onHideWithDelay', [], 500); + + if (ViewModelClass.__vm.onHideTrigger) + { + ViewModelClass.__vm.onHideTrigger(!ViewModelClass.__vm.onHideTrigger()); + } } }); @@ -334,9 +366,13 @@ self.oCurrentScreen = oScreen; // show screen - if (self.oCurrentScreen) + if (self.oCurrentScreen && !bSameScreen) { Utils.delegateRun(self.oCurrentScreen, 'onShow'); + if (self.oCurrentScreen.onShowTrigger) + { + self.oCurrentScreen.onShowTrigger(!self.oCurrentScreen.onShowTrigger()); + } Plugins.runHook('screen-on-show', [self.oCurrentScreen.screenName(), self.oCurrentScreen]); @@ -351,7 +387,12 @@ ViewModelClass.__vm.viewModelVisibility(true); Utils.delegateRun(ViewModelClass.__vm, 'onShow'); - Utils.delegateRun(ViewModelClass.__vm, 'onFocus', [], 200); + if (ViewModelClass.__vm.onShowTrigger) + { + ViewModelClass.__vm.onShowTrigger(!ViewModelClass.__vm.onShowTrigger()); + } + + Utils.delegateRun(ViewModelClass.__vm, 'onShowWithDelay', [], 200); _.each(ViewModelClass.__names, function (sName) { Plugins.runHook('view-model-on-show', [sName, ViewModelClass.__vm]); @@ -427,7 +468,11 @@ _.delay(function () { Globals.$html.removeClass('rl-started-trigger').addClass('rl-started'); - }, 50); + }, 100); + + _.delay(function () { + Globals.$html.addClass('rl-started-delay'); + }, 200); }; /** diff --git a/dev/Model/Account.js b/dev/Model/Account.js index 9f7aba2f4..97de166d5 100644 --- a/dev/Model/Account.js +++ b/dev/Model/Account.js @@ -4,9 +4,12 @@ 'use strict'; var + _ = require('_'), ko = require('ko'), - Utils = require('Common/Utils') + Utils = require('Common/Utils'), + + AbstractModel = require('Knoin/AbstractModel') ; /** @@ -14,15 +17,23 @@ * * @param {string} sEmail * @param {boolean=} bCanBeDelete = true + * @param {number=} iCount = 0 */ - function AccountModel(sEmail, bCanBeDelete) + function AccountModel(sEmail, bCanBeDelete, iCount) { + AbstractModel.call(this, 'AccountModel'); + this.email = sEmail; + this.count = ko.observable(iCount || 0); + this.deleteAccess = ko.observable(false); - this.canBeDalete = ko.observable(Utils.isUnd(bCanBeDelete) ? true : !!bCanBeDelete); + this.canBeDeleted = ko.observable(Utils.isUnd(bCanBeDelete) ? true : !!bCanBeDelete); + this.canBeEdit = this.canBeDeleted; } + _.extend(AccountModel.prototype, AbstractModel.prototype); + /** * @type {string} */ @@ -33,7 +44,7 @@ */ AccountModel.prototype.changeAccountLink = function () { - return require('Common/LinkBuilder').change(this.email); + return require('Common/Links').change(this.email); }; module.exports = AccountModel; diff --git a/dev/Model/Attachment.js b/dev/Model/Attachment.js index adf42549d..d4dc6911e 100644 --- a/dev/Model/Attachment.js +++ b/dev/Model/Attachment.js @@ -5,10 +5,16 @@ var window = require('window'), + _ = require('_'), + ko = require('ko'), + Enums = require('Common/Enums'), Globals = require('Common/Globals'), Utils = require('Common/Utils'), - LinkBuilder = require('Common/LinkBuilder') + Links = require('Common/Links'), + Audio = require('Common/Audio'), + + AbstractModel = require('Knoin/AbstractModel') ; /** @@ -16,12 +22,19 @@ */ function AttachmentModel() { + AbstractModel.call(this, 'AttachmentModel'); + + this.checked = ko.observable(false); + this.mimeType = ''; this.fileName = ''; + this.fileNameExt = ''; + this.fileType = Enums.FileType.Unknown; this.estimatedSize = 0; this.friendlySize = ''; this.isInline = false; this.isLinked = false; + this.isThumbnail = false; this.cid = ''; this.cidWithOutTags = ''; this.contentLocation = ''; @@ -29,8 +42,11 @@ this.folder = ''; this.uid = ''; this.mimeIndex = ''; + this.framed = false; } + _.extend(AttachmentModel.prototype, AbstractModel.prototype); + /** * @static * @param {AjaxJsonAttachment} oJsonAttachment @@ -44,10 +60,13 @@ AttachmentModel.prototype.mimeType = ''; AttachmentModel.prototype.fileName = ''; + AttachmentModel.prototype.fileType = ''; + AttachmentModel.prototype.fileNameExt = ''; AttachmentModel.prototype.estimatedSize = 0; AttachmentModel.prototype.friendlySize = ''; AttachmentModel.prototype.isInline = false; AttachmentModel.prototype.isLinked = false; + AttachmentModel.prototype.isThumbnail = false; AttachmentModel.prototype.cid = ''; AttachmentModel.prototype.cidWithOutTags = ''; AttachmentModel.prototype.contentLocation = ''; @@ -55,6 +74,7 @@ AttachmentModel.prototype.folder = ''; AttachmentModel.prototype.uid = ''; AttachmentModel.prototype.mimeIndex = ''; + AttachmentModel.prototype.framed = false; /** * @param {AjaxJsonAttachment} oJsonAttachment @@ -64,11 +84,12 @@ var bResult = false; if (oJsonAttachment && 'Object/Attachment' === oJsonAttachment['@Object']) { - this.mimeType = (oJsonAttachment.MimeType || '').toLowerCase(); - this.fileName = oJsonAttachment.FileName; + this.mimeType = Utils.trim((oJsonAttachment.MimeType || '').toLowerCase()); + this.fileName = Utils.trim(oJsonAttachment.FileName); this.estimatedSize = Utils.pInt(oJsonAttachment.EstimatedSize); this.isInline = !!oJsonAttachment.IsInline; this.isLinked = !!oJsonAttachment.IsLinked; + this.isThumbnail = !!oJsonAttachment.IsThumbnail; this.cid = oJsonAttachment.CID; this.contentLocation = oJsonAttachment.ContentLocation; this.download = oJsonAttachment.Download; @@ -76,10 +97,14 @@ this.folder = oJsonAttachment.Folder; this.uid = oJsonAttachment.Uid; this.mimeIndex = oJsonAttachment.MimeIndex; + this.framed = !!oJsonAttachment.Framed; this.friendlySize = Utils.friendlySize(this.estimatedSize); this.cidWithOutTags = this.cid.replace(/^<+/, '').replace(/>+$/, ''); + this.fileNameExt = Utils.getFileExtension(this.fileName); + this.fileType = AttachmentModel.staticFileType(this.fileNameExt, this.mimeType); + bResult = true; } @@ -91,9 +116,42 @@ */ AttachmentModel.prototype.isImage = function () { - return -1 < Utils.inArray(this.mimeType.toLowerCase(), - ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'] - ); + return Enums.FileType.Image === this.fileType; + }; + + /** + * @return {boolean} + */ + AttachmentModel.prototype.isMp3 = function () + { + return Enums.FileType.Audio === this.fileType && + 'mp3' === this.fileNameExt; + }; + + /** + * @return {boolean} + */ + AttachmentModel.prototype.isOgg = function () + { + return Enums.FileType.Audio === this.fileType && + ('oga' === this.fileNameExt || 'ogg' === this.fileNameExt); + }; + + /** + * @return {boolean} + */ + AttachmentModel.prototype.isWav = function () + { + return Enums.FileType.Audio === this.fileType && + 'wav' === this.fileNameExt; + }; + + /** + * @return {boolean} + */ + AttachmentModel.prototype.hasThumbnail = function () + { + return this.isThumbnail; }; /** @@ -101,8 +159,12 @@ */ AttachmentModel.prototype.isText = function () { - return 'text/' === this.mimeType.substr(0, 5) && - -1 === Utils.inArray(this.mimeType, ['text/html']); + return Enums.FileType.Text === this.fileType || + Enums.FileType.Eml === this.fileType || + Enums.FileType.Certificate === this.fileType || + Enums.FileType.Html === this.fileType || + Enums.FileType.Code === this.fileType + ; }; /** @@ -110,7 +172,35 @@ */ AttachmentModel.prototype.isPdf = function () { - return Globals.bAllowPdfPreview && 'application/pdf' === this.mimeType; + return Enums.FileType.Pdf === this.fileType; + }; + + /** + * @return {boolean} + */ + AttachmentModel.prototype.isFramed = function () + { + return this.framed && (Globals.__APP__ && Globals.__APP__.googlePreviewSupported()) && + !(this.isPdf() && Globals.bAllowPdfPreview) && !this.isText() && !this.isImage(); + }; + + /** + * @return {boolean} + */ + AttachmentModel.prototype.hasPreview = function () + { + return this.isImage() || (this.isPdf() && Globals.bAllowPdfPreview) || this.isText() || this.isFramed(); + }; + + /** + * @return {boolean} + */ + AttachmentModel.prototype.hasPreplay = function () + { + return (Audio.supportedMp3 && this.isMp3()) || + (Audio.supportedOgg && this.isOgg()) || + (Audio.supportedWav && this.isWav()) + ; }; /** @@ -118,7 +208,7 @@ */ AttachmentModel.prototype.linkDownload = function () { - return LinkBuilder.attachmentDownload(this.download); + return Links.attachmentDownload(this.download); }; /** @@ -126,7 +216,32 @@ */ AttachmentModel.prototype.linkPreview = function () { - return LinkBuilder.attachmentPreview(this.download); + return Links.attachmentPreview(this.download); + }; + + /** + * @return {string} + */ + AttachmentModel.prototype.linkThumbnail = function () + { + return this.hasThumbnail() ? Links.attachmentThumbnailPreview(this.download) : ''; + }; + + /** + * @return {string} + */ + AttachmentModel.prototype.linkThumbnailPreviewStyle = function () + { + var sLink = this.linkThumbnail(); + return '' === sLink ? '' : 'background:url(' + sLink + ')'; + }; + + /** + * @return {string} + */ + AttachmentModel.prototype.linkFramed = function () + { + return Links.attachmentFramed(this.download); }; /** @@ -134,7 +249,30 @@ */ AttachmentModel.prototype.linkPreviewAsPlain = function () { - return LinkBuilder.attachmentPreviewAsPlain(this.download); + return Links.attachmentPreviewAsPlain(this.download); + }; + + /** + * @return {string} + */ + AttachmentModel.prototype.linkPreviewMain = function () + { + var sResult = ''; + switch (true) + { + case this.isImage(): + case this.isPdf() && Globals.bAllowPdfPreview: + sResult = this.linkPreview(); + break; + case this.isText(): + sResult = this.linkPreviewAsPlain(); + break; + case this.isFramed(): + sResult = this.linkFramed(); + break; + } + + return sResult; }; /** @@ -167,57 +305,86 @@ return true; }; - AttachmentModel.prototype.iconClass = function () + /** + * @param {string} sExt + * @param {string} sMimeType + * @return {string} + */ + AttachmentModel.staticFileType = _.memoize(function (sExt, sMimeType) { + sExt = Utils.trim(sExt).toLowerCase(); + sMimeType = Utils.trim(sMimeType).toLowerCase(); + var - aParts = this.mimeType.toLocaleString().split('/'), - sClass = 'icon-file' + sResult = Enums.FileType.Unknown, + aMimeTypeParts = sMimeType.split('/') ; - if (aParts && aParts[1]) + switch (true) { - if ('image' === aParts[0]) - { - sClass = 'icon-file-image'; - } - else if ('text' === aParts[0]) - { - sClass = 'icon-file-text'; - } - else if ('audio' === aParts[0]) - { - sClass = 'icon-file-music'; - } - else if ('video' === aParts[0]) - { - sClass = 'icon-file-movie'; - } - else if (-1 < Utils.inArray(aParts[1], - ['zip', '7z', 'tar', 'rar', 'gzip', 'bzip', 'bzip2', 'x-zip', 'x-7z', 'x-rar', 'x-tar', 'x-gzip', 'x-bzip', 'x-bzip2', 'x-zip-compressed', 'x-7z-compressed', 'x-rar-compressed'])) - { - sClass = 'icon-file-zip'; - } - // else if (-1 < Utils.inArray(aParts[1], - // ['pdf', 'x-pdf'])) - // { - // sClass = 'icon-file-pdf'; - // } - // else if (-1 < Utils.inArray(aParts[1], [ - // 'exe', 'x-exe', 'x-winexe', 'bat' - // ])) - // { - // sClass = 'icon-console'; - // } - else if (-1 < Utils.inArray(aParts[1], [ + case 'image' === aMimeTypeParts[0] || -1 < Utils.inArray(sExt, [ + 'png', 'jpg', 'jpeg', 'gif', 'bmp' + ]): + sResult = Enums.FileType.Image; + break; + case 'audio' === aMimeTypeParts[0] || -1 < Utils.inArray(sExt, [ + 'mp3', 'ogg', 'oga', 'wav' + ]): + sResult = Enums.FileType.Audio; + break; + case 'video' === aMimeTypeParts[0] || -1 < Utils.inArray(sExt, [ + 'mkv', 'avi' + ]): + sResult = Enums.FileType.Video; + break; + case -1 < Utils.inArray(sExt, [ + 'php', 'js', 'css' + ]): + sResult = Enums.FileType.Code; + break; + case 'eml' === sExt || -1 < Utils.inArray(sMimeType, [ + 'message/delivery-status', 'message/rfc822' + ]): + sResult = Enums.FileType.Eml; + break; + case ('text' === aMimeTypeParts[0] && 'html' !== aMimeTypeParts[1]) || -1 < Utils.inArray(sExt, [ + 'txt', 'log' + ]): + sResult = Enums.FileType.Text; + break; + case ('text/html' === sMimeType) || -1 < Utils.inArray(sExt, [ + 'html' + ]): + sResult = Enums.FileType.Html; + break; + case -1 < Utils.inArray(aMimeTypeParts[1], [ + 'zip', '7z', 'tar', 'rar', 'gzip', 'bzip', 'bzip2', 'x-zip', 'x-7z', 'x-rar', 'x-tar', 'x-gzip', 'x-bzip', 'x-bzip2', 'x-zip-compressed', 'x-7z-compressed', 'x-rar-compressed' + ]) || -1 < Utils.inArray(sExt, [ + 'zip', '7z', 'tar', 'rar', 'gzip', 'bzip', 'bzip2' + ]): + sResult = Enums.FileType.Archive; + break; + case -1 < Utils.inArray(aMimeTypeParts[1], ['pdf', 'x-pdf']) || -1 < Utils.inArray(sExt, [ + 'pdf' + ]): + sResult = Enums.FileType.Pdf; + break; + case -1 < Utils.inArray(sMimeType, [ + 'application/pgp-signature', 'application/pkcs7-signature', 'application/pgp-keys' + ]) || -1 < Utils.inArray(sExt, [ + 'asc', 'pem', 'ppk' + ]): + sResult = Enums.FileType.Certificate; + break; + case -1 < Utils.inArray(aMimeTypeParts[1], [ 'rtf', 'msword', 'vnd.msword', 'vnd.openxmlformats-officedocument.wordprocessingml.document', 'vnd.openxmlformats-officedocument.wordprocessingml.template', 'vnd.ms-word.document.macroEnabled.12', 'vnd.ms-word.template.macroEnabled.12' - ])) - { - sClass = 'icon-file-text'; - } - else if (-1 < Utils.inArray(aParts[1], [ + ]): + sResult = Enums.FileType.WordText; + break; + case -1 < Utils.inArray(aMimeTypeParts[1], [ 'excel', 'ms-excel', 'vnd.ms-excel', 'vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'vnd.openxmlformats-officedocument.spreadsheetml.template', @@ -225,11 +392,10 @@ 'vnd.ms-excel.template.macroEnabled.12', 'vnd.ms-excel.addin.macroEnabled.12', 'vnd.ms-excel.sheet.binary.macroEnabled.12' - ])) - { - sClass = 'icon-file-excel'; - } - else if (-1 < Utils.inArray(aParts[1], [ + ]): + sResult = Enums.FileType.Sheet; + break; + case -1 < Utils.inArray(aMimeTypeParts[1], [ 'powerpoint', 'ms-powerpoint', 'vnd.ms-powerpoint', 'vnd.openxmlformats-officedocument.presentationml.presentation', 'vnd.openxmlformats-officedocument.presentationml.template', @@ -238,15 +404,142 @@ 'vnd.ms-powerpoint.presentation.macroEnabled.12', 'vnd.ms-powerpoint.template.macroEnabled.12', 'vnd.ms-powerpoint.slideshow.macroEnabled.12' - ])) - { + ]): + sResult = Enums.FileType.Presentation; + break; + } + + return sResult; + }); + + /** + * @param {string} sFileType + * @return {string} + */ + AttachmentModel.staticIconClass = _.memoize(function (sFileType) + { + var + sText = '', + sClass = 'icon-file' + ; + + switch (sFileType) + { + case Enums.FileType.Text: + case Enums.FileType.Eml: + case Enums.FileType.WordText: + sClass = 'icon-file-text'; + break; + case Enums.FileType.Html: + case Enums.FileType.Code: + sClass = 'icon-file-code'; + break; + case Enums.FileType.Image: + sClass = 'icon-file-image'; + break; + case Enums.FileType.Audio: + sClass = 'icon-file-music'; + break; + case Enums.FileType.Video: + sClass = 'icon-file-movie'; + break; + case Enums.FileType.Archive: + sClass = 'icon-file-zip'; + break; + case Enums.FileType.Certificate: + sClass = 'icon-file-certificate'; + break; + case Enums.FileType.Sheet: + sClass = 'icon-file-excel'; + break; + case Enums.FileType.Presentation: sClass = 'icon-file-chart-graph'; + break; + case Enums.FileType.Pdf: + sText = 'pdf'; + sClass = 'icon-none'; + break; + } + + return [sClass, sText]; + }); + + /** + * @param {string} sFileType + * @return {string} + */ + AttachmentModel.staticCombinedIconClass = function (aData) + { + var + sClass = '', + aTypes = [] + ; + + if (Utils.isNonEmptyArray(aData)) + { + sClass = 'icon-attachment'; + + aTypes = _.uniq(_.compact(_.map(aData, function (aItem) { + return aItem ? AttachmentModel.staticFileType( + Utils.getFileExtension(aItem[0]), aItem[1]) : ''; + }))); + + if (aTypes && 1 === aTypes.length && aTypes[0]) + { + switch (aTypes[0]) + { + case Enums.FileType.Text: + case Enums.FileType.WordText: + sClass = 'icon-file-text'; + break; + case Enums.FileType.Html: + case Enums.FileType.Code: + sClass = 'icon-file-code'; + break; + case Enums.FileType.Image: + sClass = 'icon-file-image'; + break; + case Enums.FileType.Audio: + sClass = 'icon-file-music'; + break; + case Enums.FileType.Video: + sClass = 'icon-file-movie'; + break; + case Enums.FileType.Archive: + sClass = 'icon-file-zip'; + break; + case Enums.FileType.Certificate: + sClass = 'icon-file-certificate'; + break; + case Enums.FileType.Sheet: + sClass = 'icon-file-excel'; + break; + case Enums.FileType.Presentation: + sClass = 'icon-file-chart-graph'; + break; + } } } return sClass; }; + /** + * @return {string} + */ + AttachmentModel.prototype.iconClass = function () + { + return AttachmentModel.staticIconClass(this.fileType)[0]; + }; + + /** + * @return {string} + */ + AttachmentModel.prototype.iconText = function () + { + return AttachmentModel.staticIconClass(this.fileType)[1]; + }; + module.exports = AttachmentModel; }()); \ No newline at end of file diff --git a/dev/Model/ComposeAttachment.js b/dev/Model/ComposeAttachment.js index 346f67413..48d42e9d0 100644 --- a/dev/Model/ComposeAttachment.js +++ b/dev/Model/ComposeAttachment.js @@ -4,9 +4,14 @@ 'use strict'; var + _ = require('_'), ko = require('ko'), - Utils = require('Common/Utils') + Utils = require('Common/Utils'), + + AttachmentModel = require('Model/Attachment'), + + AbstractModel = require('Knoin/AbstractModel') ; /** @@ -21,6 +26,8 @@ */ function ComposeAttachmentModel(sId, sFileName, nSize, bInline, bLinked, sCID, sContentLocation) { + AbstractModel.call(this, 'ComposeAttachmentModel'); + this.id = sId; this.isInline = Utils.isUnd(bInline) ? false : !!bInline; this.isLinked = Utils.isUnd(bLinked) ? false : !!bLinked; @@ -32,18 +39,46 @@ this.size = ko.observable(Utils.isUnd(nSize) ? null : nSize); this.tempName = ko.observable(''); - this.progress = ko.observable(''); + this.progress = ko.observable(0); this.error = ko.observable(''); this.waiting = ko.observable(true); this.uploading = ko.observable(false); this.enabled = ko.observable(true); + this.complete = ko.observable(false); + + this.progressText = ko.computed(function () { + var iP = this.progress(); + return 0 === iP ? '' : '' + (98 < iP ? 100 : iP) + '%'; + }, this); + + this.progressStyle = ko.computed(function () { + var iP = this.progress(); + return 0 === iP ? '' : 'width:' + (98 < iP ? 100 : iP) + '%'; + }, this); + + this.title = ko.computed(function () { + var sError = this.error(); + return '' !== sError ? sError : this.fileName(); + }, this); this.friendlySize = ko.computed(function () { var mSize = this.size(); return null === mSize ? '' : Utils.friendlySize(this.size()); }, this); + + this.mimeType = ko.computed(function () { + return Utils.mimeContentType(this.fileName()); + }, this); + + this.fileExt = ko.computed(function () { + return Utils.getFileExtension(this.fileName()); + }, this); + + this.regDisposables([this.progressText, this.progressStyle, this.title, this.friendlySize, this.mimeType, this.fileExt]); } + _.extend(ComposeAttachmentModel.prototype, AbstractModel.prototype); + ComposeAttachmentModel.prototype.id = ''; ComposeAttachmentModel.prototype.isInline = false; ComposeAttachmentModel.prototype.isLinked = false; @@ -54,6 +89,7 @@ /** * @param {AjaxJsonComposeAttachment} oJsonAttachment + * @return {boolean} */ ComposeAttachmentModel.prototype.initByUploadJson = function (oJsonAttachment) { @@ -71,6 +107,24 @@ return bResult; }; + /** + * @return {string} + */ + ComposeAttachmentModel.prototype.iconClass = function () + { + return AttachmentModel.staticIconClass( + AttachmentModel.staticFileType(this.fileExt(), this.mimeType()))[0]; + }; + + /** + * @return {string} + */ + ComposeAttachmentModel.prototype.iconText = function () + { + return AttachmentModel.staticIconClass( + AttachmentModel.staticFileType(this.fileExt(), this.mimeType()))[1]; + }; + module.exports = ComposeAttachmentModel; }()); \ No newline at end of file diff --git a/dev/Model/Contact.js b/dev/Model/Contact.js index bf79f5ac3..5c653361d 100644 --- a/dev/Model/Contact.js +++ b/dev/Model/Contact.js @@ -9,7 +9,9 @@ Enums = require('Common/Enums'), Utils = require('Common/Utils'), - LinkBuilder = require('Common/LinkBuilder') + Links = require('Common/Links'), + + AbstractModel = require('Knoin/AbstractModel') ; /** @@ -17,10 +19,11 @@ */ function ContactModel() { + AbstractModel.call(this, 'ContactModel'); + this.idContact = 0; this.display = ''; this.properties = []; - this.tags = ''; this.readOnly = false; this.focused = ko.observable(false); @@ -29,6 +32,8 @@ this.deleted = ko.observable(false); } + _.extend(ContactModel.prototype, AbstractModel.prototype); + /** * @return {Array|null} */ @@ -71,7 +76,6 @@ this.idContact = Utils.pInt(oItem['IdContact']); this.display = Utils.pString(oItem['Display']); this.readOnly = !!oItem['ReadOnly']; - this.tags = ''; if (Utils.isNonEmptyArray(oItem['Properties'])) { @@ -83,11 +87,6 @@ }, this); } - if (Utils.isNonEmptyArray(oItem['Tags'])) - { - this.tags = oItem['Tags'].join(','); - } - bResult = true; } @@ -99,7 +98,7 @@ */ ContactModel.prototype.srcAttr = function () { - return LinkBuilder.emptyContactPic(); + return Links.emptyContactPic(); }; /** @@ -113,7 +112,7 @@ /** * @return string */ - ContactModel.prototype.lineAsCcc = function () + ContactModel.prototype.lineAsCss = function () { var aResult = []; if (this.deleted()) diff --git a/dev/Model/ContactProperty.js b/dev/Model/ContactProperty.js index d7f55ed19..f93eb5d06 100644 --- a/dev/Model/ContactProperty.js +++ b/dev/Model/ContactProperty.js @@ -4,10 +4,14 @@ 'use strict'; var + _ = require('_'), ko = require('ko'), Enums = require('Common/Enums'), - Utils = require('Common/Utils') + Utils = require('Common/Utils'), + Translator = require('Common/Translator'), + + AbstractModel = require('Knoin/AbstractModel') ; /** @@ -20,6 +24,8 @@ */ function ContactPropertyModel(iType, sTypeStr, sValue, bFocused, sPlaceholder) { + AbstractModel.call(this, 'ContactPropertyModel'); + this.type = ko.observable(Utils.isUnd(iType) ? Enums.ContactPropertyType.Unknown : iType); this.typeStr = ko.observable(Utils.isUnd(sTypeStr) ? '' : sTypeStr); this.focused = ko.observable(Utils.isUnd(bFocused) ? false : !!bFocused); @@ -29,14 +35,18 @@ this.placeholderValue = ko.computed(function () { var sPlaceholder = this.placeholder(); - return sPlaceholder ? Utils.i18n(sPlaceholder) : ''; + return sPlaceholder ? Translator.i18n(sPlaceholder) : ''; }, this); this.largeValue = ko.computed(function () { return Enums.ContactPropertyType.Note === this.type(); }, this); + + this.regDisposables([this.placeholderValue, this.largeValue]); } + _.extend(ContactPropertyModel.prototype, AbstractModel.prototype); + module.exports = ContactPropertyModel; }()); \ No newline at end of file diff --git a/dev/Model/ContactTag.js b/dev/Model/ContactTag.js deleted file mode 100644 index 0531b1b78..000000000 --- a/dev/Model/ContactTag.js +++ /dev/null @@ -1,58 +0,0 @@ - -(function () { - - 'use strict'; - - var - ko = require('ko'), - - Utils = require('Common/Utils') - ; - - /** - * @constructor - */ - function ContactTagModel() - { - this.idContactTag = 0; - this.name = ko.observable(''); - this.readOnly = false; - } - - ContactTagModel.prototype.parse = function (oItem) - { - var bResult = false; - if (oItem && 'Object/Tag' === oItem['@Object']) - { - this.idContact = Utils.pInt(oItem['IdContactTag']); - this.name(Utils.pString(oItem['Name'])); - this.readOnly = !!oItem['ReadOnly']; - - bResult = true; - } - - return bResult; - }; - - /** - * @param {string} sSearch - * @return {boolean} - */ - ContactTagModel.prototype.filterHelper = function (sSearch) - { - return this.name().toLowerCase().indexOf(sSearch.toLowerCase()) !== -1; - }; - - /** - * @param {boolean=} bEncodeHtml = false - * @return {string} - */ - ContactTagModel.prototype.toLine = function (bEncodeHtml) - { - return (Utils.isUnd(bEncodeHtml) ? false : !!bEncodeHtml) ? - Utils.encodeHtml(this.name()) : this.name(); - }; - - module.exports = ContactTagModel; - -}()); \ No newline at end of file diff --git a/dev/Model/Email.js b/dev/Model/Email.js index bb26edb39..173ed2a80 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -10,13 +10,17 @@ /** * @param {string=} sEmail * @param {string=} sName + * @param {string=} sDkimStatus + * @param {string=} sDkimValue * * @constructor */ - function EmailModel(sEmail, sName) + function EmailModel(sEmail, sName, sDkimStatus, sDkimValue) { this.email = sEmail || ''; this.name = sName || ''; + this.dkimStatus = sDkimStatus || 'none'; + this.dkimValue = sDkimValue || ''; this.clearDuplicateName(); } @@ -42,14 +46,27 @@ */ EmailModel.prototype.email = ''; + /** + * @type {string} + */ + EmailModel.prototype.dkimStatus = 'none'; + + /** + * @type {string} + */ + EmailModel.prototype.dkimValue = ''; + EmailModel.prototype.clear = function () { this.email = ''; this.name = ''; + + this.dkimStatus = 'none'; + this.dkimValue = ''; }; /** - * @returns {boolean} + * @return {boolean} */ EmailModel.prototype.validate = function () { @@ -92,7 +109,7 @@ sString = Utils.trim(sString); var - mRegex = /(?:"([^"]+)")? ?,]+)>?,? ?/g, + mRegex = /(?:"([^"]+)")? ?[<]?(.*?@[^>,]+)>?,? ?/g, mMatch = mRegex.exec(sString) ; @@ -121,6 +138,8 @@ { this.name = Utils.trim(oJsonEmail.Name); this.email = Utils.trim(oJsonEmail.Email); + this.dkimStatus = Utils.trim(oJsonEmail.DkimStatus || ''); + this.dkimValue = Utils.trim(oJsonEmail.DkimValue || ''); bResult = '' !== this.email; this.clearDuplicateName(); @@ -335,14 +354,6 @@ return true; }; - /** - * @return {string} - */ - EmailModel.prototype.inputoTagLine = function () - { - return 0 < this.name.length ? this.name + ' (' + this.email + ')' : this.email; - }; - module.exports = EmailModel; }()); \ No newline at end of file diff --git a/dev/Model/Filter.js b/dev/Model/Filter.js index 88f6da5d3..e919427d0 100644 --- a/dev/Model/Filter.js +++ b/dev/Model/Filter.js @@ -4,11 +4,18 @@ 'use strict'; var + _ = require('_'), ko = require('ko'), Enums = require('Common/Enums'), Utils = require('Common/Utils'), - FilterConditionModel = require('Model/FilterCondition') + Translator = require('Common/Translator'), + + Cache = require('Common/Cache'), + + FilterConditionModel = require('Model/FilterCondition'), + + AbstractModel = require('Knoin/AbstractModel') ; /** @@ -16,36 +23,78 @@ */ function FilterModel() { - this.isNew = ko.observable(true); + AbstractModel.call(this, 'FilterModel'); + this.enabled = ko.observable(true); - this.name = ko.observable(''); + this.id = ''; - this.conditionsType = ko.observable(Enums.FilterRulesType.And); + this.name = ko.observable(''); + this.name.error = ko.observable(false); + this.name.focused = ko.observable(false); this.conditions = ko.observableArray([]); - - this.conditions.subscribe(function () { - Utils.windowResize(); - }); + this.conditionsType = ko.observable(Enums.FilterRulesType.Any); // Actions - this.actionMarkAsRead = ko.observable(false); - this.actionSkipOtherFilters = ko.observable(true); this.actionValue = ko.observable(''); + this.actionValue.error = ko.observable(false); - this.actionType = ko.observable(Enums.FiltersAction.Move); - this.actionTypeOptions = [ // TODO i18n - {'id': Enums.FiltersAction.None, 'name': 'Action - None'}, - {'id': Enums.FiltersAction.Move, 'name': 'Action - Move to'}, - // {'id': Enums.FiltersAction.Forward, 'name': 'Action - Forward to'}, - {'id': Enums.FiltersAction.Discard, 'name': 'Action - Discard'} - ]; + this.actionValueSecond = ko.observable(''); + this.actionValueThird = ko.observable(''); + + this.actionMarkAsRead = ko.observable(false); + + this.actionKeep = ko.observable(true); + this.actionNoStop = ko.observable(false); + + this.actionType = ko.observable(Enums.FiltersAction.MoveTo); + + this.actionType.subscribe(function () { + this.actionValue(''); + this.actionValue.error(false); + this.actionValueSecond(''); + this.actionValueThird(''); + }, this); + + var fGetRealFolderName = function (sFolderFullNameRaw) { + var oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw); + return oFolder ? oFolder.fullName.replace( + '.' === oFolder.delimiter ? /\./ : /[\\\/]+/, ' / ') : sFolderFullNameRaw; + }; + + this.nameSub = ko.computed(function () { + + var + sResult = '', + sActionValue = this.actionValue() + ; + + switch (this.actionType()) + { + case Enums.FiltersAction.MoveTo: + sResult = Translator.i18n('SETTINGS_FILTERS/SUBNAME_MOVE_TO', { + 'FOLDER': fGetRealFolderName(sActionValue) + }); + break; + case Enums.FiltersAction.Forward: + sResult = Translator.i18n('SETTINGS_FILTERS/SUBNAME_FORWARD_TO', { + 'EMAIL': sActionValue + }); + break; + case Enums.FiltersAction.Vacation: + sResult = Translator.i18n('SETTINGS_FILTERS/SUBNAME_VACATION_MESSAGE'); + break; + case Enums.FiltersAction.Reject: + sResult = Translator.i18n('SETTINGS_FILTERS/SUBNAME_REJECT'); + break; + case Enums.FiltersAction.Discard: + sResult = Translator.i18n('SETTINGS_FILTERS/SUBNAME_DISCARD'); + break; + } + + return sResult ? '(' + sResult + ')' : ''; - this.actionMarkAsReadVisiblity = ko.computed(function () { - return -1 < Utils.inArray(this.actionType(), [ - Enums.FiltersAction.None, Enums.FiltersAction.Forward, Enums.FiltersAction.Move - ]); }, this); this.actionTemplate = ko.computed(function () { @@ -54,26 +103,129 @@ switch (this.actionType()) { default: - case Enums.FiltersAction.Move: - sTemplate = 'SettingsFiltersActionValueAsFolders'; + case Enums.FiltersAction.MoveTo: + sTemplate = 'SettingsFiltersActionMoveToFolder'; break; case Enums.FiltersAction.Forward: - sTemplate = 'SettingsFiltersActionWithValue'; + sTemplate = 'SettingsFiltersActionForward'; + break; + case Enums.FiltersAction.Vacation: + sTemplate = 'SettingsFiltersActionVacation'; + break; + case Enums.FiltersAction.Reject: + sTemplate = 'SettingsFiltersActionReject'; break; case Enums.FiltersAction.None: + sTemplate = 'SettingsFiltersActionNone'; + break; case Enums.FiltersAction.Discard: - sTemplate = 'SettingsFiltersActionNoValue'; + sTemplate = 'SettingsFiltersActionDiscard'; break; } return sTemplate; }, this); + + this.regDisposables(this.conditions.subscribe(Utils.windowResizeCallback)); + + this.regDisposables(this.name.subscribe(function (sValue) { + this.name.error('' === sValue); + }, this)); + + this.regDisposables(this.actionValue.subscribe(function (sValue) { + this.actionValue.error('' === sValue); + }, this)); + + this.regDisposables([this.actionNoStop, this.actionTemplate]); + + this.deleteAccess = ko.observable(false); + this.canBeDeleted = ko.observable(true); } + _.extend(FilterModel.prototype, AbstractModel.prototype); + + FilterModel.prototype.generateID = function () + { + this.id = Utils.fakeMd5(); + }; + + FilterModel.prototype.verify = function () + { + if ('' === this.name()) + { + this.name.error(true); + return false; + } + + if (0 < this.conditions().length) + { + if (_.find(this.conditions(), function (oCond) { + return oCond && !oCond.verify(); + })) + { + return false; + } + } + + if ('' === this.actionValue()) + { + if (-1 < Utils.inArray(this.actionType(), [ + Enums.FiltersAction.MoveTo, + Enums.FiltersAction.Forward, + Enums.FiltersAction.Reject, + Enums.FiltersAction.Vacation + ])) + { + this.actionValue.error(true); + return false; + } + } + + if (Enums.FiltersAction.Forward === this.actionType() && + -1 === this.actionValue().indexOf('@')) + { + this.actionValue.error(true); + return false; + } + + this.name.error(false); + this.actionValue.error(false); + + return true; + }; + + FilterModel.prototype.toJson = function () + { + return { + 'ID': this.id, + 'Enabled': this.enabled() ? '1' : '0', + 'Name': this.name(), + 'ConditionsType': this.conditionsType(), + 'Conditions': _.map(this.conditions(), function (oItem) { + return oItem.toJson(); + }), + + 'ActionValue': this.actionValue(), + 'ActionValueSecond': this.actionValueSecond(), + 'ActionValueThird': this.actionValueThird(), + 'ActionType': this.actionType(), + + 'Stop': this.actionNoStop() ? '0' : '1', + 'Keep': this.actionKeep() ? '1' : '0', + 'MarkAsRead': this.actionMarkAsRead() ? '1' : '0' + }; + }; + FilterModel.prototype.addCondition = function () { - this.conditions.push(new FilterConditionModel(this.conditions)); + this.conditions.push(new FilterConditionModel()); + }; + + FilterModel.prototype.removeCondition = function (oConditionToDelete) + { + this.conditions.remove(oConditionToDelete); + Utils.delegateRunOnDestroy(oConditionToDelete); }; FilterModel.prototype.parse = function (oItem) @@ -81,7 +233,32 @@ var bResult = false; if (oItem && 'Object/Filter' === oItem['@Object']) { + this.id = Utils.pString(oItem['ID']); this.name(Utils.pString(oItem['Name'])); + this.enabled(!!oItem['Enabled']); + + this.conditionsType(Utils.pString(oItem['ConditionsType'])); + + this.conditions([]); + + if (Utils.isNonEmptyArray(oItem['Conditions'])) + { + this.conditions(_.compact(_.map(oItem['Conditions'], function (aData) { + var oFilterCondition = new FilterConditionModel(); + return oFilterCondition && oFilterCondition.parse(aData) ? + oFilterCondition : null; + }))); + } + + this.actionType(Utils.pString(oItem['ActionType'])); + + this.actionValue(Utils.pString(oItem['ActionValue'])); + this.actionValueSecond(Utils.pString(oItem['ActionValueSecond'])); + this.actionValueThird(Utils.pString(oItem['ActionValueThird'])); + + this.actionNoStop(!oItem['Stop']); + this.actionKeep(!!oItem['Keep']); + this.actionMarkAsRead(!!oItem['MarkAsRead']); bResult = true; } @@ -89,6 +266,39 @@ return bResult; }; + FilterModel.prototype.cloneSelf = function () + { + var oClone = new FilterModel(); + + oClone.id = this.id; + + oClone.enabled(this.enabled()); + + oClone.name(this.name()); + oClone.name.error(this.name.error()); + + oClone.conditionsType(this.conditionsType()); + + oClone.actionMarkAsRead(this.actionMarkAsRead()); + + oClone.actionType(this.actionType()); + + oClone.actionValue(this.actionValue()); + oClone.actionValue.error(this.actionValue.error()); + + oClone.actionValueSecond(this.actionValueSecond()); + oClone.actionValueThird(this.actionValueThird()); + + oClone.actionKeep(this.actionKeep()); + oClone.actionNoStop(this.actionNoStop()); + + oClone.conditions(_.map(this.conditions(), function (oCondition) { + return oCondition.cloneSelf(); + })); + + return oClone; + }; + module.exports = FilterModel; }()); \ No newline at end of file diff --git a/dev/Model/FilterCondition.js b/dev/Model/FilterCondition.js index 7bde5f94a..9192ea059 100644 --- a/dev/Model/FilterCondition.js +++ b/dev/Model/FilterCondition.js @@ -4,38 +4,26 @@ 'use strict'; var + _ = require('_'), ko = require('ko'), - Enums = require('Common/Enums') + Enums = require('Common/Enums'), + Utils = require('Common/Utils'), + + AbstractModel = require('Knoin/AbstractModel') ; /** - * @param {*} oKoList * @constructor */ - function FilterConditionModel(oKoList) + function FilterConditionModel() { - this.parentList = oKoList; + AbstractModel.call(this, 'FilterConditionModel'); this.field = ko.observable(Enums.FilterConditionField.From); - - this.fieldOptions = [ // TODO i18n - {'id': Enums.FilterConditionField.From, 'name': 'From'}, - {'id': Enums.FilterConditionField.Recipient, 'name': 'Recipient (To or CC)'}, - {'id': Enums.FilterConditionField.To, 'name': 'To'}, - {'id': Enums.FilterConditionField.Subject, 'name': 'Subject'} - ]; - - this.type = ko.observable(Enums.FilterConditionType.EqualTo); - - this.typeOptions = [ // TODO i18n - {'id': Enums.FilterConditionType.EqualTo, 'name': 'Equal To'}, - {'id': Enums.FilterConditionType.NotEqualTo, 'name': 'Not Equal To'}, - {'id': Enums.FilterConditionType.Contains, 'name': 'Contains'}, - {'id': Enums.FilterConditionType.NotContains, 'name': 'Not Contains'} - ]; - + this.type = ko.observable(Enums.FilterConditionType.Contains); this.value = ko.observable(''); + this.value.error = ko.observable(false); this.template = ko.computed(function () { @@ -50,11 +38,55 @@ return sTemplate; }, this); + + this.regDisposables([this.template]); } - FilterConditionModel.prototype.removeSelf = function () + _.extend(FilterConditionModel.prototype, AbstractModel.prototype); + + FilterConditionModel.prototype.verify = function () { - this.parentList.remove(this); + if ('' === this.value()) + { + this.value.error(true); + return false; + } + + return true; + }; + + FilterConditionModel.prototype.parse = function (oItem) + { + if (oItem && oItem['Field'] && oItem['Type']) + { + this.field(Utils.pString(oItem['Field'])); + this.type(Utils.pString(oItem['Type'])); + this.value(Utils.pString(oItem['Value'])); + + return true; + } + + return false; + }; + + FilterConditionModel.prototype.toJson = function () + { + return { + 'Field': this.field(), + 'Type': this.type(), + 'Value': this.value() + }; + }; + + FilterConditionModel.prototype.cloneSelf = function () + { + var oClone = new FilterConditionModel(); + + oClone.field(this.field()); + oClone.type(this.type()); + oClone.value(this.value()); + + return oClone; }; module.exports = FilterConditionModel; diff --git a/dev/Model/Folder.js b/dev/Model/Folder.js index f5794ffa7..668fec202 100644 --- a/dev/Model/Folder.js +++ b/dev/Model/Folder.js @@ -8,9 +8,13 @@ ko = require('ko'), Enums = require('Common/Enums'), - Globals = require('Common/Globals'), Utils = require('Common/Utils'), - Events = require('Common/Events') + Events = require('Common/Events'), + Translator = require('Common/Translator'), + + Cache = require('Common/Cache'), + + AbstractModel = require('Knoin/AbstractModel') ; /** @@ -18,6 +22,8 @@ */ function FolderModel() { + AbstractModel.call(this, 'FolderModel'); + this.name = ko.observable(''); this.fullName = ''; this.fullNameRaw = ''; @@ -49,6 +55,8 @@ this.collapsedPrivate = ko.observable(true); } + _.extend(FolderModel.prototype, AbstractModel.prototype); + /** * @static * @param {AjaxJsonFolder} oJsonFolder @@ -65,6 +73,12 @@ */ FolderModel.prototype.initComputed = function () { + var sInboxFolderName = Cache.getFolderInboxName(); + + this.isInbox = ko.computed(function () { + return Enums.FolderType.Inbox === this.type(); + }, this); + this.hasSubScribedSubfolders = ko.computed(function () { return !!_.find(this.subFolders(), function (oFolder) { return oFolder.subScribed() && !oFolder.isSystemFolder(); @@ -76,12 +90,14 @@ }, this); this.visible = ko.computed(function () { + var bSubScribed = this.subScribed(), bSubFolders = this.hasSubScribedSubfolders() ; return (bSubScribed || (bSubFolders && (!this.existen || !this.selectable))); + }, this); this.isSystemFolder = ko.computed(function () { @@ -115,7 +131,7 @@ } }, 'owner': this - }); + }).extend({'notify': 'always'}); this.messageCountUnread = ko.computed({ 'read': this.privateMessageCountUnread, @@ -130,7 +146,7 @@ } }, 'owner': this - }); + }).extend({'notify': 'always'}); this.printableUnreadCount = ko.computed(function () { var @@ -159,11 +175,11 @@ var bSystem = this.isSystemFolder() ; - return !bSystem && 0 === this.subFolders().length && 'INBOX' !== this.fullNameRaw; + return !bSystem && 0 === this.subFolders().length && sInboxFolderName !== this.fullNameRaw; }, this); this.canBeSubScribed = ko.computed(function () { - return !this.isSystemFolder() && this.selectable && 'INBOX' !== this.fullNameRaw; + return !this.isSystemFolder() && this.selectable && sInboxFolderName !== this.fullNameRaw; }, this); // this.visible.subscribe(function () { @@ -174,7 +190,7 @@ this.localName = ko.computed(function () { - Globals.langChangeTrigger(); + Translator.trigger(); var iType = this.type(), @@ -186,22 +202,22 @@ switch (iType) { case Enums.FolderType.Inbox: - sName = Utils.i18n('FOLDER_LIST/INBOX_NAME'); + sName = Translator.i18n('FOLDER_LIST/INBOX_NAME'); break; case Enums.FolderType.SentItems: - sName = Utils.i18n('FOLDER_LIST/SENT_NAME'); + sName = Translator.i18n('FOLDER_LIST/SENT_NAME'); break; case Enums.FolderType.Draft: - sName = Utils.i18n('FOLDER_LIST/DRAFTS_NAME'); + sName = Translator.i18n('FOLDER_LIST/DRAFTS_NAME'); break; case Enums.FolderType.Spam: - sName = Utils.i18n('FOLDER_LIST/SPAM_NAME'); + sName = Translator.i18n('FOLDER_LIST/SPAM_NAME'); break; case Enums.FolderType.Trash: - sName = Utils.i18n('FOLDER_LIST/TRASH_NAME'); + sName = Translator.i18n('FOLDER_LIST/TRASH_NAME'); break; case Enums.FolderType.Archive: - sName = Utils.i18n('FOLDER_LIST/ARCHIVE_NAME'); + sName = Translator.i18n('FOLDER_LIST/ARCHIVE_NAME'); break; } } @@ -212,7 +228,7 @@ this.manageFolderSystemName = ko.computed(function () { - Globals.langChangeTrigger(); + Translator.trigger(); var sSuffix = '', @@ -225,22 +241,22 @@ switch (iType) { case Enums.FolderType.Inbox: - sSuffix = '(' + Utils.i18n('FOLDER_LIST/INBOX_NAME') + ')'; + sSuffix = '(' + Translator.i18n('FOLDER_LIST/INBOX_NAME') + ')'; break; case Enums.FolderType.SentItems: - sSuffix = '(' + Utils.i18n('FOLDER_LIST/SENT_NAME') + ')'; + sSuffix = '(' + Translator.i18n('FOLDER_LIST/SENT_NAME') + ')'; break; case Enums.FolderType.Draft: - sSuffix = '(' + Utils.i18n('FOLDER_LIST/DRAFTS_NAME') + ')'; + sSuffix = '(' + Translator.i18n('FOLDER_LIST/DRAFTS_NAME') + ')'; break; case Enums.FolderType.Spam: - sSuffix = '(' + Utils.i18n('FOLDER_LIST/SPAM_NAME') + ')'; + sSuffix = '(' + Translator.i18n('FOLDER_LIST/SPAM_NAME') + ')'; break; case Enums.FolderType.Trash: - sSuffix = '(' + Utils.i18n('FOLDER_LIST/TRASH_NAME') + ')'; + sSuffix = '(' + Translator.i18n('FOLDER_LIST/TRASH_NAME') + ')'; break; case Enums.FolderType.Archive: - sSuffix = '(' + Utils.i18n('FOLDER_LIST/ARCHIVE_NAME') + ')'; + sSuffix = '(' + Translator.i18n('FOLDER_LIST/ARCHIVE_NAME') + ')'; break; } } @@ -319,7 +335,11 @@ */ FolderModel.prototype.initByJson = function (oJsonFolder) { - var bResult = false; + var + bResult = false, + sInboxFolderName = Cache.getFolderInboxName() + ; + if (oJsonFolder && 'Object/Folder' === oJsonFolder['@Object']) { this.name(oJsonFolder.Name); @@ -332,7 +352,7 @@ this.existen = !!oJsonFolder.IsExists; this.subScribed(!!oJsonFolder.IsSubscribed); - this.type('INBOX' === this.fullNameRaw ? Enums.FolderType.Inbox : Enums.FolderType.User); + this.type(sInboxFolderName === this.fullNameRaw ? Enums.FolderType.Inbox : Enums.FolderType.User); bResult = true; } diff --git a/dev/Model/Identity.js b/dev/Model/Identity.js index 0a8f512a1..d28c201a3 100644 --- a/dev/Model/Identity.js +++ b/dev/Model/Identity.js @@ -4,45 +4,47 @@ 'use strict'; var + _ = require('_'), ko = require('ko'), - Utils = require('Common/Utils') + AbstractModel = require('Knoin/AbstractModel') ; /** * @param {string} sId * @param {string} sEmail - * @param {boolean=} bCanBeDelete = true * @constructor */ - function IdentityModel(sId, sEmail, bCanBeDelete) + function IdentityModel(sId, sEmail) { - this.id = sId; + AbstractModel.call(this, 'IdentityModel'); + + this.id = ko.observable(sId); this.email = ko.observable(sEmail); this.name = ko.observable(''); + this.replyTo = ko.observable(''); this.bcc = ko.observable(''); + this.signature = ko.observable(''); + this.signatureInsertBefore = ko.observable(false); + this.deleteAccess = ko.observable(false); - this.canBeDalete = ko.observable(bCanBeDelete); + this.canBeDeleted = ko.computed(function () { + return '' !== this.id(); + }, this); } + _.extend(IdentityModel.prototype, AbstractModel.prototype); + IdentityModel.prototype.formattedName = function () { - var sName = this.name(); - return '' === sName ? this.email() : sName + ' <' + this.email() + '>'; - }; + var + sName = this.name(), + sEmail = this.email() + ; - IdentityModel.prototype.formattedNameForCompose = function () - { - var sName = this.name(); - return '' === sName ? this.email() : sName + ' (' + this.email() + ')'; - }; - - IdentityModel.prototype.formattedNameForEmail = function () - { - var sName = this.name(); - return '' === sName ? this.email() : '"' + Utils.quoteName(sName) + '" <' + this.email() + '>'; + return ('' !== sName) ? sName + ' (' + sEmail + ')' : sEmail; }; module.exports = IdentityModel; diff --git a/dev/Model/Message.js b/dev/Model/Message.js index 7a44ae66f..399e51912 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -8,22 +8,28 @@ _ = require('_'), $ = require('$'), ko = require('ko'), - moment = require('moment'), Enums = require('Common/Enums'), Utils = require('Common/Utils'), Globals = require('Common/Globals'), - LinkBuilder = require('Common/LinkBuilder'), + Links = require('Common/Links'), - EmailModel = require('Model/Email'), - AttachmentModel = require('Model/Attachment') + PgpStore = require('Stores/User/Pgp'), + + AttachmentModel = require('Model/Attachment'), + + MessageHelper = require('Helper/Message'), + + AbstractModel = require('Knoin/AbstractModel') ; /** - * @constructor - */ + * @constructor + */ function MessageModel() { + AbstractModel.call(this, 'MessageModel'); + this.folderFullNameRaw = ''; this.uid = ''; this.hash = ''; @@ -57,6 +63,7 @@ this.newForAnimation = ko.observable(false); this.deleted = ko.observable(false); + this.deletedMark = ko.observable(false); this.unseen = ko.observable(false); this.flagged = ko.observable(false); this.answered = ko.observable(false); @@ -67,48 +74,16 @@ this.selected = ko.observable(false); this.checked = ko.observable(false); this.hasAttachments = ko.observable(false); - this.attachmentsMainType = ko.observable(''); - - this.moment = ko.observable(moment(moment.unix(0))); - + this.attachmentsSpecData = ko.observableArray([]); + this.attachmentIconClass = ko.computed(function () { - var sClass = ''; - if (this.hasAttachments()) - { - sClass = 'icon-attachment'; - switch (this.attachmentsMainType()) - { - case 'image': - sClass = 'icon-image'; - break; - case 'archive': - sClass = 'icon-file-zip'; - break; - case 'doc': - sClass = 'icon-file-text'; - break; - // case 'pdf': - // sClass = 'icon-file-pdf'; - // break; - } - } - return sClass; - }, this); - - this.fullFormatDateValue = ko.computed(function () { - return MessageModel.calculateFullFromatDateValue(this.dateTimeStampInUTC()); - }, this); - - this.momentDate = Utils.createMomentDate(this); - this.momentShortDate = Utils.createMomentShortDate(this); - - this.dateTimeStampInUTC.subscribe(function (iValue) { - var iNow = moment().unix(); - this.moment(moment.unix(iNow < iValue ? iNow : iValue)); + return AttachmentModel.staticCombinedIconClass( + this.hasAttachments() ? this.attachmentsSpecData() : []); }, this); this.body = null; this.plainRaw = ''; + this.isHtml = ko.observable(false); this.hasImages = ko.observable(false); this.attachments = ko.observableArray([]); @@ -126,220 +101,102 @@ this.sInReplyTo = ''; this.sReferences = ''; - this.parentUid = ko.observable(0); - this.threads = ko.observableArray([]); - this.threadsLen = ko.observable(0); this.hasUnseenSubMessage = ko.observable(false); this.hasFlaggedSubMessage = ko.observable(false); - this.lastInCollapsedThread = ko.observable(false); - this.lastInCollapsedThreadLoading = ko.observable(false); + this.threads = ko.observableArray([]); - this.threadsLenResult = ko.computed(function () { - var iCount = this.threadsLen(); - return 0 === this.parentUid() && 0 < iCount ? iCount + 1 : ''; + this.threadsLen = ko.computed(function () { + return this.threads().length; }, this); + + this.isImportant = ko.computed(function () { + return Enums.MessagePriority.High === this.priority(); + }, this); + + this.regDisposables([this.attachmentIconClass, this.threadsLen, this.isImportant]); } + _.extend(MessageModel.prototype, AbstractModel.prototype); + /** - * @static - * @param {AjaxJsonMessage} oJsonMessage - * @return {?MessageModel} - */ + * @static + * @param {AjaxJsonMessage} oJsonMessage + * @return {?MessageModel} + */ MessageModel.newInstanceFromJson = function (oJsonMessage) { var oMessageModel = new MessageModel(); return oMessageModel.initByJson(oJsonMessage) ? oMessageModel : null; }; - /** - * @static - * @param {number} iTimeStampInUTC - * @return {string} - */ - MessageModel.calculateFullFromatDateValue = function (iTimeStampInUTC) - { - return 0 < iTimeStampInUTC ? moment.unix(iTimeStampInUTC).format('LLL') : ''; - }; - - /** - * @static - * @param {Array} aEmail - * @param {boolean=} bFriendlyView - * @param {boolean=} bWrapWithLink = false - * @return {string} - */ - MessageModel.emailsToLine = function (aEmail, bFriendlyView, bWrapWithLink) - { - var - aResult = [], - iIndex = 0, - iLen = 0 - ; - - if (Utils.isNonEmptyArray(aEmail)) - { - for (iIndex = 0, iLen = aEmail.length; iIndex < iLen; iIndex++) - { - aResult.push(aEmail[iIndex].toLine(bFriendlyView, bWrapWithLink)); - } - } - - return aResult.join(', '); - }; - - /** - * @static - * @param {Array} aEmail - * @return {string} - */ - MessageModel.emailsToLineClear = function (aEmail) - { - var - aResult = [], - iIndex = 0, - iLen = 0 - ; - - if (Utils.isNonEmptyArray(aEmail)) - { - for (iIndex = 0, iLen = aEmail.length; iIndex < iLen; iIndex++) - { - if (aEmail[iIndex] && aEmail[iIndex].email && '' !== aEmail[iIndex].name) - { - aResult.push(aEmail[iIndex].email); - } - } - } - - return aResult.join(', '); - }; - - /** - * @static - * @param {?Array} aJsonEmails - * @return {Array.} - */ - MessageModel.initEmailsFromJson = function (aJsonEmails) - { - var - iIndex = 0, - iLen = 0, - oEmailModel = null, - aResult = [] - ; - - if (Utils.isNonEmptyArray(aJsonEmails)) - { - for (iIndex = 0, iLen = aJsonEmails.length; iIndex < iLen; iIndex++) - { - oEmailModel = EmailModel.newInstanceFromJson(aJsonEmails[iIndex]); - if (oEmailModel) - { - aResult.push(oEmailModel); - } - } - } - - return aResult; - }; - - /** - * @static - * @param {Array.} aMessageEmails - * @param {Object} oLocalUnic - * @param {Array} aLocalEmails - */ - MessageModel.replyHelper = function (aMessageEmails, oLocalUnic, aLocalEmails) - { - if (aMessageEmails && 0 < aMessageEmails.length) - { - var - iIndex = 0, - iLen = aMessageEmails.length - ; - - for (; iIndex < iLen; iIndex++) - { - if (Utils.isUnd(oLocalUnic[aMessageEmails[iIndex].email])) - { - oLocalUnic[aMessageEmails[iIndex].email] = true; - aLocalEmails.push(aMessageEmails[iIndex]); - } - } - } - }; - MessageModel.prototype.clear = function () { - this.folderFullNameRaw = ''; - this.uid = ''; - this.hash = ''; - this.requestHash = ''; - this.subject(''); - this.subjectPrefix(''); - this.subjectSuffix(''); - this.size(0); - this.dateTimeStampInUTC(0); - this.priority(Enums.MessagePriority.Normal); + this.folderFullNameRaw = ''; + this.uid = ''; + this.hash = ''; + this.requestHash = ''; + this.subject(''); + this.subjectPrefix(''); + this.subjectSuffix(''); + this.size(0); + this.dateTimeStampInUTC(0); + this.priority(Enums.MessagePriority.Normal); - this.proxy = false; + this.proxy = false; - this.fromEmailString(''); - this.fromClearEmailString(''); - this.toEmailsString(''); - this.toClearEmailsString(''); - this.senderEmailsString(''); - this.senderClearEmailsString(''); + this.fromEmailString(''); + this.fromClearEmailString(''); + this.toEmailsString(''); + this.toClearEmailsString(''); + this.senderEmailsString(''); + this.senderClearEmailsString(''); - this.emails = []; + this.emails = []; - this.from = []; - this.to = []; - this.cc = []; - this.bcc = []; - this.replyTo = []; - this.deliveredTo = []; + this.from = []; + this.to = []; + this.cc = []; + this.bcc = []; + this.replyTo = []; + this.deliveredTo = []; - this.newForAnimation(false); + this.newForAnimation(false); - this.deleted(false); - this.unseen(false); - this.flagged(false); - this.answered(false); - this.forwarded(false); - this.isReadReceipt(false); + this.deleted(false); + this.deletedMark(false); + this.unseen(false); + this.flagged(false); + this.answered(false); + this.forwarded(false); + this.isReadReceipt(false); - this.selected(false); - this.checked(false); - this.hasAttachments(false); - this.attachmentsMainType(''); + this.selected(false); + this.checked(false); + this.hasAttachments(false); + this.attachmentsSpecData([]); - this.body = null; - this.isHtml(false); - this.hasImages(false); - this.attachments([]); + this.body = null; + this.isHtml(false); + this.hasImages(false); + this.attachments([]); - this.isPgpSigned(false); - this.isPgpEncrypted(false); - this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None); - this.pgpSignedVerifyUser(''); + this.isPgpSigned(false); + this.isPgpEncrypted(false); + this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None); + this.pgpSignedVerifyUser(''); - this.priority(Enums.MessagePriority.Normal); - this.readReceipt(''); - this.aDraftInfo = []; - this.sMessageId = ''; - this.sInReplyTo = ''; - this.sReferences = ''; + this.priority(Enums.MessagePriority.Normal); + this.readReceipt(''); + this.aDraftInfo = []; + this.sMessageId = ''; + this.sInReplyTo = ''; + this.sReferences = ''; - this.parentUid(0); - this.threads([]); - this.threadsLen(0); - this.hasUnseenSubMessage(false); - this.hasFlaggedSubMessage(false); + this.threads([]); - this.lastInCollapsedThread(false); - this.lastInCollapsedThreadLoading(false); + this.hasUnseenSubMessage(false); + this.hasFlaggedSubMessage(false); }; /** @@ -353,9 +210,8 @@ MessageModel.prototype.computeSenderEmail = function () { var - Data = require('Storage/App/Data'), - sSent = Data.sentFolder(), - sDraft = Data.draftFolder() + sSent = require('Stores/User/Folder').sentFolder(), + sDraft = require('Stores/User/Folder').draftFolder() ; this.senderEmailsString(this.folderFullNameRaw === sSent || this.folderFullNameRaw === sDraft ? @@ -366,743 +222,784 @@ }; /** - * @param {AjaxJsonMessage} oJsonMessage - * @return {boolean} - */ + * @param {AjaxJsonMessage} oJsonMessage + * @return {boolean} + */ MessageModel.prototype.initByJson = function (oJsonMessage) { - var bResult = false; - if (oJsonMessage && 'Object/Message' === oJsonMessage['@Object']) - { - this.folderFullNameRaw = oJsonMessage.Folder; - this.uid = oJsonMessage.Uid; - this.hash = oJsonMessage.Hash; - this.requestHash = oJsonMessage.RequestHash; + var + bResult = false, + iPriority = Enums.MessagePriority.Normal + ; - this.proxy = !!oJsonMessage.ExternalProxy; + if (oJsonMessage && 'Object/Message' === oJsonMessage['@Object']) + { + iPriority = Utils.pInt(oJsonMessage.Priority); + this.priority(-1 < Utils.inArray(iPriority, [Enums.MessagePriority.High, Enums.MessagePriority.Low]) ? + iPriority : Enums.MessagePriority.Normal); - this.size(Utils.pInt(oJsonMessage.Size)); + this.folderFullNameRaw = oJsonMessage.Folder; + this.uid = oJsonMessage.Uid; + this.hash = oJsonMessage.Hash; + this.requestHash = oJsonMessage.RequestHash; - this.from = MessageModel.initEmailsFromJson(oJsonMessage.From); - this.to = MessageModel.initEmailsFromJson(oJsonMessage.To); - this.cc = MessageModel.initEmailsFromJson(oJsonMessage.Cc); - this.bcc = MessageModel.initEmailsFromJson(oJsonMessage.Bcc); - this.replyTo = MessageModel.initEmailsFromJson(oJsonMessage.ReplyTo); - this.deliveredTo = MessageModel.initEmailsFromJson(oJsonMessage.DeliveredTo); + this.proxy = !!oJsonMessage.ExternalProxy; - this.subject(oJsonMessage.Subject); - if (Utils.isArray(oJsonMessage.SubjectParts)) - { - this.subjectPrefix(oJsonMessage.SubjectParts[0]); - this.subjectSuffix(oJsonMessage.SubjectParts[1]); - } - else - { - this.subjectPrefix(''); - this.subjectSuffix(this.subject()); - } + this.size(Utils.pInt(oJsonMessage.Size)); - this.dateTimeStampInUTC(Utils.pInt(oJsonMessage.DateTimeStampInUTC)); - this.hasAttachments(!!oJsonMessage.HasAttachments); - this.attachmentsMainType(oJsonMessage.AttachmentsMainType); + this.from = MessageHelper.emailArrayFromJson(oJsonMessage.From); + this.to = MessageHelper.emailArrayFromJson(oJsonMessage.To); + this.cc = MessageHelper.emailArrayFromJson(oJsonMessage.Cc); + this.bcc = MessageHelper.emailArrayFromJson(oJsonMessage.Bcc); + this.replyTo = MessageHelper.emailArrayFromJson(oJsonMessage.ReplyTo); + this.deliveredTo = MessageHelper.emailArrayFromJson(oJsonMessage.DeliveredTo); - this.fromEmailString(MessageModel.emailsToLine(this.from, true)); - this.fromClearEmailString(MessageModel.emailsToLineClear(this.from)); - this.toEmailsString(MessageModel.emailsToLine(this.to, true)); - this.toClearEmailsString(MessageModel.emailsToLineClear(this.to)); + this.subject(oJsonMessage.Subject); + if (Utils.isArray(oJsonMessage.SubjectParts)) + { + this.subjectPrefix(oJsonMessage.SubjectParts[0]); + this.subjectSuffix(oJsonMessage.SubjectParts[1]); + } + else + { + this.subjectPrefix(''); + this.subjectSuffix(this.subject()); + } - this.parentUid(Utils.pInt(oJsonMessage.ParentThread)); - this.threads(Utils.isArray(oJsonMessage.Threads) ? oJsonMessage.Threads : []); - this.threadsLen(Utils.pInt(oJsonMessage.ThreadsLen)); + this.dateTimeStampInUTC(Utils.pInt(oJsonMessage.DateTimeStampInUTC)); + this.hasAttachments(!!oJsonMessage.HasAttachments); + this.attachmentsSpecData(Utils.isArray(oJsonMessage.AttachmentsSpecData) ? + oJsonMessage.AttachmentsSpecData : []); - this.initFlagsByJson(oJsonMessage); - this.computeSenderEmail(); + this.fromEmailString(MessageHelper.emailArrayToString(this.from, true)); + this.fromClearEmailString(MessageHelper.emailArrayToStringClear(this.from)); + this.toEmailsString(MessageHelper.emailArrayToString(this.to, true)); + this.toClearEmailsString(MessageHelper.emailArrayToStringClear(this.to)); - bResult = true; - } + this.threads(Utils.isArray(oJsonMessage.Threads) ? oJsonMessage.Threads : []); - return bResult; + this.initFlagsByJson(oJsonMessage); + this.computeSenderEmail(); + + bResult = true; + } + + return bResult; }; /** - * @param {AjaxJsonMessage} oJsonMessage - * @return {boolean} - */ + * @param {AjaxJsonMessage} oJsonMessage + * @return {boolean} + */ MessageModel.prototype.initUpdateByMessageJson = function (oJsonMessage) { - var - bResult = false, - Data = require('Storage/App/Data'), - iPriority = Enums.MessagePriority.Normal - ; + var + bResult = false, + iPriority = Enums.MessagePriority.Normal + ; - if (oJsonMessage && 'Object/Message' === oJsonMessage['@Object']) - { - iPriority = Utils.pInt(oJsonMessage.Priority); - this.priority(-1 < Utils.inArray(iPriority, [Enums.MessagePriority.High, Enums.MessagePriority.Low]) ? - iPriority : Enums.MessagePriority.Normal); + if (oJsonMessage && 'Object/Message' === oJsonMessage['@Object']) + { + iPriority = Utils.pInt(oJsonMessage.Priority); + this.priority(-1 < Utils.inArray(iPriority, [Enums.MessagePriority.High, Enums.MessagePriority.Low]) ? + iPriority : Enums.MessagePriority.Normal); - this.aDraftInfo = oJsonMessage.DraftInfo; + this.aDraftInfo = oJsonMessage.DraftInfo; - this.sMessageId = oJsonMessage.MessageId; - this.sInReplyTo = oJsonMessage.InReplyTo; - this.sReferences = oJsonMessage.References; + this.sMessageId = oJsonMessage.MessageId; + this.sInReplyTo = oJsonMessage.InReplyTo; + this.sReferences = oJsonMessage.References; - this.proxy = !!oJsonMessage.ExternalProxy; + this.proxy = !!oJsonMessage.ExternalProxy; - if (Data.capaOpenPGP()) - { - this.isPgpSigned(!!oJsonMessage.PgpSigned); - this.isPgpEncrypted(!!oJsonMessage.PgpEncrypted); - } + if (require('Stores/User/Pgp').capaOpenPGP()) + { + this.isPgpSigned(!!oJsonMessage.PgpSigned); + this.isPgpEncrypted(!!oJsonMessage.PgpEncrypted); + } - this.hasAttachments(!!oJsonMessage.HasAttachments); - this.attachmentsMainType(oJsonMessage.AttachmentsMainType); + this.hasAttachments(!!oJsonMessage.HasAttachments); + this.attachmentsSpecData(Utils.isArray(oJsonMessage.AttachmentsSpecData) ? + oJsonMessage.AttachmentsSpecData : []); - this.foundedCIDs = Utils.isArray(oJsonMessage.FoundedCIDs) ? oJsonMessage.FoundedCIDs : []; - this.attachments(this.initAttachmentsFromJson(oJsonMessage.Attachments)); + this.foundedCIDs = Utils.isArray(oJsonMessage.FoundedCIDs) ? oJsonMessage.FoundedCIDs : []; + this.attachments(this.initAttachmentsFromJson(oJsonMessage.Attachments)); - this.readReceipt(oJsonMessage.ReadReceipt || ''); + this.readReceipt(oJsonMessage.ReadReceipt || ''); - this.computeSenderEmail(); + this.computeSenderEmail(); - bResult = true; - } + bResult = true; + } - return bResult; + return bResult; }; /** - * @param {(AjaxJsonAttachment|null)} oJsonAttachments - * @return {Array} - */ + * @param {(AjaxJsonAttachment|null)} oJsonAttachments + * @return {Array} + */ MessageModel.prototype.initAttachmentsFromJson = function (oJsonAttachments) { - var - iIndex = 0, - iLen = 0, - oAttachmentModel = null, - aResult = [] - ; + var + iIndex = 0, + iLen = 0, + oAttachmentModel = null, + aResult = [] + ; - if (oJsonAttachments && 'Collection/AttachmentCollection' === oJsonAttachments['@Object'] && - Utils.isNonEmptyArray(oJsonAttachments['@Collection'])) - { - for (iIndex = 0, iLen = oJsonAttachments['@Collection'].length; iIndex < iLen; iIndex++) - { - oAttachmentModel = AttachmentModel.newInstanceFromJson(oJsonAttachments['@Collection'][iIndex]); - if (oAttachmentModel) - { - if ('' !== oAttachmentModel.cidWithOutTags && 0 < this.foundedCIDs.length && - 0 <= Utils.inArray(oAttachmentModel.cidWithOutTags, this.foundedCIDs)) - { - oAttachmentModel.isLinked = true; - } + if (oJsonAttachments && 'Collection/AttachmentCollection' === oJsonAttachments['@Object'] && + Utils.isNonEmptyArray(oJsonAttachments['@Collection'])) + { + for (iIndex = 0, iLen = oJsonAttachments['@Collection'].length; iIndex < iLen; iIndex++) + { + oAttachmentModel = AttachmentModel.newInstanceFromJson(oJsonAttachments['@Collection'][iIndex]); + if (oAttachmentModel) + { + if ('' !== oAttachmentModel.cidWithOutTags && 0 < this.foundedCIDs.length && + 0 <= Utils.inArray(oAttachmentModel.cidWithOutTags, this.foundedCIDs)) + { + oAttachmentModel.isLinked = true; + } - aResult.push(oAttachmentModel); - } - } - } + aResult.push(oAttachmentModel); + } + } + } - return aResult; + return aResult; }; /** - * @param {AjaxJsonMessage} oJsonMessage - * @return {boolean} - */ + * @param {AjaxJsonMessage} oJsonMessage + * @return {boolean} + */ MessageModel.prototype.initFlagsByJson = function (oJsonMessage) { - var bResult = false; + var bResult = false; - if (oJsonMessage && 'Object/Message' === oJsonMessage['@Object']) - { - this.unseen(!oJsonMessage.IsSeen); - this.flagged(!!oJsonMessage.IsFlagged); - this.answered(!!oJsonMessage.IsAnswered); - this.forwarded(!!oJsonMessage.IsForwarded); - this.isReadReceipt(!!oJsonMessage.IsReadReceipt); + if (oJsonMessage && 'Object/Message' === oJsonMessage['@Object']) + { + this.unseen(!oJsonMessage.IsSeen); + this.flagged(!!oJsonMessage.IsFlagged); + this.answered(!!oJsonMessage.IsAnswered); + this.forwarded(!!oJsonMessage.IsForwarded); + this.isReadReceipt(!!oJsonMessage.IsReadReceipt); + this.deletedMark(!!oJsonMessage.IsDeleted); - bResult = true; - } + bResult = true; + } - return bResult; + return bResult; }; /** - * @param {boolean} bFriendlyView - * @param {boolean=} bWrapWithLink = false - * @return {string} - */ + * @param {boolean} bFriendlyView + * @param {boolean=} bWrapWithLink = false + * @return {string} + */ MessageModel.prototype.fromToLine = function (bFriendlyView, bWrapWithLink) { - return MessageModel.emailsToLine(this.from, bFriendlyView, bWrapWithLink); + return MessageHelper.emailArrayToString(this.from, bFriendlyView, bWrapWithLink); }; /** - * @param {boolean} bFriendlyView - * @param {boolean=} bWrapWithLink = false - * @return {string} - */ + * @return {string} + */ + MessageModel.prototype.fromDkimData = function () + { + var aResult = ['none', '']; + if (Utils.isNonEmptyArray(this.from) && 1 === this.from.length && + this.from[0] && this.from[0].dkimStatus) + { + aResult = [this.from[0].dkimStatus, this.from[0].dkimValue || '']; + } + + return aResult; + }; + + /** + * @param {boolean} bFriendlyView + * @param {boolean=} bWrapWithLink = false + * @return {string} + */ MessageModel.prototype.toToLine = function (bFriendlyView, bWrapWithLink) { - return MessageModel.emailsToLine(this.to, bFriendlyView, bWrapWithLink); + return MessageHelper.emailArrayToString(this.to, bFriendlyView, bWrapWithLink); }; /** - * @param {boolean} bFriendlyView - * @param {boolean=} bWrapWithLink = false - * @return {string} - */ + * @param {boolean} bFriendlyView + * @param {boolean=} bWrapWithLink = false + * @return {string} + */ MessageModel.prototype.ccToLine = function (bFriendlyView, bWrapWithLink) { - return MessageModel.emailsToLine(this.cc, bFriendlyView, bWrapWithLink); + return MessageHelper.emailArrayToString(this.cc, bFriendlyView, bWrapWithLink); }; /** - * @param {boolean} bFriendlyView - * @param {boolean=} bWrapWithLink = false - * @return {string} - */ + * @param {boolean} bFriendlyView + * @param {boolean=} bWrapWithLink = false + * @return {string} + */ MessageModel.prototype.bccToLine = function (bFriendlyView, bWrapWithLink) { - return MessageModel.emailsToLine(this.bcc, bFriendlyView, bWrapWithLink); + return MessageHelper.emailArrayToString(this.bcc, bFriendlyView, bWrapWithLink); }; /** - * @return string - */ - MessageModel.prototype.lineAsCcc = function () + * @param {boolean} bFriendlyView + * @param {boolean=} bWrapWithLink = false + * @return {string} + */ + MessageModel.prototype.replyToToLine = function (bFriendlyView, bWrapWithLink) { - var aResult = []; - if (this.deleted()) - { - aResult.push('deleted'); - } - if (this.selected()) - { - aResult.push('selected'); - } - if (this.checked()) - { - aResult.push('checked'); - } - if (this.flagged()) - { - aResult.push('flagged'); - } - if (this.unseen()) - { - aResult.push('unseen'); - } - if (this.answered()) - { - aResult.push('answered'); - } - if (this.forwarded()) - { - aResult.push('forwarded'); - } - if (this.focused()) - { - aResult.push('focused'); - } - if (this.hasAttachments()) - { - aResult.push('withAttachments'); - switch (this.attachmentsMainType()) - { - case 'image': - aResult.push('imageOnlyAttachments'); - break; - case 'archive': - aResult.push('archiveOnlyAttachments'); - break; - } - } - if (this.newForAnimation()) - { - aResult.push('new'); - } - if ('' === this.subject()) - { - aResult.push('emptySubject'); - } - if (0 < this.parentUid()) - { - aResult.push('hasParentMessage'); - } - if (0 < this.threadsLen() && 0 === this.parentUid()) - { - aResult.push('hasChildrenMessage'); - } - if (this.hasUnseenSubMessage()) - { - aResult.push('hasUnseenSubMessage'); - } - if (this.hasFlaggedSubMessage()) - { - aResult.push('hasFlaggedSubMessage'); - } - - return aResult.join(' '); + return MessageHelper.emailArrayToString(this.replyTo, bFriendlyView, bWrapWithLink); }; /** - * @return {boolean} - */ + * @return string + */ + MessageModel.prototype.lineAsCss = function () + { + var aResult = []; + if (this.deleted()) + { + aResult.push('deleted'); + } + if (this.deletedMark()) + { + aResult.push('deleted-mark'); + } + if (this.selected()) + { + aResult.push('selected'); + } + if (this.checked()) + { + aResult.push('checked'); + } + if (this.flagged()) + { + aResult.push('flagged'); + } + if (this.unseen()) + { + aResult.push('unseen'); + } + if (this.answered()) + { + aResult.push('answered'); + } + if (this.forwarded()) + { + aResult.push('forwarded'); + } + if (this.focused()) + { + aResult.push('focused'); + } + if (this.isImportant()) + { + aResult.push('important'); + } + if (this.hasAttachments()) + { + aResult.push('withAttachments'); + } + if (this.newForAnimation()) + { + aResult.push('new'); + } + if ('' === this.subject()) + { + aResult.push('emptySubject'); + } +// if (1 < this.threadsLen()) +// { +// aResult.push('hasChildrenMessage'); +// } + if (this.hasUnseenSubMessage()) + { + aResult.push('hasUnseenSubMessage'); + } + if (this.hasFlaggedSubMessage()) + { + aResult.push('hasFlaggedSubMessage'); + } + + return aResult.join(' '); + }; + + /** + * @return {boolean} + */ MessageModel.prototype.hasVisibleAttachments = function () { - return !!_.find(this.attachments(), function (oAttachment) { - return !oAttachment.isLinked; - }); + return !!_.find(this.attachments(), function (oAttachment) { + return !oAttachment.isLinked; + }); }; /** - * @param {string} sCid - * @return {*} - */ + * @param {string} sCid + * @return {*} + */ MessageModel.prototype.findAttachmentByCid = function (sCid) { - var - oResult = null, - aAttachments = this.attachments() - ; + var + oResult = null, + aAttachments = this.attachments() + ; - if (Utils.isNonEmptyArray(aAttachments)) - { - sCid = sCid.replace(/^<+/, '').replace(/>+$/, ''); - oResult = _.find(aAttachments, function (oAttachment) { - return sCid === oAttachment.cidWithOutTags; - }); - } + if (Utils.isNonEmptyArray(aAttachments)) + { + sCid = sCid.replace(/^<+/, '').replace(/>+$/, ''); + oResult = _.find(aAttachments, function (oAttachment) { + return sCid === oAttachment.cidWithOutTags; + }); + } - return oResult || null; + return oResult || null; }; /** - * @param {string} sContentLocation - * @return {*} - */ + * @param {string} sContentLocation + * @return {*} + */ MessageModel.prototype.findAttachmentByContentLocation = function (sContentLocation) { - var - oResult = null, - aAttachments = this.attachments() - ; + var + oResult = null, + aAttachments = this.attachments() + ; - if (Utils.isNonEmptyArray(aAttachments)) - { - oResult = _.find(aAttachments, function (oAttachment) { - return sContentLocation === oAttachment.contentLocation; - }); - } + if (Utils.isNonEmptyArray(aAttachments)) + { + oResult = _.find(aAttachments, function (oAttachment) { + return sContentLocation === oAttachment.contentLocation; + }); + } - return oResult || null; + return oResult || null; }; /** - * @return {string} - */ + * @return {string} + */ MessageModel.prototype.messageId = function () { - return this.sMessageId; + return this.sMessageId; }; /** - * @return {string} - */ + * @return {string} + */ MessageModel.prototype.inReplyTo = function () { - return this.sInReplyTo; + return this.sInReplyTo; }; /** - * @return {string} - */ + * @return {string} + */ MessageModel.prototype.references = function () { - return this.sReferences; + return this.sReferences; }; /** - * @return {string} - */ + * @return {string} + */ MessageModel.prototype.fromAsSingleEmail = function () { - return Utils.isArray(this.from) && this.from[0] ? this.from[0].email : ''; + return Utils.isArray(this.from) && this.from[0] ? this.from[0].email : ''; }; /** - * @return {string} - */ + * @return {string} + */ MessageModel.prototype.viewLink = function () { - return LinkBuilder.messageViewLink(this.requestHash); + return Links.messageViewLink(this.requestHash); }; /** - * @return {string} - */ + * @return {string} + */ MessageModel.prototype.downloadLink = function () { - return LinkBuilder.messageDownloadLink(this.requestHash); + return Links.messageDownloadLink(this.requestHash); }; /** - * @param {Object} oExcludeEmails - * @return {Array} - */ - MessageModel.prototype.replyEmails = function (oExcludeEmails) + * @param {Object} oExcludeEmails + * @param {boolean=} bLast = false + * @return {Array} + */ + MessageModel.prototype.replyEmails = function (oExcludeEmails, bLast) { - var - aResult = [], - oUnic = Utils.isUnd(oExcludeEmails) ? {} : oExcludeEmails - ; + var + aResult = [], + oUnic = Utils.isUnd(oExcludeEmails) ? {} : oExcludeEmails + ; - MessageModel.replyHelper(this.replyTo, oUnic, aResult); - if (0 === aResult.length) - { - MessageModel.replyHelper(this.from, oUnic, aResult); - } + MessageHelper.replyHelper(this.replyTo, oUnic, aResult); + if (0 === aResult.length) + { + MessageHelper.replyHelper(this.from, oUnic, aResult); + } - return aResult; + if (0 === aResult.length && !bLast) + { + return this.replyEmails({}, true); + } + + return aResult; }; /** - * @param {Object} oExcludeEmails - * @return {Array.} - */ - MessageModel.prototype.replyAllEmails = function (oExcludeEmails) + * @param {Object} oExcludeEmails + * @param {boolean=} bLast = false + * @return {Array.} + */ + MessageModel.prototype.replyAllEmails = function (oExcludeEmails, bLast) { - var - aToResult = [], - aCcResult = [], - oUnic = Utils.isUnd(oExcludeEmails) ? {} : oExcludeEmails - ; + var + aData = [], + aToResult = [], + aCcResult = [], + oUnic = Utils.isUnd(oExcludeEmails) ? {} : oExcludeEmails + ; - MessageModel.replyHelper(this.replyTo, oUnic, aToResult); - if (0 === aToResult.length) - { - MessageModel.replyHelper(this.from, oUnic, aToResult); - } + MessageHelper.replyHelper(this.replyTo, oUnic, aToResult); + if (0 === aToResult.length) + { + MessageHelper.replyHelper(this.from, oUnic, aToResult); + } - MessageModel.replyHelper(this.to, oUnic, aToResult); - MessageModel.replyHelper(this.cc, oUnic, aCcResult); + MessageHelper.replyHelper(this.to, oUnic, aToResult); + MessageHelper.replyHelper(this.cc, oUnic, aCcResult); - return [aToResult, aCcResult]; + if (0 === aToResult.length && !bLast) + { + aData = this.replyAllEmails({}, true); + return [aData[0], aCcResult]; + } + + return [aToResult, aCcResult]; }; /** - * @return {string} - */ + * @return {string} + */ MessageModel.prototype.textBodyToString = function () { - return this.body ? this.body.html() : ''; + return this.body ? this.body.html() : ''; }; /** - * @return {string} - */ + * @return {string} + */ MessageModel.prototype.attachmentsToStringLine = function () { - var aAttachLines = _.map(this.attachments(), function (oItem) { - return oItem.fileName + ' (' + oItem.friendlySize + ')'; - }); + var aAttachLines = _.map(this.attachments(), function (oItem) { + return oItem.fileName + ' (' + oItem.friendlySize + ')'; + }); - return aAttachLines && 0 < aAttachLines.length ? aAttachLines.join(', ') : ''; + return aAttachLines && 0 < aAttachLines.length ? aAttachLines.join(', ') : ''; }; /** - * @return {Object} - */ + * @return {Object} + */ MessageModel.prototype.getDataForWindowPopup = function () { - return { - 'popupFrom': this.fromToLine(false), - 'popupTo': this.toToLine(false), - 'popupCc': this.ccToLine(false), - 'popupBcc': this.bccToLine(false), - 'popupSubject': this.subject(), - 'popupIsHtml': this.isHtml(), - 'popupDate': this.fullFormatDateValue(), - 'popupAttachments': this.attachmentsToStringLine(), - 'popupBody': this.textBodyToString() - }; + return { + 'popupFrom': this.fromToLine(false), + 'popupTo': this.toToLine(false), + 'popupCc': this.ccToLine(false), + 'popupBcc': this.bccToLine(false), + 'popupReplyTo': this.replyToToLine(false), + 'popupSubject': this.subject(), + 'popupIsHtml': this.isHtml(), + 'popupDate': require('Common/Momentor').format(this.dateTimeStampInUTC(), 'FULL'), + 'popupAttachments': this.attachmentsToStringLine(), + 'popupBody': this.textBodyToString() + }; }; /** - * @param {boolean=} bPrint = false - */ + * @param {boolean=} bPrint = false + */ MessageModel.prototype.viewPopupMessage = function (bPrint) { - Utils.windowPopupKnockout(this.getDataForWindowPopup(), 'PopupsWindowSimpleMessage', this.subject(), function (oPopupWin) { - if (oPopupWin && oPopupWin.document && oPopupWin.document.body) - { - $('img.lazy', oPopupWin.document.body).each(function (iIndex, oImg) { + Utils.windowPopupKnockout(this.getDataForWindowPopup(), 'PopupsWindowSimpleMessage', + this.subject(), function (oPopupWin) + { + if (oPopupWin && oPopupWin.document && oPopupWin.document.body) + { + $('img.lazy', oPopupWin.document.body).each(function (iIndex, oImg) { - var - $oImg = $(oImg), - sOrig = $oImg.data('original'), - sSrc = $oImg.attr('src') - ; + var + $oImg = $(oImg), + sOrig = $oImg.data('original'), + sSrc = $oImg.attr('src') + ; - if (0 <= iIndex && sOrig && !sSrc) - { - $oImg.attr('src', sOrig); - } - }); + if (0 <= iIndex && sOrig && !sSrc) + { + $oImg.attr('src', sOrig); + } + }); - if (bPrint) - { - window.setTimeout(function () { - oPopupWin.print(); - }, 100); - } - } - }); + if (bPrint) + { + window.setTimeout(function () { + oPopupWin.print(); + }, 100); + } + } + }); }; MessageModel.prototype.printMessage = function () { - this.viewPopupMessage(true); + this.viewPopupMessage(true); }; /** - * @returns {string} - */ + * @return {string} + */ MessageModel.prototype.generateUid = function () { - return this.folderFullNameRaw + '/' + this.uid; + return this.folderFullNameRaw + '/' + this.uid; }; /** - * @param {MessageModel} oMessage - * @return {MessageModel} - */ + * @param {MessageModel} oMessage + * @return {MessageModel} + */ MessageModel.prototype.populateByMessageListItem = function (oMessage) { - this.folderFullNameRaw = oMessage.folderFullNameRaw; - this.uid = oMessage.uid; - this.hash = oMessage.hash; - this.requestHash = oMessage.requestHash; - this.subject(oMessage.subject()); - this.subjectPrefix(this.subjectPrefix()); - this.subjectSuffix(this.subjectSuffix()); + this.folderFullNameRaw = oMessage.folderFullNameRaw; + this.uid = oMessage.uid; + this.hash = oMessage.hash; + this.requestHash = oMessage.requestHash; + this.subject(oMessage.subject()); + this.subjectPrefix(this.subjectPrefix()); + this.subjectSuffix(this.subjectSuffix()); - this.size(oMessage.size()); - this.dateTimeStampInUTC(oMessage.dateTimeStampInUTC()); - this.priority(oMessage.priority()); + this.size(oMessage.size()); + this.dateTimeStampInUTC(oMessage.dateTimeStampInUTC()); + this.priority(oMessage.priority()); - this.proxy = oMessage.proxy; + this.proxy = oMessage.proxy; - this.fromEmailString(oMessage.fromEmailString()); - this.fromClearEmailString(oMessage.fromClearEmailString()); - this.toEmailsString(oMessage.toEmailsString()); - this.toClearEmailsString(oMessage.toClearEmailsString()); + this.fromEmailString(oMessage.fromEmailString()); + this.fromClearEmailString(oMessage.fromClearEmailString()); + this.toEmailsString(oMessage.toEmailsString()); + this.toClearEmailsString(oMessage.toClearEmailsString()); - this.emails = oMessage.emails; + this.emails = oMessage.emails; - this.from = oMessage.from; - this.to = oMessage.to; - this.cc = oMessage.cc; - this.bcc = oMessage.bcc; - this.replyTo = oMessage.replyTo; - this.deliveredTo = oMessage.deliveredTo; + this.from = oMessage.from; + this.to = oMessage.to; + this.cc = oMessage.cc; + this.bcc = oMessage.bcc; + this.replyTo = oMessage.replyTo; + this.deliveredTo = oMessage.deliveredTo; - this.unseen(oMessage.unseen()); - this.flagged(oMessage.flagged()); - this.answered(oMessage.answered()); - this.forwarded(oMessage.forwarded()); - this.isReadReceipt(oMessage.isReadReceipt()); + this.unseen(oMessage.unseen()); + this.flagged(oMessage.flagged()); + this.answered(oMessage.answered()); + this.forwarded(oMessage.forwarded()); + this.isReadReceipt(oMessage.isReadReceipt()); + this.deletedMark(oMessage.deletedMark()); - this.selected(oMessage.selected()); - this.checked(oMessage.checked()); - this.hasAttachments(oMessage.hasAttachments()); - this.attachmentsMainType(oMessage.attachmentsMainType()); + this.priority(oMessage.priority()); - this.moment(oMessage.moment()); + this.selected(oMessage.selected()); + this.checked(oMessage.checked()); + this.hasAttachments(oMessage.hasAttachments()); + this.attachmentsSpecData(oMessage.attachmentsSpecData()); - this.body = null; + this.body = null; - this.priority(Enums.MessagePriority.Normal); - this.aDraftInfo = []; - this.sMessageId = ''; - this.sInReplyTo = ''; - this.sReferences = ''; + this.aDraftInfo = []; + this.sMessageId = ''; + this.sInReplyTo = ''; + this.sReferences = ''; - this.parentUid(oMessage.parentUid()); - this.threads(oMessage.threads()); - this.threadsLen(oMessage.threadsLen()); + this.threads(oMessage.threads()); - this.computeSenderEmail(); + this.computeSenderEmail(); - return this; + return this; }; MessageModel.prototype.showExternalImages = function (bLazy) { - if (this.body && this.body.data('rl-has-images')) - { - var sAttr = ''; - bLazy = Utils.isUnd(bLazy) ? false : bLazy; + if (this.body && this.body.data('rl-has-images')) + { + var sAttr = ''; + bLazy = Utils.isUnd(bLazy) ? false : bLazy; - this.hasImages(false); - this.body.data('rl-has-images', false); + this.hasImages(false); + this.body.data('rl-has-images', false); - sAttr = this.proxy ? 'data-x-additional-src' : 'data-x-src'; - $('[' + sAttr + ']', this.body).each(function () { - if (bLazy && $(this).is('img')) - { - $(this) - .addClass('lazy') - .attr('data-original', $(this).attr(sAttr)) - .removeAttr(sAttr) - ; - } - else - { - $(this).attr('src', $(this).attr(sAttr)).removeAttr(sAttr); - } - }); + sAttr = this.proxy ? 'data-x-additional-src' : 'data-x-src'; + $('[' + sAttr + ']', this.body).each(function () { + if (bLazy && $(this).is('img')) + { + $(this) + .addClass('lazy') + .attr('data-original', $(this).attr(sAttr)) + .removeAttr(sAttr) + ; + } + else + { + $(this).attr('src', $(this).attr(sAttr)).removeAttr(sAttr); + } + }); - sAttr = this.proxy ? 'data-x-additional-style-url' : 'data-x-style-url'; - $('[' + sAttr + ']', this.body).each(function () { - var sStyle = Utils.trim($(this).attr('style')); - sStyle = '' === sStyle ? '' : (';' === sStyle.substr(-1) ? sStyle + ' ' : sStyle + '; '); - $(this).attr('style', sStyle + $(this).attr(sAttr)).removeAttr(sAttr); - }); + sAttr = this.proxy ? 'data-x-additional-style-url' : 'data-x-style-url'; + $('[' + sAttr + ']', this.body).each(function () { + var sStyle = Utils.trim($(this).attr('style')); + sStyle = '' === sStyle ? '' : (';' === sStyle.substr(-1) ? sStyle + ' ' : sStyle + '; '); + $(this).attr('style', sStyle + $(this).attr(sAttr)).removeAttr(sAttr); + }); - if (bLazy) - { - $('img.lazy', this.body).addClass('lazy-inited').lazyload({ - 'threshold' : 400, - 'effect' : 'fadeIn', - 'skip_invisible' : false, - 'container': $('.RL-MailMessageView .messageView .messageItem .content')[0] - }); + if (bLazy) + { + $('img.lazy', this.body).addClass('lazy-inited').lazyload({ + 'threshold': 400, + 'effect': 'fadeIn', + 'skip_invisible': false, + 'container': $('.RL-MailMessageView .messageView .messageItem .content')[0] + }); - Globals.$win.resize(); - } + Globals.$win.resize(); + } - Utils.windowResize(500); - } + Utils.windowResize(500); + } }; MessageModel.prototype.showInternalImages = function (bLazy) { - if (this.body && !this.body.data('rl-init-internal-images')) - { - this.body.data('rl-init-internal-images', true); + if (this.body && !this.body.data('rl-init-internal-images')) + { + this.body.data('rl-init-internal-images', true); - bLazy = Utils.isUnd(bLazy) ? false : bLazy; + bLazy = Utils.isUnd(bLazy) ? false : bLazy; - var self = this; + var self = this; - $('[data-x-src-cid]', this.body).each(function () { + $('[data-x-src-cid]', this.body).each(function () { - var oAttachment = self.findAttachmentByCid($(this).attr('data-x-src-cid')); - if (oAttachment && oAttachment.download) - { - if (bLazy && $(this).is('img')) - { - $(this) - .addClass('lazy') - .attr('data-original', oAttachment.linkPreview()); - } - else - { - $(this).attr('src', oAttachment.linkPreview()); - } - } - }); + var oAttachment = self.findAttachmentByCid($(this).attr('data-x-src-cid')); + if (oAttachment && oAttachment.download) + { + if (bLazy && $(this).is('img')) + { + $(this) + .addClass('lazy') + .attr('data-original', oAttachment.linkPreview()); + } + else + { + $(this).attr('src', oAttachment.linkPreview()); + } + } + }); - $('[data-x-src-location]', this.body).each(function () { + $('[data-x-src-location]', this.body).each(function () { - var oAttachment = self.findAttachmentByContentLocation($(this).attr('data-x-src-location')); - if (!oAttachment) - { - oAttachment = self.findAttachmentByCid($(this).attr('data-x-src-location')); - } + var oAttachment = self.findAttachmentByContentLocation($(this).attr('data-x-src-location')); + if (!oAttachment) + { + oAttachment = self.findAttachmentByCid($(this).attr('data-x-src-location')); + } - if (oAttachment && oAttachment.download) - { - if (bLazy && $(this).is('img')) - { - $(this) - .addClass('lazy') - .attr('data-original', oAttachment.linkPreview()); - } - else - { - $(this).attr('src', oAttachment.linkPreview()); - } - } - }); + if (oAttachment && oAttachment.download) + { + if (bLazy && $(this).is('img')) + { + $(this) + .addClass('lazy') + .attr('data-original', oAttachment.linkPreview()); + } + else + { + $(this).attr('src', oAttachment.linkPreview()); + } + } + }); - $('[data-x-style-cid]', this.body).each(function () { + $('[data-x-style-cid]', this.body).each(function () { - var - sStyle = '', - sName = '', - oAttachment = self.findAttachmentByCid($(this).attr('data-x-style-cid')) - ; + var + sStyle = '', + sName = '', + oAttachment = self.findAttachmentByCid($(this).attr('data-x-style-cid')) + ; - if (oAttachment && oAttachment.linkPreview) - { - sName = $(this).attr('data-x-style-cid-name'); - if ('' !== sName) - { - sStyle = Utils.trim($(this).attr('style')); - sStyle = '' === sStyle ? '' : (';' === sStyle.substr(-1) ? sStyle + ' ' : sStyle + '; '); - $(this).attr('style', sStyle + sName + ': url(\'' + oAttachment.linkPreview() + '\')'); - } - } - }); + if (oAttachment && oAttachment.linkPreview) + { + sName = $(this).attr('data-x-style-cid-name'); + if ('' !== sName) + { + sStyle = Utils.trim($(this).attr('style')); + sStyle = '' === sStyle ? '' : (';' === sStyle.substr(-1) ? sStyle + ' ' : sStyle + '; '); + $(this).attr('style', sStyle + sName + ': url(\'' + oAttachment.linkPreview() + '\')'); + } + } + }); - if (bLazy) - { - (function ($oImg, oContainer) { - _.delay(function () { - $oImg.addClass('lazy-inited').lazyload({ - 'threshold' : 400, - 'effect' : 'fadeIn', - 'skip_invisible' : false, - 'container': oContainer - }); - }, 300); - }($('img.lazy', self.body), $('.RL-MailMessageView .messageView .messageItem .content')[0])); - } + if (bLazy) + { + (function ($oImg, oContainer) { + _.delay(function () { + $oImg.addClass('lazy-inited').lazyload({ + 'threshold': 400, + 'effect': 'fadeIn', + 'skip_invisible': false, + 'container': oContainer + }); + }, 300); + }($('img.lazy', self.body), $('.RL-MailMessageView .messageView .messageItem .content')[0])); + } - Utils.windowResize(500); - } + Utils.windowResize(500); + } }; MessageModel.prototype.storeDataToDom = function () { - if (this.body) - { - this.body.data('rl-is-html', !!this.isHtml()); - this.body.data('rl-has-images', !!this.hasImages()); + if (this.body) + { + this.body.data('rl-is-html', !!this.isHtml()); + this.body.data('rl-has-images', !!this.hasImages()); - this.body.data('rl-plain-raw', this.plainRaw); + this.body.data('rl-plain-raw', this.plainRaw); - var Data = require('Storage/App/Data'); - if (Data.capaOpenPGP()) - { - this.body.data('rl-plain-pgp-signed', !!this.isPgpSigned()); - this.body.data('rl-plain-pgp-encrypted', !!this.isPgpEncrypted()); - this.body.data('rl-pgp-verify-status', this.pgpSignedVerifyStatus()); - this.body.data('rl-pgp-verify-user', this.pgpSignedVerifyUser()); - } - } + if (require('Stores/User/Pgp').capaOpenPGP()) + { + this.body.data('rl-plain-pgp-signed', !!this.isPgpSigned()); + this.body.data('rl-plain-pgp-encrypted', !!this.isPgpEncrypted()); + this.body.data('rl-pgp-verify-status', this.pgpSignedVerifyStatus()); + this.body.data('rl-pgp-verify-user', this.pgpSignedVerifyUser()); + } + } }; MessageModel.prototype.storePgpVerifyDataToDom = function () { - var Data = require('Storage/App/Data'); - if (this.body && Data.capaOpenPGP()) + if (this.body && require('Stores/User/Pgp').capaOpenPGP()) { this.body.data('rl-pgp-verify-status', this.pgpSignedVerifyStatus()); this.body.data('rl-pgp-verify-user', this.pgpSignedVerifyUser()); @@ -1118,8 +1015,7 @@ this.plainRaw = Utils.pString(this.body.data('rl-plain-raw')); - var Data = require('Storage/App/Data'); - if (Data.capaOpenPGP()) + if (require('Stores/User/Pgp').capaOpenPGP()) { this.isPgpSigned(!!this.body.data('rl-plain-pgp-signed')); this.isPgpEncrypted(!!this.body.data('rl-plain-pgp-encrypted')); @@ -1138,155 +1034,153 @@ MessageModel.prototype.verifyPgpSignedClearMessage = function () { - if (this.isPgpSigned()) - { - var - aRes = [], - mPgpMessage = null, - Data = require('Storage/App/Data'), - sFrom = this.from && this.from[0] && this.from[0].email ? this.from[0].email : '', - aPublicKeys = Data.findPublicKeysByEmail(sFrom), - oValidKey = null, - oValidSysKey = null, - sPlain = '' - ; + if (this.isPgpSigned()) + { + var + aRes = [], + mPgpMessage = null, + sFrom = this.from && this.from[0] && this.from[0].email ? this.from[0].email : '', + aPublicKeys = PgpStore.findPublicKeysByEmail(sFrom), + oValidKey = null, + oValidSysKey = null, + sPlain = '' + ; - this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Error); - this.pgpSignedVerifyUser(''); + this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Error); + this.pgpSignedVerifyUser(''); - try - { - mPgpMessage = Data.openpgp.cleartext.readArmored(this.plainRaw); - if (mPgpMessage && mPgpMessage.getText) - { - this.pgpSignedVerifyStatus( - aPublicKeys.length ? Enums.SignedVerifyStatus.Unverified : Enums.SignedVerifyStatus.UnknownPublicKeys); + try + { + mPgpMessage = PgpStore.openpgp.cleartext.readArmored(this.plainRaw); + if (mPgpMessage && mPgpMessage.getText) + { + this.pgpSignedVerifyStatus( + aPublicKeys.length ? Enums.SignedVerifyStatus.Unverified : Enums.SignedVerifyStatus.UnknownPublicKeys); - aRes = mPgpMessage.verify(aPublicKeys); - if (aRes && 0 < aRes.length) - { - oValidKey = _.find(aRes, function (oItem) { - return oItem && oItem.keyid && oItem.valid; - }); + aRes = mPgpMessage.verify(aPublicKeys); + if (aRes && 0 < aRes.length) + { + oValidKey = _.find(aRes, function (oItem) { + return oItem && oItem.keyid && oItem.valid; + }); - if (oValidKey) - { - oValidSysKey = Data.findPublicKeyByHex(oValidKey.keyid.toHex()); - if (oValidSysKey) - { - sPlain = mPgpMessage.getText(); + if (oValidKey) + { + oValidSysKey = PgpStore.findPublicKeyByHex(oValidKey.keyid.toHex()); + if (oValidSysKey) + { + sPlain = mPgpMessage.getText(); - this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Success); - this.pgpSignedVerifyUser(oValidSysKey.user); + this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Success); + this.pgpSignedVerifyUser(oValidSysKey.user); - sPlain = - Globals.$div.empty().append( - $('
').text(sPlain)
-								   ).html()
-							   ;
+								sPlain =
+									Globals.$div.empty().append(
+									$('
').text(sPlain)
+									).html()
+									;
 
-							   Globals.$div.empty();
+								Globals.$div.empty();
 
-							   this.replacePlaneTextBody(sPlain);
-						   }
-					   }
-				   }
-			   }
-		   }
-		   catch (oExc) {}
+								this.replacePlaneTextBody(sPlain);
+							}
+						}
+					}
+				}
+			}
+			catch (oExc) {}
 
-		   this.storePgpVerifyDataToDom();
-	   }
+			this.storePgpVerifyDataToDom();
+		}
 	};
 
 	MessageModel.prototype.decryptPgpEncryptedMessage = function (sPassword)
 	{
-	   if (this.isPgpEncrypted())
-	   {
-		   var
-			   aRes = [],
-			   mPgpMessage = null,
-			   mPgpMessageDecrypted = null,
-			   Data = require('Storage/App/Data'),
-			   sFrom = this.from && this.from[0] && this.from[0].email ? this.from[0].email : '',
-			   aPublicKey = Data.findPublicKeysByEmail(sFrom),
-			   oPrivateKey = Data.findSelfPrivateKey(sPassword),
-			   oValidKey = null,
-			   oValidSysKey = null,
-			   sPlain = ''
-		   ;
+		if (this.isPgpEncrypted())
+		{
+			var
+				aRes = [],
+				mPgpMessage = null,
+				mPgpMessageDecrypted = null,
+				sFrom = this.from && this.from[0] && this.from[0].email ? this.from[0].email : '',
+				aPublicKey = PgpStore.findPublicKeysByEmail(sFrom),
+				oPrivateKey = PgpStore.findSelfPrivateKey(sPassword),
+				oValidKey = null,
+				oValidSysKey = null,
+				sPlain = ''
+			;
 
-		   this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Error);
-		   this.pgpSignedVerifyUser('');
+			this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Error);
+			this.pgpSignedVerifyUser('');
 
-		   if (!oPrivateKey)
-		   {
-			   this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.UnknownPrivateKey);
-		   }
+			if (!oPrivateKey)
+			{
+				this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.UnknownPrivateKey);
+			}
 
-		   try
-		   {
-			   mPgpMessage = Data.openpgp.message.readArmored(this.plainRaw);
-			   if (mPgpMessage && oPrivateKey && mPgpMessage.decrypt)
-			   {
-				   this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Unverified);
+			try
+			{
+				mPgpMessage = PgpStore.openpgp.message.readArmored(this.plainRaw);
+				if (mPgpMessage && oPrivateKey && mPgpMessage.decrypt)
+				{
+					this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Unverified);
 
-				   mPgpMessageDecrypted = mPgpMessage.decrypt(oPrivateKey);
-				   if (mPgpMessageDecrypted)
-				   {
-					   aRes = mPgpMessageDecrypted.verify(aPublicKey);
-					   if (aRes && 0 < aRes.length)
-					   {
-						   oValidKey = _.find(aRes, function (oItem) {
-							   return oItem && oItem.keyid && oItem.valid;
-						   });
+					mPgpMessageDecrypted = mPgpMessage.decrypt(oPrivateKey);
+					if (mPgpMessageDecrypted)
+					{
+						aRes = mPgpMessageDecrypted.verify(aPublicKey);
+						if (aRes && 0 < aRes.length)
+						{
+							oValidKey = _.find(aRes, function (oItem) {
+								return oItem && oItem.keyid && oItem.valid;
+							});
 
-						   if (oValidKey)
-						   {
-							   oValidSysKey = Data.findPublicKeyByHex(oValidKey.keyid.toHex());
-							   if (oValidSysKey)
-							   {
-								   this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Success);
-								   this.pgpSignedVerifyUser(oValidSysKey.user);
-							   }
-						   }
-					   }
+							if (oValidKey)
+							{
+								oValidSysKey = PgpStore.findPublicKeyByHex(oValidKey.keyid.toHex());
+								if (oValidSysKey)
+								{
+									this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Success);
+									this.pgpSignedVerifyUser(oValidSysKey.user);
+								}
+							}
+						}
 
-					   sPlain = mPgpMessageDecrypted.getText();
+						sPlain = mPgpMessageDecrypted.getText();
 
-					   sPlain =
-						   Globals.$div.empty().append(
-							   $('
').text(sPlain)
-						   ).html()
-					   ;
+						sPlain =
+							Globals.$div.empty().append(
+							$('
').text(sPlain)
+							).html()
+							;
 
-					   Globals.$div.empty();
+						Globals.$div.empty();
 
-					   this.replacePlaneTextBody(sPlain);
-				   }
-			   }
-		   }
-		   catch (oExc) {}
+						this.replacePlaneTextBody(sPlain);
+					}
+				}
+			}
+			catch (oExc) {}
 
-		   this.storePgpVerifyDataToDom();
-	   }
+			this.storePgpVerifyDataToDom();
+		}
 	};
 
 	MessageModel.prototype.replacePlaneTextBody = function (sPlain)
 	{
-	   if (this.body)
-	   {
-		   this.body.html(sPlain).addClass('b-text-part plain');
-	   }
+		if (this.body)
+		{
+			this.body.html(sPlain).addClass('b-text-part plain');
+		}
 	};
 
 	/**
-	* @return {string}
-	*/
+	 * @return {string}
+	 */
 	MessageModel.prototype.flagHash = function ()
 	{
-	   return [this.deleted(), this.unseen(), this.flagged(), this.answered(), this.forwarded(),
-		   this.isReadReceipt()].join('');
+		return [this.deleted(), this.deletedMark(), this.unseen(), this.flagged(), this.answered(), this.forwarded(),
+			this.isReadReceipt()].join(',');
 	};
 
 	module.exports = MessageModel;
diff --git a/dev/Model/MessageFull.js b/dev/Model/MessageFull.js
new file mode 100644
index 000000000..88c185ea6
--- /dev/null
+++ b/dev/Model/MessageFull.js
@@ -0,0 +1,89 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+
+		Enums = require('Common/Enums'),
+		Utils = require('Common/Utils'),
+
+//		MessageHelper = require('Helper/Message'),
+
+		MessageSimpleModel = require('Model/MessageSimple')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function MessageFullModel()
+	{
+		MessageSimpleModel.call(this, 'MessageFullModel');
+	}
+
+	_.extend(MessageFullModel.prototype, MessageSimpleModel.prototype);
+
+	MessageFullModel.prototype.priority = 0;
+	MessageFullModel.prototype.hash = '';
+	MessageFullModel.prototype.requestHash = '';
+	MessageFullModel.prototype.proxy = false;
+	MessageFullModel.prototype.hasAttachments = false;
+
+	MessageFullModel.prototype.clear = function ()
+	{
+		MessageSimpleModel.prototype.clear.call(this);
+
+		this.priority = 0;
+		this.hash = '';
+		this.requestHash = '';
+
+		this.proxy = false;
+
+		this.hasAttachments = false;
+	};
+
+	/**
+	 * @param {AjaxJsonMessage} oJson
+	 * @return {boolean}
+	 */
+	MessageFullModel.prototype.initByJson = function (oJson)
+	{
+		var bResult = false;
+
+		if (oJson && 'Object/Message' === oJson['@Object'])
+		{
+			if (MessageSimpleModel.prototype.initByJson.call(this, oJson))
+			{
+				this.priority = Utils.pInt(oJson.Priority);
+				this.priority =  Utils.inArray(this.priority, [Enums.MessagePriority.High, Enums.MessagePriority.Low]) ?
+					this.priority : Enums.MessagePriority.Normal;
+
+				this.hash = Utils.pString(oJson.Hash);
+				this.requestHash = Utils.pString(oJson.RequestHash);
+
+				this.proxy = !!oJson.ExternalProxy;
+
+				this.hasAttachments = !!oJson.HasAttachments;
+
+				bResult = true;
+			}
+		}
+
+		return bResult;
+	};
+
+	/**
+	 * @static
+	 * @param {AjaxJsonMessage} oJson
+	 * @return {?MessageFullModel}
+	 */
+	MessageFullModel.newInstanceFromJson = function (oJson)
+	{
+		var oItem = oJson ? new MessageFullModel() : null;
+		return oItem && oItem.initByJson(oJson) ? oItem : null;
+	};
+
+	module.exports = MessageFullModel;
+
+}());
\ No newline at end of file
diff --git a/dev/Model/MessageSimple.js b/dev/Model/MessageSimple.js
new file mode 100644
index 000000000..d4eb52d23
--- /dev/null
+++ b/dev/Model/MessageSimple.js
@@ -0,0 +1,174 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko'),
+
+		Utils = require('Common/Utils'),
+
+		MessageHelper = require('Helper/Message'),
+
+		AbstractModel = require('Knoin/AbstractModel')
+	;
+
+	/**
+	 * @param {string=} sSuperName
+	 * @constructor
+	 */
+	function MessageSimpleModel(sSuperName)
+	{
+		AbstractModel.call(this, sSuperName || 'MessageSimpleModel');
+
+		this.flagged = ko.observable(false);
+		this.selected = ko.observable(false);
+	}
+
+	_.extend(MessageSimpleModel.prototype, AbstractModel.prototype);
+
+	MessageSimpleModel.prototype.__simple_message__ = true;
+
+	MessageSimpleModel.prototype.folder = '';
+	MessageSimpleModel.prototype.folderFullNameRaw = '';
+
+	MessageSimpleModel.prototype.uid = '';
+	MessageSimpleModel.prototype.subject = '';
+
+	MessageSimpleModel.prototype.to = [];
+	MessageSimpleModel.prototype.from = [];
+	MessageSimpleModel.prototype.cc = [];
+	MessageSimpleModel.prototype.bcc = [];
+	MessageSimpleModel.prototype.replyTo = [];
+	MessageSimpleModel.prototype.deliveredTo = [];
+
+	MessageSimpleModel.prototype.fromAsString = '';
+	MessageSimpleModel.prototype.fromAsStringClear = '';
+	MessageSimpleModel.prototype.toAsString = '';
+	MessageSimpleModel.prototype.toAsStringClear = '';
+	MessageSimpleModel.prototype.senderAsString = '';
+	MessageSimpleModel.prototype.senderAsStringClear = '';
+
+	MessageSimpleModel.prototype.size = 0;
+	MessageSimpleModel.prototype.timestamp = 0;
+
+	MessageSimpleModel.prototype.clear = function ()
+	{
+		this.folder = '';
+		this.folderFullNameRaw = '';
+
+		this.uid = '';
+
+		this.subject = '';
+
+		this.to = [];
+		this.from = [];
+		this.cc = [];
+		this.bcc = [];
+		this.replyTo = [];
+		this.deliveredTo = [];
+
+		this.fromAsString = '';
+		this.fromAsStringClear = '';
+		this.toAsString = '';
+		this.toAsStringClear = '';
+		this.senderAsString = '';
+		this.senderAsStringClear = '';
+
+		this.size = 0;
+		this.timestamp = 0;
+
+		this.flagged(false);
+		this.selected(false);
+	};
+
+	/**
+	 * @param {Object} oJson
+	 * @return {boolean}
+	 */
+	MessageSimpleModel.prototype.initByJson = function (oJson)
+	{
+		var bResult = false;
+
+		if (oJson && 'Object/Message' === oJson['@Object'])
+		{
+			this.folder = Utils.pString(oJson.Folder);
+			this.folderFullNameRaw = this.folder;
+
+			this.uid = Utils.pString(oJson.Uid);
+
+			this.subject = Utils.pString(oJson.Subject);
+
+			if (Utils.isArray(oJson.SubjectParts))
+			{
+				this.subjectPrefix = Utils.pString(oJson.SubjectParts[0]);
+				this.subjectSuffix = Utils.pString(oJson.SubjectParts[1]);
+			}
+			else
+			{
+				this.subjectPrefix = '';
+				this.subjectSuffix = this.subject;
+			}
+
+			this.from = MessageHelper.emailArrayFromJson(oJson.From);
+			this.to = MessageHelper.emailArrayFromJson(oJson.To);
+			this.cc = MessageHelper.emailArrayFromJson(oJson.Cc);
+			this.bcc = MessageHelper.emailArrayFromJson(oJson.Bcc);
+			this.replyTo = MessageHelper.emailArrayFromJson(oJson.ReplyTo);
+			this.deliveredTo = MessageHelper.emailArrayFromJson(oJson.DeliveredTo);
+
+			this.size = Utils.pInt(oJson.Size);
+			this.timestamp = Utils.pInt(oJson.DateTimeStampInUTC);
+
+			this.fromAsString = MessageHelper.emailArrayToString(this.from, true);
+			this.fromAsStringClear = MessageHelper.emailArrayToStringClear(this.from);
+
+			this.toAsString = MessageHelper.emailArrayToString(this.to, true);
+			this.toAsStringClear = MessageHelper.emailArrayToStringClear(this.to);
+
+			this.flagged(false);
+			this.selected(false);
+
+			this.populateSenderEmail();
+
+			bResult = true;
+		}
+
+		return bResult;
+	};
+
+	MessageSimpleModel.prototype.populateSenderEmail = function (bDraftOrSentFolder)
+	{
+		this.senderAsString = this.fromAsString;
+		this.senderAsStringClear = this.fromAsStringClear;
+
+		if (bDraftOrSentFolder)
+		{
+			this.senderAsString = this.toAsString;
+			this.senderAsStringClear = this.toAsStringClear;
+		}
+	};
+
+	/**
+	 * @return {Array}
+	 */
+	MessageSimpleModel.prototype.threads = function ()
+	{
+		return [];
+	};
+
+	/**
+	 * @static
+	 * @param {Object} oJson
+	 * @return {?MessageSimpleModel}
+	 */
+	MessageSimpleModel.newInstanceFromJson = function (oJson)
+	{
+		var oItem = oJson ? new MessageSimpleModel() : null;
+		return oItem && oItem.initByJson(oJson) ? oItem : null;
+	};
+
+	module.exports = MessageSimpleModel;
+
+}());
\ No newline at end of file
diff --git a/dev/Model/OpenPgpKey.js b/dev/Model/OpenPgpKey.js
index d2b0abfc2..22c46bbbe 100644
--- a/dev/Model/OpenPgpKey.js
+++ b/dev/Model/OpenPgpKey.js
@@ -4,7 +4,10 @@
 	'use strict';
 
 	var
-		ko = require('ko')
+		_ = require('_'),
+		ko = require('ko'),
+
+		AbstractModel = require('Knoin/AbstractModel')
 	;
 
 	/**
@@ -19,6 +22,8 @@
 	 */
 	function OpenPgpKeyModel(iIndex, sGuID, sID, sUserID, sEmail, bIsPrivate, sArmor)
 	{
+		AbstractModel.call(this, 'OpenPgpKeyModel');
+
 		this.index = iIndex;
 		this.id = sID;
 		this.guid = sGuID;
@@ -30,6 +35,8 @@
 		this.deleteAccess = ko.observable(false);
 	}
 
+	_.extend(OpenPgpKeyModel.prototype, AbstractModel.prototype);
+
 	OpenPgpKeyModel.prototype.index = 0;
 	OpenPgpKeyModel.prototype.id = '';
 	OpenPgpKeyModel.prototype.guid = '';
diff --git a/dev/Model/Template.js b/dev/Model/Template.js
new file mode 100644
index 000000000..765802dc2
--- /dev/null
+++ b/dev/Model/Template.js
@@ -0,0 +1,77 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko'),
+
+		Utils = require('Common/Utils'),
+
+		AbstractModel = require('Knoin/AbstractModel')
+	;
+
+	/**
+	 * @constructor
+	 *
+	 * @param {string} sID
+	 * @param {string} sName
+	 * @param {string} sBody
+	 */
+	function TemplateModel(sID, sName, sBody)
+	{
+		AbstractModel.call(this, 'TemplateModel');
+
+		this.id = sID;
+		this.name = sName;
+		this.body = sBody;
+		this.populated = true;
+
+		this.deleteAccess = ko.observable(false);
+	}
+
+	_.extend(TemplateModel.prototype, AbstractModel.prototype);
+
+	/**
+	 * @type {string}
+	 */
+	TemplateModel.prototype.id = '';
+
+	/**
+	 * @type {string}
+	 */
+	TemplateModel.prototype.name = '';
+
+	/**
+	 * @type {string}
+	 */
+	TemplateModel.prototype.body = '';
+
+	/**
+	 * @type {boolean}
+	 */
+	TemplateModel.prototype.populated = true;
+
+	/**
+	 * @type {boolean}
+	 */
+	TemplateModel.prototype.parse = function (oItem)
+	{
+		var bResult = false;
+		if (oItem && 'Object/Template' === oItem['@Object'])
+		{
+			this.id = Utils.pString(oItem['ID']);
+			this.name = Utils.pString(oItem['Name']);
+			this.body = Utils.pString(oItem['Body']);
+			this.populated = !!oItem['Populated'];
+
+			bResult = true;
+		}
+
+		return bResult;
+	};
+
+	module.exports = TemplateModel;
+
+}());
\ No newline at end of file
diff --git a/dev/Promises/AbstractAjax.js b/dev/Promises/AbstractAjax.js
new file mode 100644
index 000000000..0ef3faf62
--- /dev/null
+++ b/dev/Promises/AbstractAjax.js
@@ -0,0 +1,165 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		$ = require('$'),
+		_ = require('_'),
+		Q = require('Q'),
+
+		Consts = require('Common/Consts'),
+		Enums = require('Common/Enums'),
+		Utils = require('Common/Utils'),
+		Links = require('Common/Links'),
+
+		Settings = require('Storage/Settings'),
+
+		AbstractBasicPromises = require('Promises/AbstractBasic')
+	;
+
+	/**
+	* @constructor
+	*/
+	function AbstractAjaxPromises()
+	{
+		AbstractBasicPromises.call(this);
+
+		this.clear();
+	}
+
+	_.extend(AbstractAjaxPromises.prototype, AbstractBasicPromises.prototype);
+
+	AbstractAjaxPromises.prototype.oRequests = {};
+
+	AbstractAjaxPromises.prototype.clear = function ()
+	{
+		this.oRequests = {};
+	};
+
+	AbstractAjaxPromises.prototype.abort = function (sAction, bClearOnly)
+	{
+		if (this.oRequests[sAction])
+		{
+			if (!bClearOnly && this.oRequests[sAction].abort)
+			{
+				this.oRequests[sAction].__aborted__ = true;
+				this.oRequests[sAction].abort();
+			}
+
+			this.oRequests[sAction] = null;
+			delete this.oRequests[sAction];
+		}
+
+		return this;
+	};
+
+	AbstractAjaxPromises.prototype.ajaxRequest = function (sAction, bPost, iTimeOut, oParameters, sAdditionalGetString, fTrigger)
+	{
+		var
+			oH = null,
+			self = this,
+			iStart = Utils.microtime(),
+			oDeferred = Q.defer()
+		;
+
+		iTimeOut = Utils.isNormal(iTimeOut) ? iTimeOut : Consts.Defaults.DefaultAjaxTimeout;
+		sAdditionalGetString = Utils.isUnd(sAdditionalGetString) ? '' : Utils.pString(sAdditionalGetString);
+
+		if (bPost)
+		{
+			oParameters['XToken'] = Settings.settingsGet('Token');
+		}
+
+		this.setTrigger(fTrigger, true);
+
+		oH = $.ajax({
+			'type': bPost ? 'POST' : 'GET',
+			'url': Links.ajax(sAdditionalGetString),
+			'async': true,
+			'dataType': 'json',
+			'data': bPost ? (oParameters || {}) : {},
+			'timeout': iTimeOut,
+			'global': true
+		}).always(function (oData, sTextStatus) {
+
+			var bCached = false;
+			if (oData && oData['Time'])
+			{
+				bCached = Utils.pInt(oData['Time']) > Utils.microtime() - iStart;
+			}
+
+			if ('success' === sTextStatus)
+			{
+				if (oData && oData.Result && sAction === oData.Action)
+				{
+					oData.__cached__ = bCached;
+					oDeferred.resolve(oData);
+				}
+				else if (oData && oData.Action)
+				{
+					oDeferred.reject(oData.ErrorCode ? oData.ErrorCode : Enums.Notification.AjaxFalse);
+				}
+				else
+				{
+					oDeferred.reject(Enums.Notification.AjaxParse);
+				}
+			}
+			else if ('timeout' === sTextStatus)
+			{
+				oDeferred.reject(Enums.Notification.AjaxTimeout);
+			}
+			else if ('abort' === sTextStatus)
+			{
+				if (!oData || !oData.__aborted__)
+				{
+					oDeferred.reject(Enums.Notification.AjaxAbort);
+				}
+			}
+			else
+			{
+				oDeferred.reject(Enums.Notification.AjaxParse);
+			}
+
+			if (self.oRequests[sAction])
+			{
+				self.oRequests[sAction] = null;
+				delete self.oRequests[sAction];
+			}
+
+			self.setTrigger(fTrigger, false);
+		});
+
+		if (oH)
+		{
+			if (this.oRequests[sAction])
+			{
+				this.oRequests[sAction] = null;
+				delete this.oRequests[sAction];
+			}
+
+			this.oRequests[sAction] = oH;
+		}
+
+		return oDeferred.promise;
+	};
+
+	AbstractAjaxPromises.prototype.getRequest = function (sAction, fTrigger, sAdditionalGetString, iTimeOut)
+	{
+		sAdditionalGetString = Utils.isUnd(sAdditionalGetString) ? '' : Utils.pString(sAdditionalGetString);
+		sAdditionalGetString = sAction + '/' + sAdditionalGetString;
+
+		return this.ajaxRequest(sAction, false, iTimeOut, null, sAdditionalGetString, fTrigger);
+	};
+
+	AbstractAjaxPromises.prototype.postRequest = function (sAction, fTrigger, oParameters, iTimeOut)
+	{
+		oParameters = oParameters || {};
+		oParameters['Action'] = sAction;
+
+		return this.ajaxRequest(sAction, true, iTimeOut, oParameters, '', fTrigger);
+	};
+
+	module.exports = AbstractAjaxPromises;
+
+}());
\ No newline at end of file
diff --git a/dev/Promises/AbstractBasic.js b/dev/Promises/AbstractBasic.js
new file mode 100644
index 000000000..79996e615
--- /dev/null
+++ b/dev/Promises/AbstractBasic.js
@@ -0,0 +1,56 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		Q = require('Q'),
+
+		Utils = require('Common/Utils')
+	;
+
+	/**
+	* @constructor
+	*/
+	function AbstractBasicPromises()
+	{
+		this.oPromisesStack = {};
+	}
+
+	AbstractBasicPromises.prototype.func = function (fFunc)
+	{
+		fFunc();
+		return this;
+	};
+
+	AbstractBasicPromises.prototype.fastResolve = function (mData)
+	{
+		var oDeferred = Q.defer();
+		oDeferred.resolve(mData);
+		return oDeferred.promise;
+	};
+
+	AbstractBasicPromises.prototype.fastReject = function (mData)
+	{
+		var oDeferred = Q.defer();
+		oDeferred.reject(mData);
+		return oDeferred.promise;
+	};
+
+	AbstractBasicPromises.prototype.setTrigger = function (mTrigger, bValue)
+	{
+		if (mTrigger)
+		{
+			_.each(Utils.isArray(mTrigger) ? mTrigger : [mTrigger], function (fTrigger) {
+				if (fTrigger)
+				{
+					fTrigger(!!bValue);
+				}
+			});
+		}
+	};
+
+	module.exports = AbstractBasicPromises;
+
+}());
\ No newline at end of file
diff --git a/dev/Promises/User/Ajax.js b/dev/Promises/User/Ajax.js
new file mode 100644
index 000000000..6dec2b76f
--- /dev/null
+++ b/dev/Promises/User/Ajax.js
@@ -0,0 +1,195 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		window = require('window'),
+		_ = require('_'),
+
+//		Enums = require('Common/Enums'),
+//		Utils = require('Common/Utils'),
+//		Base64 = require('Common/Base64'),
+//		Cache = require('Common/Cache'),
+//		Links = require('Common/Links'),
+//
+//		AppStore = require('Stores/User/App'),
+//		SettingsStore = require('Stores/User/Settings'),
+
+		MessageSimpleModel = require('Model/MessageSimple'),
+
+		PromisesPopulator = require('Promises/User/Populator'),
+		AbstractAjaxPromises = require('Promises/AbstractAjax')
+	;
+
+	/**
+	 * @constructor
+	 * @extends AbstractAjaxPromises
+	 */
+	function UserAjaxUserPromises()
+	{
+		AbstractAjaxPromises.call(this);
+
+		this.messageListSimpleHash = '';
+		this.messageListSimpleCache = null;
+	}
+
+	_.extend(UserAjaxUserPromises.prototype, AbstractAjaxPromises.prototype);
+
+	UserAjaxUserPromises.prototype.messageListSimple = function (sFolder, aUids, fTrigger)
+	{
+		var self = this, sHash = sFolder + '~' + aUids.join('/');
+		if (sHash === this.messageListSimpleHash && this.messageListSimpleCache)
+		{
+			return this.fastResolve(this.messageListSimpleCache);
+		}
+
+		return this.abort('MessageListSimple')
+			.postRequest('MessageListSimple', fTrigger, {
+				'Folder': sFolder,
+				'Uids': aUids
+			}).then(function (oData) {
+
+				self.messageListSimpleHash = sHash;
+				self.messageListSimpleCache = _.compact(_.map(oData.Result, function (aItem) {
+					return MessageSimpleModel.newInstanceFromJson(aItem);
+				}));
+
+				return self.messageListSimpleCache;
+
+			}, function (iError) {
+
+				self.messageListSimpleHash = '';
+				self.messageListSimpleCache = null;
+
+				return self.fastReject(iError);
+			})
+		;
+	};
+
+	UserAjaxUserPromises.prototype.foldersReload = function (fTrigger)
+	{
+		return this.abort('Folders')
+			.postRequest('Folders', fTrigger).then(function (oData) {
+				PromisesPopulator.foldersList(oData.Result);
+				PromisesPopulator.foldersAdditionalParameters(oData.Result);
+				return true;
+			});
+	};
+
+	UserAjaxUserPromises.prototype._folders_timeout_ = 0;
+	UserAjaxUserPromises.prototype.foldersReloadWithTimeout = function (fTrigger)
+	{
+		this.setTrigger(fTrigger, true);
+
+		var self = this;
+		window.clearTimeout(this._folders_timeout_);
+		this._folders_timeout_ = window.setTimeout(function () {
+			self.foldersReload(fTrigger);
+		}, 500);
+	};
+
+	UserAjaxUserPromises.prototype.folderDelete = function (sFolderFullNameRaw, fTrigger)
+	{
+		return this.postRequest('FolderDelete', fTrigger, {
+			'Folder': sFolderFullNameRaw
+		});
+	};
+
+	UserAjaxUserPromises.prototype.folderCreate = function (sNewFolderName, sParentName, fTrigger)
+	{
+		return this.postRequest('FolderCreate', fTrigger, {
+			'Folder': sNewFolderName,
+			'Parent': sParentName
+		});
+	};
+
+	UserAjaxUserPromises.prototype.folderRename = function (sPrevFolderFullNameRaw, sNewFolderName, fTrigger)
+	{
+		return this.postRequest('FolderRename', fTrigger, {
+			'Folder': sPrevFolderFullNameRaw,
+			'NewFolderName': sNewFolderName
+		});
+	};
+
+	UserAjaxUserPromises.prototype.attachmentsActions = function (sAction, aHashes, fTrigger)
+	{
+		return this.postRequest('AttachmentsActions', fTrigger, {
+			'Action': sAction,
+			'Hashes': aHashes
+		});
+	};
+
+	UserAjaxUserPromises.prototype.welcomeClose = function ()
+	{
+		return this.postRequest('WelcomeClose');
+	};
+
+//	UserAjaxUserPromises.prototype.messageList = function (sFolderFullNameRaw, iOffset, iLimit, sSearch, fTrigger)
+//	{
+//		sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw);
+//		sSearch = Utils.pString(sSearch);
+//		iOffset = Utils.pInt(iOffset);
+//		iLimit = Utils.pInt(iLimit);
+//
+//		var sFolderHash = Cache.getFolderHash(sFolderFullNameRaw);
+//
+//		if ('' !== sFolderHash && ('' === sSearch || -1 === sSearch.indexOf('is:')))
+//		{
+//			return this.abort('MessageList')
+//				.getRequest('MessageList', fTrigger,
+//					Links.subQueryPrefix() + '/' + Base64.urlsafe_encode([
+//						sFolderFullNameRaw,
+//						iOffset,
+//						iLimit,
+//						sSearch,
+//						AppStore.projectHash(),
+//						sFolderHash,
+//						Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '',
+//						AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0',
+//						''
+//					].join(String.fromCharCode(0))))
+//				.then(PromisesPopulator.messageList);
+//		}
+//		else
+//		{
+//			return this.abort('MessageList')
+//				.postRequest('MessageList', fTrigger,{
+//					'Folder': sFolderFullNameRaw,
+//					'Offset': iOffset,
+//					'Limit': iLimit,
+//					'Search': sSearch,
+//					'UidNext': Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '',
+//					'UseThreads': AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0'
+//				})
+//				.then(PromisesPopulator.messageList);
+//		}
+//
+//		return this.fastReject(Enums.Notification.UnknownError);
+//	};
+//
+//	UserAjaxUserPromises.prototype.message = function (sFolderFullNameRaw, iUid, fTrigger)
+//	{
+//		sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw);
+//		iUid = Utils.pInt(iUid);
+//
+//		if (Cache.getFolderFromCacheList(sFolderFullNameRaw) && 0 >= iUid)
+//		{
+//			return this.abort('Message')
+//				.getRequest('Message', fTrigger,
+//					Links.subQueryPrefix() + '/' + Base64.urlsafe_encode([
+//						sFolderFullNameRaw, iUid,
+//						AppStore.projectHash(),
+//						AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0'
+//					].join(String.fromCharCode(0))))
+//				.then(function (oData) {
+//					return oData;
+//				});
+//		}
+//
+//		return this.fastReject(Enums.Notification.UnknownError);
+//	};
+
+	module.exports = new UserAjaxUserPromises();
+
+}());
\ No newline at end of file
diff --git a/dev/Promises/User/Populator.js b/dev/Promises/User/Populator.js
new file mode 100644
index 000000000..623396bb0
--- /dev/null
+++ b/dev/Promises/User/Populator.js
@@ -0,0 +1,199 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+
+		Consts = require('Common/Consts'),
+		Enums = require('Common/Enums'),
+		Utils = require('Common/Utils'),
+		Cache = require('Common/Cache'),
+
+		AppStore = require('Stores/User/App'),
+		FolderStore = require('Stores/User/Folder'),
+
+		Settings = require('Storage/Settings'),
+		Local = require('Storage/Client'),
+
+		FolderModel = require('Model/Folder'),
+
+		AbstractBasicPromises = require('Promises/AbstractBasic')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function PromisesUserPopulator()
+	{
+		AbstractBasicPromises.call(this);
+	}
+
+	_.extend(PromisesUserPopulator.prototype, AbstractBasicPromises.prototype);
+
+	/**
+	 * @param {string} sFullNameHash
+	 * @return {boolean}
+	 */
+	PromisesUserPopulator.prototype.isFolderExpanded = function (sFullNameHash)
+	{
+		var aExpandedList = Local.get(Enums.ClientSideKeyName.ExpandedFolders);
+		return Utils.isArray(aExpandedList) && -1 !== _.indexOf(aExpandedList, sFullNameHash);
+	};
+
+	/**
+	 * @param {string} sFolderFullNameRaw
+	 * @return {string}
+	 */
+	PromisesUserPopulator.prototype.normalizeFolder = function (sFolderFullNameRaw)
+	{
+		return ('' === sFolderFullNameRaw || Consts.Values.UnuseOptionValue === sFolderFullNameRaw ||
+			null !== Cache.getFolderFromCacheList(sFolderFullNameRaw)) ? sFolderFullNameRaw : '';
+	};
+
+	/**
+	 * @param {string} sNamespace
+	 * @param {Array} aFolders
+	 * @return {Array}
+	 */
+	PromisesUserPopulator.prototype.folderResponseParseRec = function (sNamespace, aFolders)
+	{
+		var
+			self = this,
+			iIndex = 0,
+			iLen = 0,
+			oFolder = null,
+			oCacheFolder = null,
+			sFolderFullNameRaw = '',
+			aSubFolders = [],
+			aList = []
+		;
+
+		for (iIndex = 0, iLen = aFolders.length; iIndex < iLen; iIndex++)
+		{
+			oFolder = aFolders[iIndex];
+			if (oFolder)
+			{
+				sFolderFullNameRaw = oFolder.FullNameRaw;
+
+				oCacheFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
+				if (!oCacheFolder)
+				{
+					oCacheFolder = FolderModel.newInstanceFromJson(oFolder);
+					if (oCacheFolder)
+					{
+						Cache.setFolderToCacheList(sFolderFullNameRaw, oCacheFolder);
+						Cache.setFolderFullNameRaw(oCacheFolder.fullNameHash, sFolderFullNameRaw, oCacheFolder);
+					}
+				}
+
+				if (oCacheFolder)
+				{
+					oCacheFolder.collapsed(!self.isFolderExpanded(oCacheFolder.fullNameHash));
+
+					if (oFolder.Extended)
+					{
+						if (oFolder.Extended.Hash)
+						{
+							Cache.setFolderHash(oCacheFolder.fullNameRaw, oFolder.Extended.Hash);
+						}
+
+						if (Utils.isNormal(oFolder.Extended.MessageCount))
+						{
+							oCacheFolder.messageCountAll(oFolder.Extended.MessageCount);
+						}
+
+						if (Utils.isNormal(oFolder.Extended.MessageUnseenCount))
+						{
+							oCacheFolder.messageCountUnread(oFolder.Extended.MessageUnseenCount);
+						}
+					}
+
+					aSubFolders = oFolder['SubFolders'];
+					if (aSubFolders && 'Collection/FolderCollection' === aSubFolders['@Object'] &&
+						aSubFolders['@Collection'] && Utils.isArray(aSubFolders['@Collection']))
+					{
+						oCacheFolder.subFolders(
+							this.folderResponseParseRec(sNamespace, aSubFolders['@Collection']));
+					}
+
+					aList.push(oCacheFolder);
+				}
+			}
+		}
+
+		return aList;
+	};
+
+	PromisesUserPopulator.prototype.foldersList = function (oData)
+	{
+		if (oData && 'Collection/FolderCollection' === oData['@Object'] &&
+			oData['@Collection'] && Utils.isArray(oData['@Collection']))
+		{
+			FolderStore.folderList(this.folderResponseParseRec(
+				Utils.isUnd(oData.Namespace) ? '' : oData.Namespace, oData['@Collection']));
+		}
+	};
+
+	PromisesUserPopulator.prototype.foldersAdditionalParameters = function (oData)
+	{
+		if (oData && oData && 'Collection/FolderCollection' === oData['@Object'] &&
+			oData['@Collection'] && Utils.isArray(oData['@Collection']))
+		{
+			if (!Utils.isUnd(oData.Namespace))
+			{
+				FolderStore.namespace = oData.Namespace;
+			}
+
+			AppStore.threadsAllowed(
+				!!Settings.settingsGet('UseImapThread') &&
+				oData.IsThreadsSupported && true);
+
+			FolderStore.folderList.optimized(!!oData.Optimized);
+
+			var bUpdate = false;
+
+			if (oData['SystemFolders'] && '' === '' +
+				Settings.settingsGet('SentFolder') +
+				Settings.settingsGet('DraftFolder') +
+				Settings.settingsGet('SpamFolder') +
+				Settings.settingsGet('TrashFolder') +
+				Settings.settingsGet('ArchiveFolder') +
+				Settings.settingsGet('NullFolder'))
+			{
+				// TODO Magic Numbers
+				Settings.settingsSet('SentFolder', oData['SystemFolders'][2] || null);
+				Settings.settingsSet('DraftFolder', oData['SystemFolders'][3] || null);
+				Settings.settingsSet('SpamFolder', oData['SystemFolders'][4] || null);
+				Settings.settingsSet('TrashFolder', oData['SystemFolders'][5] || null);
+				Settings.settingsSet('ArchiveFolder', oData['SystemFolders'][12] || null);
+
+				bUpdate = true;
+			}
+
+			FolderStore.sentFolder(this.normalizeFolder(Settings.settingsGet('SentFolder')));
+			FolderStore.draftFolder(this.normalizeFolder(Settings.settingsGet('DraftFolder')));
+			FolderStore.spamFolder(this.normalizeFolder(Settings.settingsGet('SpamFolder')));
+			FolderStore.trashFolder(this.normalizeFolder(Settings.settingsGet('TrashFolder')));
+			FolderStore.archiveFolder(this.normalizeFolder(Settings.settingsGet('ArchiveFolder')));
+
+			if (bUpdate)
+			{
+				require('Remote/User/Ajax').saveSystemFolders(Utils.emptyFunction, {
+					'SentFolder': FolderStore.sentFolder(),
+					'DraftFolder': FolderStore.draftFolder(),
+					'SpamFolder': FolderStore.spamFolder(),
+					'TrashFolder': FolderStore.trashFolder(),
+					'ArchiveFolder': FolderStore.archiveFolder(),
+					'NullFolder': 'NullFolder'
+				});
+			}
+
+			Local.set(Enums.ClientSideKeyName.FoldersLashHash, oData.FoldersHash);
+		}
+	};
+
+	module.exports = new PromisesUserPopulator();
+
+}());
\ No newline at end of file
diff --git a/dev/Remote/AbstractAjax.js b/dev/Remote/AbstractAjax.js
new file mode 100644
index 000000000..9329c9db0
--- /dev/null
+++ b/dev/Remote/AbstractAjax.js
@@ -0,0 +1,311 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		window = require('window'),
+		_ = require('_'),
+		$ = require('$'),
+
+		Consts = require('Common/Consts'),
+		Enums = require('Common/Enums'),
+		Globals = require('Common/Globals'),
+		Utils = require('Common/Utils'),
+		Plugins = require('Common/Plugins'),
+		Links = require('Common/Links'),
+
+		Settings = require('Storage/Settings')
+	;
+
+	/**
+	* @constructor
+	*/
+	function AbstractAjaxRemote()
+	{
+		this.oRequests = {};
+	}
+
+	AbstractAjaxRemote.prototype.oRequests = {};
+
+	/**
+	 * @param {?Function} fCallback
+	 * @param {string} sRequestAction
+	 * @param {string} sType
+	 * @param {?AjaxJsonDefaultResponse} oData
+	 * @param {boolean} bCached
+	 * @param {*=} oRequestParameters
+	 */
+	AbstractAjaxRemote.prototype.defaultResponse = function (fCallback, sRequestAction, sType, oData, bCached, oRequestParameters)
+	{
+		var
+			fCall = function () {
+				if (Enums.StorageResultType.Success !== sType && Globals.bUnload)
+				{
+					sType = Enums.StorageResultType.Unload;
+				}
+
+				if (Enums.StorageResultType.Success === sType && oData && !oData.Result)
+				{
+					if (oData && -1 < Utils.inArray(oData.ErrorCode, [
+						Enums.Notification.AuthError, Enums.Notification.AccessError,
+						Enums.Notification.ConnectionError, Enums.Notification.DomainNotAllowed, Enums.Notification.AccountNotAllowed,
+						Enums.Notification.MailServerError,	Enums.Notification.UnknownNotification, Enums.Notification.UnknownError
+					]))
+					{
+						Globals.iAjaxErrorCount++;
+					}
+
+					if (oData && Enums.Notification.InvalidToken === oData.ErrorCode)
+					{
+						Globals.iTokenErrorCount++;
+					}
+
+					if (Consts.Values.TokenErrorLimit < Globals.iTokenErrorCount)
+					{
+						if (Globals.__APP__ && Globals.__APP__.loginAndLogoutReload)
+						{
+							 Globals.__APP__.loginAndLogoutReload(false, true);
+						}
+					}
+
+					if (oData.ClearAuth || oData.Logout || Consts.Values.AjaxErrorLimit < Globals.iAjaxErrorCount)
+					{
+						if (Globals.__APP__ && Globals.__APP__.clearClientSideToken)
+						{
+							Globals.__APP__.clearClientSideToken();
+
+							if (!oData.ClearAuth &&  Globals.__APP__.loginAndLogoutReload)
+							{
+								 Globals.__APP__.loginAndLogoutReload(false, true);
+							}
+						}
+					}
+				}
+				else if (Enums.StorageResultType.Success === sType && oData && oData.Result)
+				{
+					Globals.iAjaxErrorCount = 0;
+					Globals.iTokenErrorCount = 0;
+				}
+
+				if (fCallback)
+				{
+					Plugins.runHook('ajax-default-response', [sRequestAction, Enums.StorageResultType.Success === sType ? oData : null, sType, bCached, oRequestParameters]);
+
+					fCallback(
+						sType,
+						Enums.StorageResultType.Success === sType ? oData : null,
+						bCached,
+						sRequestAction,
+						oRequestParameters
+					);
+				}
+			}
+		;
+
+		switch (sType)
+		{
+			case 'success':
+				sType = Enums.StorageResultType.Success;
+				break;
+			case 'abort':
+				sType = Enums.StorageResultType.Abort;
+				break;
+			default:
+				sType = Enums.StorageResultType.Error;
+				break;
+		}
+
+		if (Enums.StorageResultType.Error === sType)
+		{
+			_.delay(fCall, 300);
+		}
+		else
+		{
+			fCall();
+		}
+	};
+
+	/**
+	 * @param {?Function} fResultCallback
+	 * @param {Object} oParameters
+	 * @param {?number=} iTimeOut = 20000
+	 * @param {string=} sGetAdd = ''
+	 * @param {Array=} aAbortActions = []
+	 * @return {jQuery.jqXHR}
+	 */
+	AbstractAjaxRemote.prototype.ajaxRequest = function (fResultCallback, oParameters, iTimeOut, sGetAdd, aAbortActions)
+	{
+		var
+			self = this,
+			bPost = '' === sGetAdd,
+			oHeaders = {},
+			iStart = (new window.Date()).getTime(),
+			oDefAjax = null,
+			sAction = ''
+		;
+
+		oParameters = oParameters || {};
+		iTimeOut = Utils.isNormal(iTimeOut) ? iTimeOut : 20000;
+		sGetAdd = Utils.isUnd(sGetAdd) ? '' : Utils.pString(sGetAdd);
+		aAbortActions = Utils.isArray(aAbortActions) ? aAbortActions : [];
+
+		sAction = oParameters.Action || '';
+
+		if (sAction && 0 < aAbortActions.length)
+		{
+			_.each(aAbortActions, function (sActionToAbort) {
+				if (self.oRequests[sActionToAbort])
+				{
+					self.oRequests[sActionToAbort].__aborted = true;
+					if (self.oRequests[sActionToAbort].abort)
+					{
+						self.oRequests[sActionToAbort].abort();
+					}
+					self.oRequests[sActionToAbort] = null;
+				}
+			});
+		}
+
+		if (bPost)
+		{
+			oParameters['XToken'] = Settings.settingsGet('Token');
+		}
+
+		oDefAjax = $.ajax({
+			'type': bPost ? 'POST' : 'GET',
+			'url': Links.ajax(sGetAdd),
+			'async': true,
+			'dataType': 'json',
+			'data': bPost ? oParameters : {},
+			'headers': oHeaders,
+			'timeout': iTimeOut,
+			'global': true
+		});
+
+		oDefAjax.always(function (oData, sType) {
+
+			var bCached = false;
+			if (oData && oData['Time'])
+			{
+				bCached = Utils.pInt(oData['Time']) > (new window.Date()).getTime() - iStart;
+			}
+
+			if (sAction && self.oRequests[sAction])
+			{
+				if (self.oRequests[sAction].__aborted)
+				{
+					sType = 'abort';
+				}
+
+				self.oRequests[sAction] = null;
+			}
+
+			self.defaultResponse(fResultCallback, sAction, sType, oData, bCached, oParameters);
+		});
+
+		if (sAction && 0 < aAbortActions.length && -1 < Utils.inArray(sAction, aAbortActions))
+		{
+			if (this.oRequests[sAction])
+			{
+				this.oRequests[sAction].__aborted = true;
+				if (this.oRequests[sAction].abort)
+				{
+					this.oRequests[sAction].abort();
+				}
+				this.oRequests[sAction] = null;
+			}
+
+			this.oRequests[sAction] = oDefAjax;
+		}
+
+		return oDefAjax;
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 * @param {string} sAction
+	 * @param {Object=} oParameters
+	 * @param {?number=} iTimeout
+	 * @param {string=} sGetAdd = ''
+	 * @param {Array=} aAbortActions = []
+	 */
+	AbstractAjaxRemote.prototype.defaultRequest = function (fCallback, sAction, oParameters, iTimeout, sGetAdd, aAbortActions)
+	{
+		oParameters = oParameters || {};
+		oParameters.Action = sAction;
+
+		sGetAdd = Utils.pString(sGetAdd);
+
+		Plugins.runHook('ajax-default-request', [sAction, oParameters, sGetAdd]);
+
+		return this.ajaxRequest(fCallback, oParameters,
+			Utils.isUnd(iTimeout) ? Consts.Defaults.DefaultAjaxTimeout : Utils.pInt(iTimeout), sGetAdd, aAbortActions);
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 */
+	AbstractAjaxRemote.prototype.noop = function (fCallback)
+	{
+		this.defaultRequest(fCallback, 'Noop');
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 * @param {string} sMessage
+	 * @param {string} sFileName
+	 * @param {number} iLineNo
+	 * @param {string} sLocation
+	 * @param {string} sHtmlCapa
+	 * @param {number} iTime
+	 */
+	AbstractAjaxRemote.prototype.jsError = function (fCallback, sMessage, sFileName, iLineNo, sLocation, sHtmlCapa, iTime)
+	{
+		this.defaultRequest(fCallback, 'JsError', {
+			'Message': sMessage,
+			'FileName': sFileName,
+			'LineNo': iLineNo,
+			'Location': sLocation,
+			'HtmlCapa': sHtmlCapa,
+			'TimeOnPage': iTime
+		});
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 * @param {string} sType
+	 * @param {Array=} mData = null
+	 * @param {boolean=} bIsError = false
+	 */
+	AbstractAjaxRemote.prototype.jsInfo = function (fCallback, sType, mData, bIsError)
+	{
+		this.defaultRequest(fCallback, 'JsInfo', {
+			'Type': sType,
+			'Data': mData,
+			'IsError': (Utils.isUnd(bIsError) ? false : !!bIsError) ? '1' : '0'
+		});
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 */
+	AbstractAjaxRemote.prototype.getPublicKey = function (fCallback)
+	{
+		this.defaultRequest(fCallback, 'GetPublicKey');
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 * @param {string} sVersion
+	 */
+	AbstractAjaxRemote.prototype.jsVersion = function (fCallback, sVersion)
+	{
+		this.defaultRequest(fCallback, 'Version', {
+			'Version': sVersion
+		});
+	};
+
+	module.exports = AbstractAjaxRemote;
+
+}());
\ No newline at end of file
diff --git a/dev/Storage/Admin/Remote.js b/dev/Remote/Admin/Ajax.js
similarity index 85%
rename from dev/Storage/Admin/Remote.js
rename to dev/Remote/Admin/Ajax.js
index b4b4f219b..5173daeb0 100644
--- a/dev/Storage/Admin/Remote.js
+++ b/dev/Remote/Admin/Ajax.js
@@ -1,4 +1,3 @@
-/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
 
 (function () {
 
@@ -7,21 +6,21 @@
 	var
 		_ = require('_'),
 
-		AbstractRemoteStorage = require('Storage/AbstractRemote')
+		AbstractAjaxRemote = require('Remote/AbstractAjax')
 	;
 
 	/**
 	 * @constructor
-	 * @extends AbstractRemoteStorage
+	 * @extends AbstractAjaxRemote
 	 */
 	function RemoteAdminStorage()
 	{
-		AbstractRemoteStorage.call(this);
+		AbstractAjaxRemote.call(this);
 
 		this.oRequests = {};
 	}
 
-	_.extend(RemoteAdminStorage.prototype, AbstractRemoteStorage.prototype);
+	_.extend(RemoteAdminStorage.prototype, AbstractAjaxRemote.prototype);
 
 	/**
 	 * @param {?Function} fCallback
@@ -210,38 +209,57 @@
 	};
 
 	RemoteAdminStorage.prototype.createOrUpdateDomain = function (fCallback,
-		bCreate, sName, sIncHost, iIncPort, sIncSecure, bIncShortLogin,
-		sOutHost, iOutPort, sOutSecure, bOutShortLogin, bOutAuth, sWhiteList)
+		bCreate, sName,
+		sIncHost, iIncPort, sIncSecure, bIncShortLogin,
+		bUseSieve, sSieveAllowRaw, sSieveHost, iSievePort, sSieveSecure,
+		sOutHost, iOutPort, sOutSecure, bOutShortLogin, bOutAuth, bOutPhpMail,
+		sWhiteList)
 	{
 		this.defaultRequest(fCallback, 'AdminDomainSave', {
 			'Create': bCreate ? '1' : '0',
 			'Name': sName,
+
 			'IncHost': sIncHost,
 			'IncPort': iIncPort,
 			'IncSecure': sIncSecure,
 			'IncShortLogin': bIncShortLogin ? '1' : '0',
+
+			'UseSieve': bUseSieve ? '1' : '0',
+			'SieveAllowRaw': sSieveAllowRaw ? '1' : '0',
+			'SieveHost': sSieveHost,
+			'SievePort': iSievePort,
+			'SieveSecure': sSieveSecure,
+
 			'OutHost': sOutHost,
 			'OutPort': iOutPort,
 			'OutSecure': sOutSecure,
 			'OutShortLogin': bOutShortLogin ? '1' : '0',
 			'OutAuth': bOutAuth ? '1' : '0',
+			'OutUsePhpMail': bOutPhpMail ? '1' : '0',
+
 			'WhiteList': sWhiteList
 		});
 	};
 
 	RemoteAdminStorage.prototype.testConnectionForDomain = function (fCallback, sName,
 		sIncHost, iIncPort, sIncSecure,
-		sOutHost, iOutPort, sOutSecure, bOutAuth)
+		bUseSieve, sSieveHost, iSievePort, sSieveSecure,
+		sOutHost, iOutPort, sOutSecure, bOutAuth, bOutPhpMail)
 	{
 		this.defaultRequest(fCallback, 'AdminDomainTest', {
 			'Name': sName,
 			'IncHost': sIncHost,
 			'IncPort': iIncPort,
 			'IncSecure': sIncSecure,
+			'UseSieve': bUseSieve ? '1' : '0',
+			'SieveHost': sSieveHost,
+			'SievePort': iSievePort,
+			'SieveSecure': sSieveSecure,
 			'OutHost': sOutHost,
 			'OutPort': iOutPort,
 			'OutSecure': sOutSecure,
-			'OutAuth': bOutAuth ? '1' : '0'
+			'OutAuth': bOutAuth ? '1' : '0',
+			'OutUsePhpMail': bOutPhpMail ? '1' : '0'
 		});
 	};
 
diff --git a/dev/Storage/App/Remote.js b/dev/Remote/User/Ajax.js
similarity index 59%
rename from dev/Storage/App/Remote.js
rename to dev/Remote/User/Ajax.js
index 92be01fea..20ee13045 100644
--- a/dev/Storage/App/Remote.js
+++ b/dev/Remote/User/Ajax.js
@@ -1,4 +1,3 @@
-/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
 
 (function () {
 
@@ -11,30 +10,34 @@
 		Consts = require('Common/Consts'),
 		Base64 = require('Common/Base64'),
 
-		Settings = require('Storage/Settings'),
-		Cache = require('Storage/App/Cache'),
-		Data = require('Storage/App/Data'),
+		Cache = require('Common/Cache'),
+		Links = require('Common/Links'),
 
-		AbstractRemoteStorage = require('Storage/AbstractRemote')
+		Settings = require('Storage/Settings'),
+
+		AppStore = require('Stores/User/App'),
+		SettingsStore = require('Stores/User/Settings'),
+
+		AbstractAjaxRemote = require('Remote/AbstractAjax')
 	;
 
 	/**
 	 * @constructor
 	 * @extends AbstractRemoteStorage
 	 */
-	function RemoteAppStorage()
+	function RemoteUserAjax()
 	{
-		AbstractRemoteStorage.call(this);
+		AbstractAjaxRemote.call(this);
 
 		this.oRequests = {};
 	}
 
-	_.extend(RemoteAppStorage.prototype, AbstractRemoteStorage.prototype);
+	_.extend(RemoteUserAjax.prototype, AbstractAjaxRemote.prototype);
 
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.folders = function (fCallback)
+	RemoteUserAjax.prototype.folders = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'Folders', {
 			'SentFolder': Settings.settingsGet('SentFolder'),
@@ -55,7 +58,7 @@
 	 * @param {string=} sAdditionalCode
 	 * @param {boolean=} bAdditionalCodeSignMe
 	 */
-	RemoteAppStorage.prototype.login = function (fCallback, sEmail, sLogin, sPassword, bSignMe, sLanguage, sAdditionalCode, bAdditionalCodeSignMe)
+	RemoteUserAjax.prototype.login = function (fCallback, sEmail, sLogin, sPassword, bSignMe, sLanguage, sAdditionalCode, bAdditionalCodeSignMe)
 	{
 		this.defaultRequest(fCallback, 'Login', {
 			'Email': sEmail,
@@ -71,7 +74,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.getTwoFactor = function (fCallback)
+	RemoteUserAjax.prototype.getTwoFactor = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'GetTwoFactorInfo');
 	};
@@ -79,7 +82,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.createTwoFactor = function (fCallback)
+	RemoteUserAjax.prototype.createTwoFactor = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'CreateTwoFactorSecret');
 	};
@@ -87,7 +90,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.clearTwoFactor = function (fCallback)
+	RemoteUserAjax.prototype.clearTwoFactor = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
 	};
@@ -95,7 +98,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.showTwoFactorSecret = function (fCallback)
+	RemoteUserAjax.prototype.showTwoFactorSecret = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'ShowTwoFactorSecret');
 	};
@@ -104,7 +107,7 @@
 	 * @param {?Function} fCallback
 	 * @param {string} sCode
 	 */
-	RemoteAppStorage.prototype.testTwoFactor = function (fCallback, sCode)
+	RemoteUserAjax.prototype.testTwoFactor = function (fCallback, sCode)
 	{
 		this.defaultRequest(fCallback, 'TestTwoFactorInfo', {
 			'Code': sCode
@@ -115,7 +118,7 @@
 	 * @param {?Function} fCallback
 	 * @param {boolean} bEnable
 	 */
-	RemoteAppStorage.prototype.enableTwoFactor = function (fCallback, bEnable)
+	RemoteUserAjax.prototype.enableTwoFactor = function (fCallback, bEnable)
 	{
 		this.defaultRequest(fCallback, 'EnableTwoFactor', {
 			'Enable': bEnable ? '1' : '0'
@@ -125,7 +128,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.clearTwoFactorInfo = function (fCallback)
+	RemoteUserAjax.prototype.clearTwoFactorInfo = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
 	};
@@ -133,7 +136,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.contactsSync = function (fCallback)
+	RemoteUserAjax.prototype.contactsSync = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'ContactsSync', null, Consts.Defaults.ContactsSyncAjaxTimeout);
 	};
@@ -145,7 +148,7 @@
 	 * @param {string} sUser
 	 * @param {string} sPassword
 	 */
-	RemoteAppStorage.prototype.saveContactsSyncData = function (fCallback, bEnable, sUrl, sUser, sPassword)
+	RemoteUserAjax.prototype.saveContactsSyncData = function (fCallback, bEnable, sUrl, sUser, sPassword)
 	{
 		this.defaultRequest(fCallback, 'SaveContactsSyncData', {
 			'Enable': bEnable ? '1' : '0',
@@ -158,15 +161,17 @@
 	/**
 	 * @param {?Function} fCallback
 	 * @param {string} sEmail
-	 * @param {string} sLogin
 	 * @param {string} sPassword
+	 * @param {boolean=} bNew
 	 */
-	RemoteAppStorage.prototype.accountAdd = function (fCallback, sEmail, sLogin, sPassword)
+	RemoteUserAjax.prototype.accountSetup = function (fCallback, sEmail, sPassword, bNew)
 	{
-		this.defaultRequest(fCallback, 'AccountAdd', {
+		bNew = Utils.isUnd(bNew) ? true : !!bNew;
+
+		this.defaultRequest(fCallback, 'AccountSetup', {
 			'Email': sEmail,
-			'Login': sLogin,
-			'Password': sPassword
+			'Password': sPassword,
+			'New': bNew ? '1' : '0'
 		});
 	};
 
@@ -174,13 +179,26 @@
 	 * @param {?Function} fCallback
 	 * @param {string} sEmailToDelete
 	 */
-	RemoteAppStorage.prototype.accountDelete = function (fCallback, sEmailToDelete)
+	RemoteUserAjax.prototype.accountDelete = function (fCallback, sEmailToDelete)
 	{
 		this.defaultRequest(fCallback, 'AccountDelete', {
 			'EmailToDelete': sEmailToDelete
 		});
 	};
 
+	/**
+	 * @param {?Function} fCallback
+	 * @param {Array} aAccounts
+	 * @param {Array} aIdentities
+	 */
+	RemoteUserAjax.prototype.accountsAndIdentitiesSortOrder = function (fCallback, aAccounts, aIdentities)
+	{
+		this.defaultRequest(fCallback, 'AccountsAndIdentitiesSortOrder', {
+			'Accounts': aAccounts,
+			'Identities': aIdentities
+		});
+	};
+
 	/**
 	 * @param {?Function} fCallback
 	 * @param {string} sId
@@ -188,15 +206,20 @@
 	 * @param {string} sName
 	 * @param {string} sReplyTo
 	 * @param {string} sBcc
+	 * @param {string} sSignature
+	 * @param {boolean} bSignatureInsertBefore
 	 */
-	RemoteAppStorage.prototype.identityUpdate = function (fCallback, sId, sEmail, sName, sReplyTo, sBcc)
+	RemoteUserAjax.prototype.identityUpdate = function (fCallback, sId, sEmail, sName, sReplyTo, sBcc,
+		sSignature, bSignatureInsertBefore)
 	{
 		this.defaultRequest(fCallback, 'IdentityUpdate', {
 			'Id': sId,
 			'Email': sEmail,
 			'Name': sName,
 			'ReplyTo': sReplyTo,
-			'Bcc': sBcc
+			'Bcc': sBcc,
+			'Signature': sSignature,
+			'SignatureInsertBefore': bSignatureInsertBefore ? '1' : '0'
 		});
 	};
 
@@ -204,7 +227,7 @@
 	 * @param {?Function} fCallback
 	 * @param {string} sIdToDelete
 	 */
-	RemoteAppStorage.prototype.identityDelete = function (fCallback, sIdToDelete)
+	RemoteUserAjax.prototype.identityDelete = function (fCallback, sIdToDelete)
 	{
 		this.defaultRequest(fCallback, 'IdentityDelete', {
 			'IdToDelete': sIdToDelete
@@ -214,11 +237,95 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.accountsAndIdentities = function (fCallback)
+	RemoteUserAjax.prototype.accountsAndIdentities = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'AccountsAndIdentities');
 	};
 
+	/**
+	 * @param {?Function} fCallback
+	 */
+	RemoteUserAjax.prototype.accountsCounts = function (fCallback)
+	{
+		this.defaultRequest(fCallback, 'AccountsCounts');
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 */
+	RemoteUserAjax.prototype.filtersSave = function (fCallback,
+		aFilters, sRaw, bRawIsActive)
+	{
+		this.defaultRequest(fCallback, 'FiltersSave', {
+			'Raw': sRaw,
+			'RawIsActive': bRawIsActive ? '1' : '0',
+			'Filters': _.map(aFilters, function (oItem) {
+				return oItem.toJson();
+			})
+		});
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 */
+	RemoteUserAjax.prototype.filtersGet = function (fCallback)
+	{
+		this.defaultRequest(fCallback, 'Filters', {});
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 */
+	RemoteUserAjax.prototype.templates = function (fCallback)
+	{
+		this.defaultRequest(fCallback, 'Templates', {});
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 */
+	RemoteUserAjax.prototype.templateGetById = function (fCallback, sID)
+	{
+		this.defaultRequest(fCallback, 'TemplateGetByID', {
+			'ID': sID
+		});
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 */
+	RemoteUserAjax.prototype.templateDelete = function (fCallback, sID)
+	{
+		this.defaultRequest(fCallback, 'TemplateDelete', {
+			'IdToDelete': sID
+		});
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 */
+	RemoteUserAjax.prototype.templateSetup = function (fCallback, sID, sName, sBody)
+	{
+		this.defaultRequest(fCallback, 'TemplateSetup', {
+			'ID': sID,
+			'Name': sName,
+			'Body': sBody
+		});
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 * @param {string} sFolderFullNameRaw
+	 * @param {Array} aUids
+	 */
+	RemoteUserAjax.prototype.messageListSimple = function (fCallback, sFolderFullNameRaw, aUids)
+	{
+		return this.defaultRequest(fCallback, 'MessageListSimple', {
+			'Folder': Utils.pString(sFolderFullNameRaw),
+			'Uids': aUids
+		}, Consts.Defaults.DefaultAjaxTimeout, '', ['MessageListSimple']);
+	};
+
 	/**
 	 * @param {?Function} fCallback
 	 * @param {string} sFolderFullNameRaw
@@ -227,7 +334,7 @@
 	 * @param {string=} sSearch = ''
 	 * @param {boolean=} bSilent = false
 	 */
-	RemoteAppStorage.prototype.messageList = function (fCallback, sFolderFullNameRaw, iOffset, iLimit, sSearch, bSilent)
+	RemoteUserAjax.prototype.messageList = function (fCallback, sFolderFullNameRaw, iOffset, iLimit, sSearch, bSilent)
 	{
 		sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw);
 
@@ -242,30 +349,29 @@
 
 		if ('' !== sFolderHash && ('' === sSearch || -1 === sSearch.indexOf('is:')))
 		{
-			this.defaultRequest(fCallback, 'MessageList', {},
+			return this.defaultRequest(fCallback, 'MessageList', {},
 				'' === sSearch ? Consts.Defaults.DefaultAjaxTimeout : Consts.Defaults.SearchAjaxTimeout,
-				'MessageList/' + Base64.urlsafe_encode([
+				'MessageList/' + Links.subQueryPrefix() + '/' + Base64.urlsafe_encode([
 					sFolderFullNameRaw,
 					iOffset,
 					iLimit,
 					sSearch,
-					Data.projectHash(),
+					AppStore.projectHash(),
 					sFolderHash,
-					'INBOX' === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '',
-					Data.threading() && Data.useThreads() ? '1' : '0',
-					Data.threading() && sFolderFullNameRaw === Data.messageListThreadFolder() ? Data.messageListThreadUids().join(',') : ''
+					Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '',
+					AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0',
+					''
 				].join(String.fromCharCode(0))), bSilent ? [] : ['MessageList']);
 		}
 		else
 		{
-			this.defaultRequest(fCallback, 'MessageList', {
+			return this.defaultRequest(fCallback, 'MessageList', {
 				'Folder': sFolderFullNameRaw,
 				'Offset': iOffset,
 				'Limit': iLimit,
 				'Search': sSearch,
-				'UidNext': 'INBOX' === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '',
-				'UseThreads': Data.threading() && Data.useThreads() ? '1' : '0',
-				'ExpandedThreadUid': Data.threading() && sFolderFullNameRaw === Data.messageListThreadFolder() ? Data.messageListThreadUids().join(',') : ''
+				'UidNext': Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '',
+				'UseThreads': AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0'
 			}, '' === sSearch ? Consts.Defaults.DefaultAjaxTimeout : Consts.Defaults.SearchAjaxTimeout, '', bSilent ? [] : ['MessageList']);
 		}
 	};
@@ -274,7 +380,7 @@
 	 * @param {?Function} fCallback
 	 * @param {Array} aDownloads
 	 */
-	RemoteAppStorage.prototype.messageUploadAttachments = function (fCallback, aDownloads)
+	RemoteUserAjax.prototype.messageUploadAttachments = function (fCallback, aDownloads)
 	{
 		this.defaultRequest(fCallback, 'MessageUploadAttachments', {
 			'Attachments': aDownloads
@@ -287,7 +393,7 @@
 	 * @param {number} iUid
 	 * @return {boolean}
 	 */
-	RemoteAppStorage.prototype.message = function (fCallback, sFolderFullNameRaw, iUid)
+	RemoteUserAjax.prototype.message = function (fCallback, sFolderFullNameRaw, iUid)
 	{
 		sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw);
 		iUid = Utils.pInt(iUid);
@@ -295,11 +401,11 @@
 		if (Cache.getFolderFromCacheList(sFolderFullNameRaw) && 0 < iUid)
 		{
 			this.defaultRequest(fCallback, 'Message', {}, null,
-				'Message/' + Base64.urlsafe_encode([
+				'Message/' + Links.subQueryPrefix() + '/' + Base64.urlsafe_encode([
 					sFolderFullNameRaw,
 					iUid,
-					Data.projectHash(),
-					Data.threading() && Data.useThreads() ? '1' : '0'
+					AppStore.projectHash(),
+					AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0'
 				].join(String.fromCharCode(0))), ['Message']);
 
 			return true;
@@ -308,11 +414,40 @@
 		return false;
 	};
 
+	/**
+	 * @param {?Function} fCallback
+	 * @param {string} sFolderFullNameRaw
+	 * @param {number} iUid
+	 * @return {boolean}
+	 */
+	RemoteUserAjax.prototype.messageThreadsFromCache = function (fCallback, sFolderFullNameRaw, iUid)
+	{
+		sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw);
+		iUid = Utils.pInt(iUid);
+
+		if (Cache.getFolderFromCacheList(sFolderFullNameRaw) && 0 < iUid)
+		{
+			var sFolderHash = Cache.getFolderHash(sFolderFullNameRaw);
+			if (sFolderHash)
+			{
+				this.defaultRequest(fCallback, 'MessageThreadsFromCache', {
+					'Folder': sFolderFullNameRaw,
+					'FolderHash': sFolderHash,
+					'Uid': iUid
+				});
+
+				return true;
+			}
+		}
+
+		return false;
+	};
+
 	/**
 	 * @param {?Function} fCallback
 	 * @param {Array} aExternals
 	 */
-	RemoteAppStorage.prototype.composeUploadExternals = function (fCallback, aExternals)
+	RemoteUserAjax.prototype.composeUploadExternals = function (fCallback, aExternals)
 	{
 		this.defaultRequest(fCallback, 'ComposeUploadExternals', {
 			'Externals': aExternals
@@ -324,7 +459,7 @@
 	 * @param {string} sUrl
 	 * @param {string} sAccessToken
 	 */
-	RemoteAppStorage.prototype.composeUploadDrive = function (fCallback, sUrl, sAccessToken)
+	RemoteUserAjax.prototype.composeUploadDrive = function (fCallback, sUrl, sAccessToken)
 	{
 		this.defaultRequest(fCallback, 'ComposeUploadDrive', {
 			'AccessToken': sAccessToken,
@@ -337,7 +472,7 @@
 	 * @param {string} sFolder
 	 * @param {Array=} aList = []
 	 */
-	RemoteAppStorage.prototype.folderInformation = function (fCallback, sFolder, aList)
+	RemoteUserAjax.prototype.folderInformation = function (fCallback, sFolder, aList)
 	{
 		var
 			bRequest = true,
@@ -375,12 +510,12 @@
 			this.defaultRequest(fCallback, 'FolderInformation', {
 				'Folder': sFolder,
 				'FlagsUids': Utils.isArray(aUids) ? aUids.join(',') : '',
-				'UidNext': 'INBOX' === sFolder ? Cache.getFolderUidNext(sFolder) : ''
+				'UidNext': Cache.getFolderInboxName() === sFolder ? Cache.getFolderUidNext(sFolder) : ''
 			});
 		}
-		else if (Data.useThreads())
+		else if (SettingsStore.useThreads())
 		{
-			require('App/App').reloadFlagsCurrentMessageListAndMessageFromCache();
+			require('App/User').reloadFlagsCurrentMessageListAndMessageFromCache();
 		}
 	};
 
@@ -388,7 +523,7 @@
 	 * @param {?Function} fCallback
 	 * @param {Array} aFolders
 	 */
-	RemoteAppStorage.prototype.folderInformationMultiply = function (fCallback, aFolders)
+	RemoteUserAjax.prototype.folderInformationMultiply = function (fCallback, aFolders)
 	{
 		this.defaultRequest(fCallback, 'FolderInformationMultiply', {
 			'Folders': aFolders
@@ -398,7 +533,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.logout = function (fCallback)
+	RemoteUserAjax.prototype.logout = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'Logout');
 	};
@@ -409,7 +544,7 @@
 	 * @param {Array} aUids
 	 * @param {boolean} bSetFlagged
 	 */
-	RemoteAppStorage.prototype.messageSetFlagged = function (fCallback, sFolderFullNameRaw, aUids, bSetFlagged)
+	RemoteUserAjax.prototype.messageSetFlagged = function (fCallback, sFolderFullNameRaw, aUids, bSetFlagged)
 	{
 		this.defaultRequest(fCallback, 'MessageSetFlagged', {
 			'Folder': sFolderFullNameRaw,
@@ -424,7 +559,7 @@
 	 * @param {Array} aUids
 	 * @param {boolean} bSetSeen
 	 */
-	RemoteAppStorage.prototype.messageSetSeen = function (fCallback, sFolderFullNameRaw, aUids, bSetSeen)
+	RemoteUserAjax.prototype.messageSetSeen = function (fCallback, sFolderFullNameRaw, aUids, bSetSeen)
 	{
 		this.defaultRequest(fCallback, 'MessageSetSeen', {
 			'Folder': sFolderFullNameRaw,
@@ -438,7 +573,7 @@
 	 * @param {string} sFolderFullNameRaw
 	 * @param {boolean} bSetSeen
 	 */
-	RemoteAppStorage.prototype.messageSetSeenToAll = function (fCallback, sFolderFullNameRaw, bSetSeen)
+	RemoteUserAjax.prototype.messageSetSeenToAll = function (fCallback, sFolderFullNameRaw, bSetSeen)
 	{
 		this.defaultRequest(fCallback, 'MessageSetSeenToAll', {
 			'Folder': sFolderFullNameRaw,
@@ -448,10 +583,10 @@
 
 	/**
 	 * @param {?Function} fCallback
+	 * @param {string} sIdentityID
 	 * @param {string} sMessageFolder
 	 * @param {string} sMessageUid
 	 * @param {string} sDraftFolder
-	 * @param {string} sFrom
 	 * @param {string} sTo
 	 * @param {string} sCc
 	 * @param {string} sBcc
@@ -462,24 +597,27 @@
 	 * @param {(Array|null)} aDraftInfo
 	 * @param {string} sInReplyTo
 	 * @param {string} sReferences
+	 * @param {boolean} bMarkAsImportant
 	 */
-	RemoteAppStorage.prototype.saveMessage = function (fCallback, sMessageFolder, sMessageUid, sDraftFolder,
-		sFrom, sTo, sCc, sBcc, sSubject, bTextIsHtml, sText, aAttachments, aDraftInfo, sInReplyTo, sReferences)
+	RemoteUserAjax.prototype.saveMessage = function (fCallback, sIdentityID, sMessageFolder, sMessageUid, sDraftFolder,
+		sTo, sCc, sBcc, sReplyTo, sSubject, bTextIsHtml, sText, aAttachments, aDraftInfo, sInReplyTo, sReferences, bMarkAsImportant)
 	{
 		this.defaultRequest(fCallback, 'SaveMessage', {
+			'IdentityID': sIdentityID,
 			'MessageFolder': sMessageFolder,
 			'MessageUid': sMessageUid,
 			'DraftFolder': sDraftFolder,
-			'From': sFrom,
 			'To': sTo,
 			'Cc': sCc,
 			'Bcc': sBcc,
+			'ReplyTo': sReplyTo,
 			'Subject': sSubject,
 			'TextIsHtml': bTextIsHtml ? '1' : '0',
 			'Text': sText,
 			'DraftInfo': aDraftInfo,
 			'InReplyTo': sInReplyTo,
 			'References': sReferences,
+			'MarkAsImportant': bMarkAsImportant ? '1' : '0',
 			'Attachments': aAttachments
 		}, Consts.Defaults.SaveMessageAjaxTimeout);
 	};
@@ -493,7 +631,7 @@
 	 * @param {string} sSubject
 	 * @param {string} sText
 	 */
-	RemoteAppStorage.prototype.sendReadReceiptMessage = function (fCallback, sMessageFolder, sMessageUid, sReadReceipt, sSubject, sText)
+	RemoteUserAjax.prototype.sendReadReceiptMessage = function (fCallback, sMessageFolder, sMessageUid, sReadReceipt, sSubject, sText)
 	{
 		this.defaultRequest(fCallback, 'SendReadReceiptMessage', {
 			'MessageFolder': sMessageFolder,
@@ -506,13 +644,14 @@
 
 	/**
 	 * @param {?Function} fCallback
+	 * @param {string} sIdentityID
 	 * @param {string} sMessageFolder
 	 * @param {string} sMessageUid
 	 * @param {string} sSentFolder
-	 * @param {string} sFrom
 	 * @param {string} sTo
 	 * @param {string} sCc
 	 * @param {string} sBcc
+	 * @param {string} sReplyTo
 	 * @param {string} sSubject
 	 * @param {boolean} bTextIsHtml
 	 * @param {string} sText
@@ -520,26 +659,32 @@
 	 * @param {(Array|null)} aDraftInfo
 	 * @param {string} sInReplyTo
 	 * @param {string} sReferences
+	 * @param {boolean} bRequestDsn
 	 * @param {boolean} bRequestReadReceipt
+	 * @param {boolean} bMarkAsImportant
 	 */
-	RemoteAppStorage.prototype.sendMessage = function (fCallback, sMessageFolder, sMessageUid, sSentFolder,
-		sFrom, sTo, sCc, sBcc, sSubject, bTextIsHtml, sText, aAttachments, aDraftInfo, sInReplyTo, sReferences, bRequestReadReceipt)
+	RemoteUserAjax.prototype.sendMessage = function (fCallback, sIdentityID, sMessageFolder, sMessageUid, sSentFolder,
+		sTo, sCc, sBcc, sReplyTo, sSubject, bTextIsHtml, sText, aAttachments, aDraftInfo, sInReplyTo, sReferences,
+		bRequestDsn, bRequestReadReceipt, bMarkAsImportant)
 	{
 		this.defaultRequest(fCallback, 'SendMessage', {
+			'IdentityID': sIdentityID,
 			'MessageFolder': sMessageFolder,
 			'MessageUid': sMessageUid,
 			'SentFolder': sSentFolder,
-			'From': sFrom,
 			'To': sTo,
 			'Cc': sCc,
 			'Bcc': sBcc,
+			'ReplyTo': sReplyTo,
 			'Subject': sSubject,
 			'TextIsHtml': bTextIsHtml ? '1' : '0',
 			'Text': sText,
 			'DraftInfo': aDraftInfo,
 			'InReplyTo': sInReplyTo,
 			'References': sReferences,
+			'Dsn': bRequestDsn ? '1' : '0',
 			'ReadReceiptRequest': bRequestReadReceipt ? '1' : '0',
+			'MarkAsImportant': bMarkAsImportant ? '1' : '0',
 			'Attachments': aAttachments
 		}, Consts.Defaults.SendMessageAjaxTimeout);
 	};
@@ -548,7 +693,7 @@
 	 * @param {?Function} fCallback
 	 * @param {Object} oData
 	 */
-	RemoteAppStorage.prototype.saveSystemFolders = function (fCallback, oData)
+	RemoteUserAjax.prototype.saveSystemFolders = function (fCallback, oData)
 	{
 		this.defaultRequest(fCallback, 'SystemFoldersUpdate', oData);
 	};
@@ -557,7 +702,7 @@
 	 * @param {?Function} fCallback
 	 * @param {Object} oData
 	 */
-	RemoteAppStorage.prototype.saveSettings = function (fCallback, oData)
+	RemoteUserAjax.prototype.saveSettings = function (fCallback, oData)
 	{
 		this.defaultRequest(fCallback, 'SettingsUpdate', oData);
 	};
@@ -567,7 +712,7 @@
 	 * @param {string} sPrevPassword
 	 * @param {string} sNewPassword
 	 */
-	RemoteAppStorage.prototype.changePassword = function (fCallback, sPrevPassword, sNewPassword)
+	RemoteUserAjax.prototype.changePassword = function (fCallback, sPrevPassword, sNewPassword)
 	{
 		this.defaultRequest(fCallback, 'ChangePassword', {
 			'PrevPassword': sPrevPassword,
@@ -575,48 +720,11 @@
 		});
 	};
 
-	/**
-	 * @param {?Function} fCallback
-	 * @param {string} sNewFolderName
-	 * @param {string} sParentName
-	 */
-	RemoteAppStorage.prototype.folderCreate = function (fCallback, sNewFolderName, sParentName)
-	{
-		this.defaultRequest(fCallback, 'FolderCreate', {
-			'Folder': sNewFolderName,
-			'Parent': sParentName
-		}, null, '', ['Folders']);
-	};
-
 	/**
 	 * @param {?Function} fCallback
 	 * @param {string} sFolderFullNameRaw
 	 */
-	RemoteAppStorage.prototype.folderDelete = function (fCallback, sFolderFullNameRaw)
-	{
-		this.defaultRequest(fCallback, 'FolderDelete', {
-			'Folder': sFolderFullNameRaw
-		}, null, '', ['Folders']);
-	};
-
-	/**
-	 * @param {?Function} fCallback
-	 * @param {string} sPrevFolderFullNameRaw
-	 * @param {string} sNewFolderName
-	 */
-	RemoteAppStorage.prototype.folderRename = function (fCallback, sPrevFolderFullNameRaw, sNewFolderName)
-	{
-		this.defaultRequest(fCallback, 'FolderRename', {
-			'Folder': sPrevFolderFullNameRaw,
-			'NewFolderName': sNewFolderName
-		}, null, '', ['Folders']);
-	};
-
-	/**
-	 * @param {?Function} fCallback
-	 * @param {string} sFolderFullNameRaw
-	 */
-	RemoteAppStorage.prototype.folderClear = function (fCallback, sFolderFullNameRaw)
+	RemoteUserAjax.prototype.folderClear = function (fCallback, sFolderFullNameRaw)
 	{
 		this.defaultRequest(fCallback, 'FolderClear', {
 			'Folder': sFolderFullNameRaw
@@ -628,7 +736,7 @@
 	 * @param {string} sFolderFullNameRaw
 	 * @param {boolean} bSubscribe
 	 */
-	RemoteAppStorage.prototype.folderSetSubscribe = function (fCallback, sFolderFullNameRaw, bSubscribe)
+	RemoteUserAjax.prototype.folderSetSubscribe = function (fCallback, sFolderFullNameRaw, bSubscribe)
 	{
 		this.defaultRequest(fCallback, 'FolderSubscribe', {
 			'Folder': sFolderFullNameRaw,
@@ -643,7 +751,7 @@
 	 * @param {Array} aUids
 	 * @param {string=} sLearning
 	 */
-	RemoteAppStorage.prototype.messagesMove = function (fCallback, sFolder, sToFolder, aUids, sLearning)
+	RemoteUserAjax.prototype.messagesMove = function (fCallback, sFolder, sToFolder, aUids, sLearning)
 	{
 		this.defaultRequest(fCallback, 'MessageMove', {
 			'FromFolder': sFolder,
@@ -659,7 +767,7 @@
 	 * @param {string} sToFolder
 	 * @param {Array} aUids
 	 */
-	RemoteAppStorage.prototype.messagesCopy = function (fCallback, sFolder, sToFolder, aUids)
+	RemoteUserAjax.prototype.messagesCopy = function (fCallback, sFolder, sToFolder, aUids)
 	{
 		this.defaultRequest(fCallback, 'MessageCopy', {
 			'FromFolder': sFolder,
@@ -673,7 +781,7 @@
 	 * @param {string} sFolder
 	 * @param {Array} aUids
 	 */
-	RemoteAppStorage.prototype.messagesDelete = function (fCallback, sFolder, aUids)
+	RemoteUserAjax.prototype.messagesDelete = function (fCallback, sFolder, aUids)
 	{
 		this.defaultRequest(fCallback, 'MessageDelete', {
 			'Folder': sFolder,
@@ -684,7 +792,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.appDelayStart = function (fCallback)
+	RemoteUserAjax.prototype.appDelayStart = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'AppDelayStart');
 	};
@@ -692,7 +800,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.quota = function (fCallback)
+	RemoteUserAjax.prototype.quota = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'Quota');
 	};
@@ -703,7 +811,7 @@
 	 * @param {number} iLimit
 	 * @param {string} sSearch
 	 */
-	RemoteAppStorage.prototype.contacts = function (fCallback, iOffset, iLimit, sSearch)
+	RemoteUserAjax.prototype.contacts = function (fCallback, iOffset, iLimit, sSearch)
 	{
 		this.defaultRequest(fCallback, 'Contacts', {
 			'Offset': iOffset,
@@ -714,13 +822,15 @@
 
 	/**
 	 * @param {?Function} fCallback
+	 * @param {string} sRequestUid
+	 * @param {string} sUid
+	 * @param {Array} aProperties
 	 */
-	RemoteAppStorage.prototype.contactSave = function (fCallback, sRequestUid, sUid, sTags, aProperties)
+	RemoteUserAjax.prototype.contactSave = function (fCallback, sRequestUid, sUid, aProperties)
 	{
 		this.defaultRequest(fCallback, 'ContactSave', {
 			'RequestUid': sRequestUid,
 			'Uid': Utils.trim(sUid),
-			'Tags': Utils.trim(sTags),
 			'Properties': aProperties
 		});
 	};
@@ -729,7 +839,7 @@
 	 * @param {?Function} fCallback
 	 * @param {Array} aUids
 	 */
-	RemoteAppStorage.prototype.contactsDelete = function (fCallback, aUids)
+	RemoteUserAjax.prototype.contactsDelete = function (fCallback, aUids)
 	{
 		this.defaultRequest(fCallback, 'ContactsDelete', {
 			'Uids': aUids.join(',')
@@ -741,7 +851,7 @@
 	 * @param {string} sQuery
 	 * @param {number} iPage
 	 */
-	RemoteAppStorage.prototype.suggestions = function (fCallback, sQuery, iPage)
+	RemoteUserAjax.prototype.suggestions = function (fCallback, sQuery, iPage)
 	{
 		this.defaultRequest(fCallback, 'Suggestions', {
 			'Query': sQuery,
@@ -752,7 +862,15 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.facebookUser = function (fCallback)
+	RemoteUserAjax.prototype.clearUserBackground = function (fCallback)
+	{
+		this.defaultRequest(fCallback, 'ClearUserBackground');
+	};
+
+	/**
+	 * @param {?Function} fCallback
+	 */
+	RemoteUserAjax.prototype.facebookUser = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'SocialFacebookUserInformation');
 	};
@@ -760,7 +878,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.facebookDisconnect = function (fCallback)
+	RemoteUserAjax.prototype.facebookDisconnect = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'SocialFacebookDisconnect');
 	};
@@ -768,7 +886,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.twitterUser = function (fCallback)
+	RemoteUserAjax.prototype.twitterUser = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'SocialTwitterUserInformation');
 	};
@@ -776,7 +894,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.twitterDisconnect = function (fCallback)
+	RemoteUserAjax.prototype.twitterDisconnect = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'SocialTwitterDisconnect');
 	};
@@ -784,7 +902,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.googleUser = function (fCallback)
+	RemoteUserAjax.prototype.googleUser = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'SocialGoogleUserInformation');
 	};
@@ -792,7 +910,7 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.googleDisconnect = function (fCallback)
+	RemoteUserAjax.prototype.googleDisconnect = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'SocialGoogleDisconnect');
 	};
@@ -800,11 +918,11 @@
 	/**
 	 * @param {?Function} fCallback
 	 */
-	RemoteAppStorage.prototype.socialUsers = function (fCallback)
+	RemoteUserAjax.prototype.socialUsers = function (fCallback)
 	{
 		this.defaultRequest(fCallback, 'SocialUsers');
 	};
 
-	module.exports = new RemoteAppStorage();
+	module.exports = new RemoteUserAjax();
 
 }());
\ No newline at end of file
diff --git a/dev/Screen/AbstractSettings.js b/dev/Screen/AbstractSettings.js
index e4d62572e..0727236f4 100644
--- a/dev/Screen/AbstractSettings.js
+++ b/dev/Screen/AbstractSettings.js
@@ -10,7 +10,7 @@
 
 		Globals = require('Common/Globals'),
 		Utils = require('Common/Utils'),
-		LinkBuilder = require('Common/LinkBuilder'),
+		Links = require('Common/Links'),
 
 		kn = require('Knoin/Knoin'),
 		AbstractScreen = require('Knoin/AbstractScreen')
@@ -103,7 +103,7 @@
 					RoutedSettingsViewModel.__vm = oSettingsScreen;
 
 					ko.applyBindingAccessorsToNode(oViewModelDom[0], {
-						'i18nInit': true,
+						'translatorInit': true,
 						'template': function () { return {'name': RoutedSettingsViewModel.__rlSettingsData.Template}; }
 					}, oSettingsScreen);
 
@@ -133,7 +133,7 @@
 					{
 						self.oCurrentSubScreen.viewModelDom.show();
 						Utils.delegateRun(self.oCurrentSubScreen, 'onShow');
-						Utils.delegateRun(self.oCurrentSubScreen, 'onFocus', [], 200);
+						Utils.delegateRun(self.oCurrentSubScreen, 'onShowWithDelay', [], 200);
 
 						_.each(self.menu(), function (oItem) {
 							oItem.selected(oSettingsScreen && oSettingsScreen.__rlSettingsData && oItem.route === oSettingsScreen.__rlSettingsData.Route);
@@ -149,7 +149,7 @@
 		}
 		else
 		{
-			kn.setHash(LinkBuilder.settings(), false, true);
+			kn.setHash(Links.settings(), false, true);
 		}
 	};
 
diff --git a/dev/Screen/Admin/Login.js b/dev/Screen/Admin/Login.js
index 9dd723aef..5624218a8 100644
--- a/dev/Screen/Admin/Login.js
+++ b/dev/Screen/Admin/Login.js
@@ -24,7 +24,7 @@
 
 	LoginAdminScreen.prototype.onShow = function ()
 	{
-		require('App/Admin').setTitle('');
+		require('App/Admin').setWindowTitle('');
 	};
 
 	module.exports = LoginAdminScreen;
diff --git a/dev/Screen/Admin/Settings.js b/dev/Screen/Admin/Settings.js
index 3b76641e7..71267ca4c 100644
--- a/dev/Screen/Admin/Settings.js
+++ b/dev/Screen/Admin/Settings.js
@@ -8,6 +8,8 @@
 
 		kn = require('Knoin/Knoin'),
 
+		Plugins = require('Common/Plugins'),
+
 		AbstractSettings = require('Screen/AbstractSettings')
 	;
 
@@ -31,37 +33,39 @@
 	SettingsAdminScreen.prototype.setupSettings = function (fCallback)
 	{
 		kn.addSettingsViewModel(require('Settings/Admin/General'),
-			'AdminSettingsGeneral', 'General', 'general', true);
+			'AdminSettingsGeneral', 'TABS_LABELS/LABEL_GENERAL_NAME', 'general', true);
 
 		kn.addSettingsViewModel(require('Settings/Admin/Login'),
-			'AdminSettingsLogin', 'Login', 'login');
+			'AdminSettingsLogin', 'TABS_LABELS/LABEL_LOGIN_NAME', 'login');
 
 		kn.addSettingsViewModel(require('Settings/Admin/Branding'),
-			'AdminSettingsBranding', 'Branding', 'branding');
+			'AdminSettingsBranding', 'TABS_LABELS/LABEL_BRANDING_NAME', 'branding');
 
 		kn.addSettingsViewModel(require('Settings/Admin/Contacts'),
-			'AdminSettingsContacts', 'Contacts', 'contacts');
+			'AdminSettingsContacts', 'TABS_LABELS/LABEL_CONTACTS_NAME', 'contacts');
 
 		kn.addSettingsViewModel(require('Settings/Admin/Domains'),
-			'AdminSettingsDomains', 'Domains', 'domains');
+			'AdminSettingsDomains', 'TABS_LABELS/LABEL_DOMAINS_NAME', 'domains');
 
 		kn.addSettingsViewModel(require('Settings/Admin/Security'),
-			'AdminSettingsSecurity', 'Security', 'security');
+			'AdminSettingsSecurity', 'TABS_LABELS/LABEL_SECURITY_NAME', 'security');
 
 		kn.addSettingsViewModel(require('Settings/Admin/Social'),
-			'AdminSettingsSocial', 'Social', 'social');
+			'AdminSettingsSocial', 'TABS_LABELS/LABEL_INTEGRATION_NAME', 'integrations');
 
 		kn.addSettingsViewModel(require('Settings/Admin/Plugins'),
-			'AdminSettingsPlugins', 'Plugins', 'plugins');
+			'AdminSettingsPlugins', 'TABS_LABELS/LABEL_PLUGINS_NAME', 'plugins');
 
 		kn.addSettingsViewModel(require('Settings/Admin/Packages'),
-			'AdminSettingsPackages', 'Packages', 'packages');
+			'AdminSettingsPackages', 'TABS_LABELS/LABEL_PACKAGES_NAME', 'packages');
 
 		kn.addSettingsViewModel(require('Settings/Admin/Licensing'),
-			'AdminSettingsLicensing', 'Licensing', 'licensing');
+			'AdminSettingsLicensing', 'TABS_LABELS/LABEL_LICENSING_NAME', 'licensing');
 
 		kn.addSettingsViewModel(require('Settings/Admin/About'),
-			'AdminSettingsAbout', 'About', 'about');
+			'AdminSettingsAbout', 'TABS_LABELS/LABEL_ABOUT_NAME', 'about');
+
+		Plugins.runSettingsViewModelHooks(true);
 
 		if (fCallback)
 		{
@@ -71,7 +75,7 @@
 
 	SettingsAdminScreen.prototype.onShow = function ()
 	{
-		require('App/Admin').setTitle('');
+		require('App/Admin').setWindowTitle('');
 	};
 
 	module.exports = SettingsAdminScreen;
diff --git a/dev/Screen/App/About.js b/dev/Screen/App/About.js
deleted file mode 100644
index 18accf34a..000000000
--- a/dev/Screen/App/About.js
+++ /dev/null
@@ -1,32 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-
-		AbstractScreen = require('Knoin/AbstractScreen')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractScreen
-	 */
-	function AboutAppScreen()
-	{
-		AbstractScreen.call(this, 'about', [
-			require('View/App/About')
-		]);
-	}
-
-	_.extend(AboutAppScreen.prototype, AbstractScreen.prototype);
-
-	AboutAppScreen.prototype.onShow = function ()
-	{
-		require('App/App').setTitle('RainLoop');
-	};
-
-	module.exports = AboutAppScreen;
-
-}());
\ No newline at end of file
diff --git a/dev/Screen/App/Login.js b/dev/Screen/App/Login.js
deleted file mode 100644
index b578e43a0..000000000
--- a/dev/Screen/App/Login.js
+++ /dev/null
@@ -1,32 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-
-		AbstractScreen = require('Knoin/AbstractScreen')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractScreen
-	 */
-	function LoginAppScreen()
-	{
-		AbstractScreen.call(this, 'login', [
-			require('View/App/Login')
-		]);
-	}
-
-	_.extend(LoginAppScreen.prototype, AbstractScreen.prototype);
-
-	LoginAppScreen.prototype.onShow = function ()
-	{
-		require('App/App').setTitle('');
-	};
-
-	module.exports = LoginAppScreen;
-
-}());
\ No newline at end of file
diff --git a/dev/Screen/App/MailBox.js b/dev/Screen/App/MailBox.js
deleted file mode 100644
index 94e125184..000000000
--- a/dev/Screen/App/MailBox.js
+++ /dev/null
@@ -1,196 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-
-		Enums = require('Common/Enums'),
-		Globals = require('Common/Globals'),
-		Utils = require('Common/Utils'),
-		Events = require('Common/Events'),
-
-		Settings = require('Storage/Settings'),
-		Data = require('Storage/App/Data'),
-		Cache = require('Storage/App/Cache'),
-		Remote = require('Storage/App/Remote'),
-
-		AbstractScreen = require('Knoin/AbstractScreen')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractScreen
-	 */
-	function MailBoxAppScreen()
-	{
-		AbstractScreen.call(this, 'mailbox', [
-			require('View/App/MailBox/SystemDropDown'),
-			require('View/App/MailBox/FolderList'),
-			require('View/App/MailBox/MessageList'),
-			require('View/App/MailBox/MessageView')
-		]);
-
-		this.oLastRoute = {};
-	}
-
-	_.extend(MailBoxAppScreen.prototype, AbstractScreen.prototype);
-
-	/**
-	 * @type {Object}
-	 */
-	MailBoxAppScreen.prototype.oLastRoute = {};
-
-	MailBoxAppScreen.prototype.setNewTitle  = function ()
-	{
-		var
-			sEmail = Data.accountEmail(),
-			nFoldersInboxUnreadCount = Data.foldersInboxUnreadCount()
-		;
-
-		require('App/App').setTitle(('' === sEmail ? '' :
-			(0 < nFoldersInboxUnreadCount ? '(' + nFoldersInboxUnreadCount + ') ' : ' ') + sEmail + ' - ') + Utils.i18n('TITLES/MAILBOX'));
-	};
-
-	MailBoxAppScreen.prototype.onShow = function ()
-	{
-		this.setNewTitle();
-		Globals.keyScope(Enums.KeyState.MessageList);
-	};
-
-	/**
-	 * @param {string} sFolderHash
-	 * @param {number} iPage
-	 * @param {string} sSearch
-	 * @param {boolean=} bPreview = false
-	 */
-	MailBoxAppScreen.prototype.onRoute = function (sFolderHash, iPage, sSearch, bPreview)
-	{
-		if (Utils.isUnd(bPreview) ? false : !!bPreview)
-		{
-			if (Enums.Layout.NoPreview === Data.layout() && !Data.message())
-			{
-				require('App/App').historyBack();
-			}
-		}
-		else
-		{
-			var
-				sFolderFullNameRaw = Cache.getFolderFullNameRaw(sFolderHash),
-				oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw)
-			;
-
-			if (oFolder)
-			{
-				Data
-					.currentFolder(oFolder)
-					.messageListPage(iPage)
-					.messageListSearch(sSearch)
-				;
-
-				if (Enums.Layout.NoPreview === Data.layout() && Data.message())
-				{
-					Data.message(null);
-				}
-
-				require('App/App').reloadMessageList();
-			}
-		}
-	};
-
-	MailBoxAppScreen.prototype.onStart = function ()
-	{
-		var
-			fResizeFunction = function () {
-				Utils.windowResize();
-			}
-		;
-
-		if (Settings.capa(Enums.Capa.AdditionalAccounts) || Settings.capa(Enums.Capa.AdditionalIdentities))
-		{
-			require('App/App').accountsAndIdentities();
-		}
-
-		_.delay(function () {
-			if ('INBOX' !== Data.currentFolderFullNameRaw())
-			{
-				require('App/App').folderInformation('INBOX');
-			}
-		}, 1000);
-
-		_.delay(function () {
-			require('App/App').quota();
-		}, 5000);
-
-		_.delay(function () {
-			Remote.appDelayStart(Utils.emptyFunction);
-		}, 35000);
-
-		Globals.$html.toggleClass('rl-no-preview-pane', Enums.Layout.NoPreview === Data.layout());
-
-		Data.folderList.subscribe(fResizeFunction);
-		Data.messageList.subscribe(fResizeFunction);
-		Data.message.subscribe(fResizeFunction);
-
-		Data.layout.subscribe(function (nValue) {
-			Globals.$html.toggleClass('rl-no-preview-pane', Enums.Layout.NoPreview === nValue);
-		});
-
-		Events.sub('mailbox.inbox-unread-count', function (nCount) {
-			Data.foldersInboxUnreadCount(nCount);
-		});
-
-		Data.foldersInboxUnreadCount.subscribe(function () {
-			this.setNewTitle();
-		}, this);
-	};
-
-	/**
-	 * @return {Array}
-	 */
-	MailBoxAppScreen.prototype.routes = function ()
-	{
-		var
-			fNormP = function () {
-				return ['Inbox', 1, '', true];
-			},
-			fNormS = function (oRequest, oVals) {
-				oVals[0] = Utils.pString(oVals[0]);
-				oVals[1] = Utils.pInt(oVals[1]);
-				oVals[1] = 0 >= oVals[1] ? 1 : oVals[1];
-				oVals[2] = Utils.pString(oVals[2]);
-
-				if ('' === oRequest)
-				{
-					oVals[0] = 'Inbox';
-					oVals[1] = 1;
-				}
-
-				return [decodeURI(oVals[0]), oVals[1], decodeURI(oVals[2]), false];
-			},
-			fNormD = function (oRequest, oVals) {
-				oVals[0] = Utils.pString(oVals[0]);
-				oVals[1] = Utils.pString(oVals[1]);
-
-				if ('' === oRequest)
-				{
-					oVals[0] = 'Inbox';
-				}
-
-				return [decodeURI(oVals[0]), 1, decodeURI(oVals[1]), false];
-			}
-		;
-
-		return [
-			[/^([a-zA-Z0-9]+)\/p([1-9][0-9]*)\/(.+)\/?$/, {'normalize_': fNormS}],
-			[/^([a-zA-Z0-9]+)\/p([1-9][0-9]*)$/, {'normalize_': fNormS}],
-			[/^([a-zA-Z0-9]+)\/(.+)\/?$/, {'normalize_': fNormD}],
-			[/^message-preview$/,  {'normalize_': fNormP}],
-			[/^([^\/]*)$/,  {'normalize_': fNormS}]
-		];
-	};
-
-	module.exports = MailBoxAppScreen;
-
-}());
\ No newline at end of file
diff --git a/dev/Screen/App/Settings.js b/dev/Screen/App/Settings.js
deleted file mode 100644
index 8392f0a60..000000000
--- a/dev/Screen/App/Settings.js
+++ /dev/null
@@ -1,132 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-
-		Enums = require('Common/Enums'),
-		Utils = require('Common/Utils'),
-		Globals = require('Common/Globals'),
-
-		Settings = require('Storage/Settings'),
-
-		kn = require('Knoin/Knoin'),
-
-		AbstractSettingsScreen = require('Screen/AbstractSettings')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractSettingsScreen
-	 */
-	function SettingsAppScreen()
-	{
-		AbstractSettingsScreen.call(this, [
-			require('View/App/Settings/SystemDropDown'),
-			require('View/App/Settings/Menu'),
-			require('View/App/Settings/Pane')
-		]);
-
-		Utils.initOnStartOrLangChange(function () {
-			this.sSettingsTitle = Utils.i18n('TITLES/SETTINGS');
-		}, this, function () {
-			this.setSettingsTitle();
-		});
-	}
-
-	_.extend(SettingsAppScreen.prototype, AbstractSettingsScreen.prototype);
-
-	/**
-	 * @param {Function=} fCallback
-	 */
-	SettingsAppScreen.prototype.setupSettings = function (fCallback)
-	{
-		kn.addSettingsViewModel(require('Settings/App/General'),
-			'SettingsGeneral', 'SETTINGS_LABELS/LABEL_GENERAL_NAME', 'general', true);
-
-		if (Settings.settingsGet('ContactsIsAllowed'))
-		{
-			kn.addSettingsViewModel(require('Settings/App/Contacts'),
-				'SettingsContacts', 'SETTINGS_LABELS/LABEL_CONTACTS_NAME', 'contacts');
-		}
-
-		if (Settings.capa(Enums.Capa.AdditionalAccounts))
-		{
-			kn.addSettingsViewModel(require('Settings/App/Accounts'),
-				'SettingsAccounts', 'SETTINGS_LABELS/LABEL_ACCOUNTS_NAME', 'accounts');
-		}
-
-		if (Settings.capa(Enums.Capa.AdditionalIdentities))
-		{
-			kn.addSettingsViewModel(require('Settings/App/Identities'),
-				'SettingsIdentities', 'SETTINGS_LABELS/LABEL_IDENTITIES_NAME', 'identities');
-		}
-		else
-		{
-			kn.addSettingsViewModel(require('Settings/App/Identity'),
-				'SettingsIdentity', 'SETTINGS_LABELS/LABEL_IDENTITY_NAME', 'identity');
-		}
-
-		if (Settings.capa(Enums.Capa.Filters))
-		{
-			kn.addSettingsViewModel(require('Settings/App/Filters'),
-				'SettingsFilters', 'SETTINGS_LABELS/LABEL_FILTERS_NAME', 'filters');
-		}
-
-		if (Settings.capa(Enums.Capa.TwoFactor))
-		{
-			kn.addSettingsViewModel(require('Settings/App/Security'),
-				'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security');
-		}
-
-		if (Settings.settingsGet('AllowGoogleSocial') ||
-			Settings.settingsGet('AllowFacebookSocial') ||
-			Settings.settingsGet('AllowTwitterSocial'))
-		{
-			kn.addSettingsViewModel(require('Settings/App/Social'),
-				'SettingsSocial', 'SETTINGS_LABELS/LABEL_SOCIAL_NAME', 'social');
-		}
-
-		if (Settings.settingsGet('ChangePasswordIsAllowed'))
-		{
-			kn.addSettingsViewModel(require('Settings/App/ChangePassword'),
-				'SettingsChangePassword', 'SETTINGS_LABELS/LABEL_CHANGE_PASSWORD_NAME', 'change-password');
-		}
-
-		kn.addSettingsViewModel(require('Settings/App/Folders'),
-			'SettingsFolders', 'SETTINGS_LABELS/LABEL_FOLDERS_NAME', 'folders');
-
-		if (Settings.capa(Enums.Capa.Themes))
-		{
-			kn.addSettingsViewModel(require('Settings/App/Themes'),
-				'SettingsThemes', 'SETTINGS_LABELS/LABEL_THEMES_NAME', 'themes');
-		}
-
-		if (Settings.capa(Enums.Capa.OpenPGP))
-		{
-			kn.addSettingsViewModel(require('Settings/App/OpenPGP'),
-				'SettingsOpenPGP', 'SETTINGS_LABELS/LABEL_OPEN_PGP_NAME', 'openpgp');
-		}
-
-		if (fCallback)
-		{
-			fCallback();
-		}
-	};
-
-	SettingsAppScreen.prototype.onShow = function ()
-	{
-		this.setSettingsTitle();
-		Globals.keyScope(Enums.KeyState.Settings);
-	};
-
-	SettingsAppScreen.prototype.setSettingsTitle = function ()
-	{
-		require('App/App').setTitle(this.sSettingsTitle);
-	};
-
-	module.exports = SettingsAppScreen;
-
-}());
\ No newline at end of file
diff --git a/dev/Screen/User/About.js b/dev/Screen/User/About.js
new file mode 100644
index 000000000..424f0728c
--- /dev/null
+++ b/dev/Screen/User/About.js
@@ -0,0 +1,32 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+
+		AbstractScreen = require('Knoin/AbstractScreen')
+	;
+
+	/**
+	 * @constructor
+	 * @extends AbstractScreen
+	 */
+	function AboutUserScreen()
+	{
+		AbstractScreen.call(this, 'about', [
+			require('View/User/About')
+		]);
+	}
+
+	_.extend(AboutUserScreen.prototype, AbstractScreen.prototype);
+
+	AboutUserScreen.prototype.onShow = function ()
+	{
+		require('App/User').setWindowTitle('RainLoop');
+	};
+
+	module.exports = AboutUserScreen;
+
+}());
\ No newline at end of file
diff --git a/dev/Screen/User/Login.js b/dev/Screen/User/Login.js
new file mode 100644
index 000000000..744aa2686
--- /dev/null
+++ b/dev/Screen/User/Login.js
@@ -0,0 +1,32 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+
+		AbstractScreen = require('Knoin/AbstractScreen')
+	;
+
+	/**
+	 * @constructor
+	 * @extends AbstractScreen
+	 */
+	function LoginUserScreen()
+	{
+		AbstractScreen.call(this, 'login', [
+			require('View/User/Login')
+		]);
+	}
+
+	_.extend(LoginUserScreen.prototype, AbstractScreen.prototype);
+
+	LoginUserScreen.prototype.onShow = function ()
+	{
+		require('App/User').setWindowTitle('');
+	};
+
+	module.exports = LoginUserScreen;
+
+}());
\ No newline at end of file
diff --git a/dev/Screen/User/MailBox.js b/dev/Screen/User/MailBox.js
new file mode 100644
index 000000000..64bb526cf
--- /dev/null
+++ b/dev/Screen/User/MailBox.js
@@ -0,0 +1,175 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+
+		Enums = require('Common/Enums'),
+		Globals = require('Common/Globals'),
+		Utils = require('Common/Utils'),
+		Events = require('Common/Events'),
+		Translator = require('Common/Translator'),
+
+		Cache = require('Common/Cache'),
+
+		AppStore = require('Stores/User/App'),
+		AccountStore = require('Stores/User/Account'),
+		SettingsStore = require('Stores/User/Settings'),
+		FolderStore = require('Stores/User/Folder'),
+		MessageStore = require('Stores/User/Message'),
+
+		AbstractScreen = require('Knoin/AbstractScreen')
+	;
+
+	/**
+	 * @constructor
+	 * @extends AbstractScreen
+	 */
+	function MailBoxUserScreen()
+	{
+		AbstractScreen.call(this, 'mailbox', [
+			require('View/User/MailBox/SystemDropDown'),
+			require('View/User/MailBox/FolderList'),
+			require('View/User/MailBox/MessageList'),
+			require('View/User/MailBox/MessageView')
+		]);
+
+		this.oLastRoute = {};
+	}
+
+	_.extend(MailBoxUserScreen.prototype, AbstractScreen.prototype);
+
+	/**
+	 * @type {Object}
+	 */
+	MailBoxUserScreen.prototype.oLastRoute = {};
+
+	MailBoxUserScreen.prototype.updateWindowTitle  = function ()
+	{
+		var
+			sEmail = AccountStore.email(),
+			nFoldersInboxUnreadCount = FolderStore.foldersInboxUnreadCount()
+		;
+
+		require('App/User').setWindowTitle(('' === sEmail ? '' : '' +
+			(0 < nFoldersInboxUnreadCount ? '(' + nFoldersInboxUnreadCount + ') ' : ' ') +
+				sEmail + ' - ') + Translator.i18n('TITLES/MAILBOX'));
+	};
+
+	MailBoxUserScreen.prototype.onShow = function ()
+	{
+		this.updateWindowTitle();
+
+		AppStore.focusedState(Enums.Focused.None);
+		AppStore.focusedState(Enums.Focused.MessageList);
+	};
+
+	/**
+	 * @param {string} sFolderHash
+	 * @param {number} iPage
+	 * @param {string} sSearch
+	 * @param {boolean=} bPreview = false
+	 */
+	MailBoxUserScreen.prototype.onRoute = function (sFolderHash, iPage, sSearch)
+	{
+		var
+			sFolderFullNameRaw = Cache.getFolderFullNameRaw(sFolderHash),
+			oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw)
+		;
+
+		if (oFolder)
+		{
+			FolderStore.currentFolder(oFolder);
+			MessageStore.messageListPage(iPage);
+			MessageStore.messageListSearch(sSearch);
+
+			require('App/User').reloadMessageList();
+		}
+	};
+
+	MailBoxUserScreen.prototype.onStart = function ()
+	{
+		FolderStore.folderList.subscribe(Utils.windowResizeCallback);
+
+		MessageStore.messageList.subscribe(Utils.windowResizeCallback);
+		MessageStore.message.subscribe(Utils.windowResizeCallback);
+
+		_.delay(function () {
+			SettingsStore.layout.valueHasMutated();
+		}, 50);
+
+		Events.sub('mailbox.inbox-unread-count', _.bind(function (iCount) {
+
+			FolderStore.foldersInboxUnreadCount(iCount);
+
+			var sEmail = AccountStore.email();
+
+			_.each(AccountStore.accounts(), function (oItem) {
+				if (oItem && sEmail === oItem.email)
+				{
+					oItem.count(iCount);
+				}
+			});
+
+			this.updateWindowTitle();
+
+		}, this));
+	};
+
+	MailBoxUserScreen.prototype.onBuild = function ()
+	{
+		if (!Globals.bMobileDevice)
+		{
+			_.defer(function () {
+				require('App/User').initHorizontalLayoutResizer(Enums.ClientSideKeyName.MessageListSize);
+			});
+		}
+	};
+
+	/**
+	 * @return {Array}
+	 */
+	MailBoxUserScreen.prototype.routes = function ()
+	{
+		var
+			sInboxFolderName = Cache.getFolderInboxName(),
+			fNormS = function (oRequest, oVals) {
+				oVals[0] = Utils.pString(oVals[0]);
+				oVals[1] = Utils.pInt(oVals[1]);
+				oVals[1] = 0 >= oVals[1] ? 1 : oVals[1];
+				oVals[2] = Utils.pString(oVals[2]);
+
+				if ('' === oRequest)
+				{
+					oVals[0] = sInboxFolderName;
+					oVals[1] = 1;
+				}
+
+				return [decodeURI(oVals[0]), oVals[1], decodeURI(oVals[2])];
+			},
+			fNormD = function (oRequest, oVals) {
+				oVals[0] = Utils.pString(oVals[0]);
+				oVals[1] = Utils.pString(oVals[1]);
+
+				if ('' === oRequest)
+				{
+					oVals[0] = sInboxFolderName;
+				}
+
+				return [decodeURI(oVals[0]), 1, decodeURI(oVals[1])];
+			}
+		;
+
+		return [
+			[/^([a-zA-Z0-9]+)\/p([1-9][0-9]*)\/(.+)\/?$/, {'normalize_': fNormS}],
+			[/^([a-zA-Z0-9]+)\/p([1-9][0-9]*)$/, {'normalize_': fNormS}],
+			[/^([a-zA-Z0-9]+)\/(.+)\/?$/, {'normalize_': fNormD}],
+			[/^([^\/]*)$/,  {'normalize_': fNormS}]
+		];
+	};
+
+	module.exports = MailBoxUserScreen;
+
+}());
\ No newline at end of file
diff --git a/dev/Screen/User/Settings.js b/dev/Screen/User/Settings.js
new file mode 100644
index 000000000..cb0ee5722
--- /dev/null
+++ b/dev/Screen/User/Settings.js
@@ -0,0 +1,132 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+
+		Enums = require('Common/Enums'),
+		Globals = require('Common/Globals'),
+		Translator = require('Common/Translator'),
+
+		Plugins = require('Common/Plugins'),
+
+		AppStore = require('Stores/User/App'),
+		AccountStore = require('Stores/User/Account'),
+		Settings = require('Storage/Settings'),
+
+		kn = require('Knoin/Knoin'),
+
+		AbstractSettingsScreen = require('Screen/AbstractSettings')
+	;
+
+	/**
+	 * @constructor
+	 * @extends AbstractSettingsScreen
+	 */
+	function SettingsUserScreen()
+	{
+		AbstractSettingsScreen.call(this, [
+			require('View/User/Settings/SystemDropDown'),
+			require('View/User/Settings/Menu'),
+			require('View/User/Settings/Pane')
+		]);
+
+		Translator.initOnStartOrLangChange(function () {
+			this.sSettingsTitle = Translator.i18n('TITLES/SETTINGS');
+		}, this, function () {
+			this.setSettingsTitle();
+		});
+	}
+
+	_.extend(SettingsUserScreen.prototype, AbstractSettingsScreen.prototype);
+
+	/**
+	 * @param {Function=} fCallback
+	 */
+	SettingsUserScreen.prototype.setupSettings = function (fCallback)
+	{
+		kn.addSettingsViewModel(require('Settings/User/General'),
+			'SettingsGeneral', 'SETTINGS_LABELS/LABEL_GENERAL_NAME', 'general', true);
+
+		if (AppStore.contactsIsAllowed())
+		{
+			kn.addSettingsViewModel(require('Settings/User/Contacts'),
+				'SettingsContacts', 'SETTINGS_LABELS/LABEL_CONTACTS_NAME', 'contacts');
+		}
+
+		kn.addSettingsViewModel(require('Settings/User/Accounts'), 'SettingsAccounts',
+			Settings.capa(Enums.Capa.AdditionalAccounts) ?
+				'SETTINGS_LABELS/LABEL_ACCOUNTS_NAME' : 'SETTINGS_LABELS/LABEL_IDENTITIES_NAME', 'accounts');
+
+		if (Settings.capa(Enums.Capa.Sieve))
+		{
+			kn.addSettingsViewModel(require('Settings/User/Filters'),
+				'SettingsFilters', 'SETTINGS_LABELS/LABEL_FILTERS_NAME', 'filters');
+		}
+
+		if (Settings.capa(Enums.Capa.AutoLogout) || Settings.capa(Enums.Capa.TwoFactor))
+		{
+			kn.addSettingsViewModel(require('Settings/User/Security'),
+				'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security');
+		}
+
+		if (AccountStore.isRootAccount() &&
+			(Settings.settingsGet('AllowGoogleSocial') && Settings.settingsGet('AllowGoogleSocialAuth')) ||
+			Settings.settingsGet('AllowFacebookSocial') ||
+			Settings.settingsGet('AllowTwitterSocial'))
+		{
+			kn.addSettingsViewModel(require('Settings/User/Social'),
+				'SettingsSocial', 'SETTINGS_LABELS/LABEL_SOCIAL_NAME', 'social');
+		}
+
+		if (Settings.settingsGet('ChangePasswordIsAllowed'))
+		{
+			kn.addSettingsViewModel(require('Settings/User/ChangePassword'),
+				'SettingsChangePassword', 'SETTINGS_LABELS/LABEL_CHANGE_PASSWORD_NAME', 'change-password');
+		}
+
+		if (Settings.capa(Enums.Capa.Templates))
+		{
+			kn.addSettingsViewModel(require('Settings/User/Templates'),
+				'SettingsTemplates', 'SETTINGS_LABELS/LABEL_TEMPLATES_NAME', 'templates');
+		}
+
+		kn.addSettingsViewModel(require('Settings/User/Folders'),
+			'SettingsFolders', 'SETTINGS_LABELS/LABEL_FOLDERS_NAME', 'folders');
+
+		if (Settings.capa(Enums.Capa.Themes))
+		{
+			kn.addSettingsViewModel(require('Settings/User/Themes'),
+				'SettingsThemes', 'SETTINGS_LABELS/LABEL_THEMES_NAME', 'themes');
+		}
+
+		if (Settings.capa(Enums.Capa.OpenPGP))
+		{
+			kn.addSettingsViewModel(require('Settings/User/OpenPgp'),
+				'SettingsOpenPGP', 'SETTINGS_LABELS/LABEL_OPEN_PGP_NAME', 'openpgp');
+		}
+
+		Plugins.runSettingsViewModelHooks(false);
+
+		if (fCallback)
+		{
+			fCallback();
+		}
+	};
+
+	SettingsUserScreen.prototype.onShow = function ()
+	{
+		this.setSettingsTitle();
+		Globals.keyScope(Enums.KeyState.Settings);
+	};
+
+	SettingsUserScreen.prototype.setSettingsTitle = function ()
+	{
+		require('App/User').setWindowTitle(this.sSettingsTitle);
+	};
+
+	module.exports = SettingsUserScreen;
+
+}());
\ No newline at end of file
diff --git a/dev/Settings/Admin/About.js b/dev/Settings/Admin/About.js
index 213aebb83..0c8fb7a3e 100644
--- a/dev/Settings/Admin/About.js
+++ b/dev/Settings/Admin/About.js
@@ -4,31 +4,39 @@
 	'use strict';
 
 	var
-		ko = require('ko')
+		ko = require('ko'),
+
+		Translator = require('Common/Translator'),
+
+		Settings = require('Storage/Settings'),
+		CoreStore = require('Stores/Admin/Core')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function AboutAdminSetting()
+	function AboutAdminSettings()
 	{
-		var
-			Settings = require('Storage/Settings'),
-			Data = require('Storage/Admin/Data')
-		;
-
 		this.version = ko.observable(Settings.settingsGet('Version'));
 		this.access = ko.observable(!!Settings.settingsGet('CoreAccess'));
 		this.errorDesc = ko.observable('');
 
-		this.coreReal = Data.coreReal;
-		this.coreUpdatable = Data.coreUpdatable;
-		this.coreAccess = Data.coreAccess;
-		this.coreChecking = Data.coreChecking;
-		this.coreUpdating = Data.coreUpdating;
-		this.coreRemoteVersion = Data.coreRemoteVersion;
-		this.coreRemoteRelease = Data.coreRemoteRelease;
-		this.coreVersionCompare = Data.coreVersionCompare;
+		this.coreReal = CoreStore.coreReal;
+		this.coreChannel = CoreStore.coreChannel;
+		this.coreType = CoreStore.coreType;
+		this.coreUpdatable = CoreStore.coreUpdatable;
+		this.coreAccess = CoreStore.coreAccess;
+		this.coreChecking = CoreStore.coreChecking;
+		this.coreUpdating = CoreStore.coreUpdating;
+		this.coreWarning = CoreStore.coreWarning;
+		this.coreVersion = CoreStore.coreVersion;
+		this.coreRemoteVersion = CoreStore.coreRemoteVersion;
+		this.coreRemoteRelease = CoreStore.coreRemoteRelease;
+		this.coreVersionCompare = CoreStore.coreVersionCompare;
+
+		this.coreRemoteVersionHtmlDesc = ko.computed(function () {
+			return Translator.i18n('TAB_ABOUT/HTML_NEW_VERSION', {'VERSION': this.coreRemoteVersion()});
+		}, this);
 
 		this.statusType = ko.computed(function () {
 
@@ -67,7 +75,7 @@
 		}, this);
 	}
 
-	AboutAdminSetting.prototype.onBuild = function ()
+	AboutAdminSettings.prototype.onBuild = function ()
 	{
 		if (this.access())
 		{
@@ -75,7 +83,7 @@
 		}
 	};
 
-	AboutAdminSetting.prototype.updateCoreData = function ()
+	AboutAdminSettings.prototype.updateCoreData = function ()
 	{
 		if (!this.coreUpdating())
 		{
@@ -83,6 +91,6 @@
 		}
 	};
 
-	module.exports = AboutAdminSetting;
+	module.exports = AboutAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/Admin/Branding.js b/dev/Settings/Admin/Branding.js
index 1956c0dce..748a68d79 100644
--- a/dev/Settings/Admin/Branding.js
+++ b/dev/Settings/Admin/Branding.js
@@ -7,85 +7,168 @@
 		_ = require('_'),
 		ko = require('ko'),
 
-		Utils = require('Common/Utils')
+		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function BrandingAdminSetting()
+	function BrandingAdminSettings()
 	{
 		var
 			Enums = require('Common/Enums'),
-			Settings = require('Storage/Settings')
+			Settings = require('Storage/Settings'),
+			AppStore = require('Stores/Admin/App')
 		;
 
+		this.capa = AppStore.prem;
+
 		this.title = ko.observable(Settings.settingsGet('Title'));
 		this.title.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
 
 		this.loadingDesc = ko.observable(Settings.settingsGet('LoadingDescription'));
 		this.loadingDesc.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
 
-		this.loginLogo = ko.observable(Settings.settingsGet('LoginLogo'));
+		this.loginLogo = ko.observable(Settings.settingsGet('LoginLogo') || '');
 		this.loginLogo.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
 
+		this.loginBackground = ko.observable(Settings.settingsGet('LoginBackground') || '');
+		this.loginBackground.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
+
+		this.userLogo = ko.observable(Settings.settingsGet('UserLogo') || '');
+		this.userLogo.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
+
 		this.loginDescription = ko.observable(Settings.settingsGet('LoginDescription'));
 		this.loginDescription.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
 
 		this.loginCss = ko.observable(Settings.settingsGet('LoginCss'));
 		this.loginCss.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
+
+		this.userCss = ko.observable(Settings.settingsGet('UserCss'));
+		this.userCss.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
+
+		this.welcomePageUrl = ko.observable(Settings.settingsGet('WelcomePageUrl'));
+		this.welcomePageUrl.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
+
+		this.welcomePageDisplay = ko.observable(Settings.settingsGet('WelcomePageDisplay'));
+		this.welcomePageDisplay.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
+
+		this.welcomePageDisplay.options = ko.computed(function () {
+			Translator.trigger();
+			return [
+				{'optValue': 'none', 'optText': Translator.i18n('TAB_BRANDING/OPTION_WELCOME_PAGE_DISPLAY_NONE')},
+				{'optValue': 'once', 'optText': Translator.i18n('TAB_BRANDING/OPTION_WELCOME_PAGE_DISPLAY_ONCE')},
+				{'optValue': 'always', 'optText': Translator.i18n('TAB_BRANDING/OPTION_WELCOME_PAGE_DISPLAY_ALWAYS')}
+			];
+		});
+
+		this.loginPowered = ko.observable(!!Settings.settingsGet('LoginPowered'));
 	}
 
-	BrandingAdminSetting.prototype.onBuild = function ()
+	BrandingAdminSettings.prototype.onBuild = function ()
 	{
 		var
 			self = this,
-			Remote = require('Storage/Admin/Remote')
+			Remote = require('Remote/Admin/Ajax')
 		;
 
-		_.delay(function () {
+		if (this.capa())
+		{
+			_.delay(function () {
 
-			var
-				f1 = Utils.settingsSaveHelperSimpleFunction(self.title.trigger, self),
-				f2 = Utils.settingsSaveHelperSimpleFunction(self.loadingDesc.trigger, self),
-				f3 = Utils.settingsSaveHelperSimpleFunction(self.loginLogo.trigger, self),
-				f4 = Utils.settingsSaveHelperSimpleFunction(self.loginDescription.trigger, self),
-				f5 = Utils.settingsSaveHelperSimpleFunction(self.loginCss.trigger, self)
-			;
+				var
+					f3 = Utils.settingsSaveHelperSimpleFunction(self.loginLogo.trigger, self),
+					f4 = Utils.settingsSaveHelperSimpleFunction(self.loginDescription.trigger, self),
+					f5 = Utils.settingsSaveHelperSimpleFunction(self.loginCss.trigger, self),
+					f6 = Utils.settingsSaveHelperSimpleFunction(self.userLogo.trigger, self),
+					f7 = Utils.settingsSaveHelperSimpleFunction(self.loginBackground.trigger, self),
+					f8 = Utils.settingsSaveHelperSimpleFunction(self.userCss.trigger, self),
+					f9 = Utils.settingsSaveHelperSimpleFunction(self.welcomePageUrl.trigger, self),
+					f10 = Utils.settingsSaveHelperSimpleFunction(self.welcomePageDisplay.trigger, self)
+				;
 
-			self.title.subscribe(function (sValue) {
-				Remote.saveAdminConfig(f1, {
-					'Title': Utils.trim(sValue)
+				self.loginLogo.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f3, {
+						'LoginLogo': Utils.trim(sValue)
+					});
 				});
-			});
 
-			self.loadingDesc.subscribe(function (sValue) {
-				Remote.saveAdminConfig(f2, {
-					'LoadingDescription': Utils.trim(sValue)
+				self.loginBackground.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f7, {
+						'LoginBackground': Utils.trim(sValue)
+					});
 				});
-			});
 
-			self.loginLogo.subscribe(function (sValue) {
-				Remote.saveAdminConfig(f3, {
-					'LoginLogo': Utils.trim(sValue)
+				self.userLogo.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f6, {
+						'UserLogo': Utils.trim(sValue)
+					});
 				});
-			});
 
-			self.loginDescription.subscribe(function (sValue) {
-				Remote.saveAdminConfig(f4, {
-					'LoginDescription': Utils.trim(sValue)
+				self.loginDescription.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f4, {
+						'LoginDescription': Utils.trim(sValue)
+					});
 				});
-			});
 
-			self.loginCss.subscribe(function (sValue) {
-				Remote.saveAdminConfig(f5, {
-					'LoginCss': Utils.trim(sValue)
+				self.loginCss.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f5, {
+						'LoginCss': Utils.trim(sValue)
+					});
 				});
-			});
 
-		}, 50);
+				self.userCss.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f8, {
+						'UserCss': Utils.trim(sValue)
+					});
+				});
+
+				self.welcomePageUrl.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f9, {
+						'WelcomePageUrl': Utils.trim(sValue)
+					});
+				});
+
+				self.welcomePageDisplay.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f10, {
+						'WelcomePageDisplay': Utils.trim(sValue)
+					});
+				});
+
+				self.loginPowered.subscribe(function (bValue) {
+					Remote.saveAdminConfig(null, {
+						'LoginPowered': bValue ? '1' : '0'
+					});
+				});
+
+			}, 50);
+		}
+		else
+		{
+			_.delay(function () {
+
+				var
+					f1 = Utils.settingsSaveHelperSimpleFunction(self.title.trigger, self),
+					f2 = Utils.settingsSaveHelperSimpleFunction(self.loadingDesc.trigger, self)
+				;
+
+				self.title.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f1, {
+						'Title': Utils.trim(sValue)
+					});
+				});
+
+				self.loadingDesc.subscribe(function (sValue) {
+					Remote.saveAdminConfig(f2, {
+						'LoadingDescription': Utils.trim(sValue)
+					});
+				});
+
+			}, 50);
+		}
 	};
 
-	module.exports = BrandingAdminSetting;
+	module.exports = BrandingAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/Admin/Contacts.js b/dev/Settings/Admin/Contacts.js
index 318f48ce9..2b748a5e4 100644
--- a/dev/Settings/Admin/Contacts.js
+++ b/dev/Settings/Admin/Contacts.js
@@ -10,16 +10,18 @@
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
 
+		Translator = require('Common/Translator'),
+
 		Settings = require('Storage/Settings')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function ContactsAdminSetting()
+	function ContactsAdminSettings()
 	{
 		var
-			Remote = require('Storage/Admin/Remote')
+			Remote = require('Remote/Admin/Ajax')
 		;
 
 		this.defautOptionsAfterRender = Utils.defautOptionsAfterRender;
@@ -68,7 +70,7 @@
 			var bDisabled = -1 === Utils.inArray(sValue, aSupportedTypes);
 			return {
 				'id': sValue,
-				'name': getTypeName(sValue) + (bDisabled ? ' (not supported)' : ''),
+				'name': getTypeName(sValue) + (bDisabled ? ' (' + Translator.i18n('HINTS/NOT_SUPPORTED') + ')' : ''),
 				'disabled': bDisabled
 			};
 		});
@@ -96,7 +98,7 @@
 					this.contactsType.valueHasMutated();
 				}
 			}
-		});
+		}).extend({'notify': 'always'});
 
 		this.contactsType.subscribe(function () {
 			this.testContactsSuccess(false);
@@ -141,7 +143,7 @@
 		this.onTestContactsResponse = _.bind(this.onTestContactsResponse, this);
 	}
 
-	ContactsAdminSetting.prototype.onTestContactsResponse = function (sResult, oData)
+	ContactsAdminSettings.prototype.onTestContactsResponse = function (sResult, oData)
 	{
 		this.testContactsSuccess(false);
 		this.testContactsError(false);
@@ -167,18 +169,18 @@
 		this.testing(false);
 	};
 
-	ContactsAdminSetting.prototype.onShow = function ()
+	ContactsAdminSettings.prototype.onShow = function ()
 	{
 		this.testContactsSuccess(false);
 		this.testContactsError(false);
 		this.testContactsErrorMessage('');
 	};
 
-	ContactsAdminSetting.prototype.onBuild = function ()
+	ContactsAdminSettings.prototype.onBuild = function ()
 	{
 		var
 			self = this,
-			Remote = require('Storage/Admin/Remote')
+			Remote = require('Remote/Admin/Ajax')
 		;
 
 		_.delay(function () {
@@ -237,6 +239,6 @@
 		}, 50);
 	};
 
-	module.exports = ContactsAdminSetting;
+	module.exports = ContactsAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/Admin/Domains.js b/dev/Settings/Admin/Domains.js
index 5bb60970e..6fe36c033 100644
--- a/dev/Settings/Admin/Domains.js
+++ b/dev/Settings/Admin/Domains.js
@@ -4,75 +4,47 @@
 	'use strict';
 
 	var
-		window = require('window'),
 		_ = require('_'),
 		ko = require('ko'),
 
 		Enums = require('Common/Enums'),
 
-		PopupsDomainViewModel = require('View/Popup/Domain'),
-
-		Data = require('Storage/Admin/Data'),
-		Remote = require('Storage/Admin/Remote')
+		DomainStore = require('Stores/Admin/Domain'),
+		Remote = require('Remote/Admin/Ajax')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function DomainsAdminSetting()
+	function DomainsAdminSettings()
 	{
-		this.domains = Data.domains;
-		this.domainsLoading = Data.domainsLoading;
-
-		this.iDomainForDeletionTimeout = 0;
+		this.domains = DomainStore.domains;
 
 		this.visibility = ko.computed(function () {
-			return Data.domainsLoading() ? 'visible' : 'hidden';
+			return this.domains.loading() ? 'visible' : 'hidden';
 		}, this);
 
-		this.domainForDeletion = ko.observable(null).extend({'toggleSubscribe': [this,
-			function (oPrev) {
-				if (oPrev)
-				{
-					oPrev.deleteAccess(false);
-				}
-			}, function (oNext) {
-				if (oNext)
-				{
-					oNext.deleteAccess(true);
-					this.startDomainForDeletionTimeout();
-				}
-			}
-		]});
+		this.domainForDeletion = ko.observable(null).deleteAccessHelper();
 	}
 
-	DomainsAdminSetting.prototype.startDomainForDeletionTimeout = function ()
+	DomainsAdminSettings.prototype.createDomain = function ()
 	{
-		var self = this;
-		window.clearInterval(this.iDomainForDeletionTimeout);
-		this.iDomainForDeletionTimeout = window.setTimeout(function () {
-			self.domainForDeletion(null);
-		}, 1000 * 3);
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Domain'));
 	};
 
-	DomainsAdminSetting.prototype.createDomain = function ()
-	{
-		require('Knoin/Knoin').showScreenPopup(PopupsDomainViewModel);
-	};
-
-	DomainsAdminSetting.prototype.deleteDomain = function (oDomain)
+	DomainsAdminSettings.prototype.deleteDomain = function (oDomain)
 	{
 		this.domains.remove(oDomain);
 		Remote.domainDelete(_.bind(this.onDomainListChangeRequest, this), oDomain.name);
 	};
 
-	DomainsAdminSetting.prototype.disableDomain = function (oDomain)
+	DomainsAdminSettings.prototype.disableDomain = function (oDomain)
 	{
 		oDomain.disabled(!oDomain.disabled());
 		Remote.domainDisable(_.bind(this.onDomainListChangeRequest, this), oDomain.name, oDomain.disabled());
 	};
 
-	DomainsAdminSetting.prototype.onBuild = function (oDom)
+	DomainsAdminSettings.prototype.onBuild = function (oDom)
 	{
 		var self = this;
 		oDom
@@ -88,19 +60,19 @@
 		require('App/Admin').reloadDomainList();
 	};
 
-	DomainsAdminSetting.prototype.onDomainLoadRequest = function (sResult, oData)
+	DomainsAdminSettings.prototype.onDomainLoadRequest = function (sResult, oData)
 	{
 		if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
 		{
-			require('Knoin/Knoin').showScreenPopup(PopupsDomainViewModel, [oData.Result]);
+			require('Knoin/Knoin').showScreenPopup(require('View/Popup/Domain'), [oData.Result]);
 		}
 	};
 
-	DomainsAdminSetting.prototype.onDomainListChangeRequest = function ()
+	DomainsAdminSettings.prototype.onDomainListChangeRequest = function ()
 	{
 		require('App/Admin').reloadDomainList();
 	};
 
-	module.exports = DomainsAdminSetting;
+	module.exports = DomainsAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/Admin/General.js b/dev/Settings/Admin/General.js
index c19651053..b56e2959c 100644
--- a/dev/Settings/Admin/General.js
+++ b/dev/Settings/Admin/General.js
@@ -9,63 +9,75 @@
 
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
-		LinkBuilder = require('Common/LinkBuilder'),
+		Links = require('Common/Links'),
+		Translator = require('Common/Translator'),
 
-		Settings = require('Storage/Settings'),
-		Data = require('Storage/Admin/Data')
+		ThemeStore = require('Stores/Theme'),
+		LanguageStore = require('Stores/Language'),
+		AppAdminStore = require('Stores/Admin/App'),
+		CapaAdminStore = require('Stores/Admin/Capa'),
+
+		Settings = require('Storage/Settings')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function GeneralAdminSetting()
+	function GeneralAdminSettings()
 	{
-		this.mainLanguage = Data.mainLanguage;
-		this.mainTheme = Data.mainTheme;
+		this.language = LanguageStore.language;
+		this.languages = LanguageStore.languages;
+		this.languageAdmin = LanguageStore.languageAdmin;
+		this.languagesAdmin = LanguageStore.languagesAdmin;
 
-		this.language = Data.language;
-		this.theme = Data.theme;
+		this.theme = ThemeStore.theme;
+		this.themes = ThemeStore.themes;
 
-		this.allowLanguagesOnSettings = Data.allowLanguagesOnSettings;
-		this.capaThemes = Data.capaThemes;
-		this.capaGravatar = Data.capaGravatar;
-		this.capaAdditionalAccounts = Data.capaAdditionalAccounts;
-		this.capaAdditionalIdentities = Data.capaAdditionalIdentities;
+		this.capaThemes = CapaAdminStore.themes;
+		this.capaUserBackground = CapaAdminStore.userBackground;
+		this.capaGravatar = CapaAdminStore.gravatar;
+		this.capaAdditionalAccounts = CapaAdminStore.additionalAccounts;
+		this.capaAttachmentThumbnails = CapaAdminStore.attachmentThumbnails;
+		this.capaTemplates = CapaAdminStore.templates;
+
+		this.allowLanguagesOnSettings = AppAdminStore.allowLanguagesOnSettings;
+		this.weakPassword = AppAdminStore.weakPassword;
 
 		this.mainAttachmentLimit = ko.observable(Utils.pInt(Settings.settingsGet('AttachmentLimit')) / (1024 * 1024)).extend({'posInterer': 25});
 		this.uploadData = Settings.settingsGet('PhpUploadSizes');
-		this.uploadDataDesc = this.uploadData && (this.uploadData['upload_max_filesize'] || this.uploadData['post_max_size']) ?
-			[
-				this.uploadData['upload_max_filesize'] ? 'upload_max_filesize = ' + this.uploadData['upload_max_filesize'] + '; ' : '',
-				this.uploadData['post_max_size'] ? 'post_max_size = ' + this.uploadData['post_max_size'] : ''
-			].join('')
-				: '';
+		this.uploadDataDesc = this.uploadData && (this.uploadData['upload_max_filesize'] || this.uploadData['post_max_size']) ? [
+			this.uploadData['upload_max_filesize'] ? 'upload_max_filesize = ' + this.uploadData['upload_max_filesize'] + '; ' : '',
+			this.uploadData['post_max_size'] ? 'post_max_size = ' + this.uploadData['post_max_size'] : ''
+		].join('') : '';
 
 		this.themesOptions = ko.computed(function () {
-			return _.map(Data.themes(), function (sTheme) {
+			return _.map(this.themes(), function (sTheme) {
 				return {
 					'optValue': sTheme,
 					'optText': Utils.convertThemeName(sTheme)
 				};
 			});
-		});
-
-		this.mainLanguageFullName = ko.computed(function () {
-			return Utils.convertLangName(this.mainLanguage());
 		}, this);
 
-		this.weakPassword = !!Settings.settingsGet('WeakPassword');
+		this.languageFullName = ko.computed(function () {
+			return Utils.convertLangName(this.language());
+		}, this);
+
+		this.languageAdminFullName = ko.computed(function () {
+			return Utils.convertLangName(this.languageAdmin());
+		}, this);
 
 		this.attachmentLimitTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
 		this.languageTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
+		this.languageAdminTrigger = ko.observable(Enums.SaveSettingsStep.Idle).extend({'throttle': 100});
 		this.themeTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
 	}
 
-	GeneralAdminSetting.prototype.onBuild = function ()
+	GeneralAdminSettings.prototype.onBuild = function ()
 	{
 		var
 			self = this,
-			Remote = require('Storage/Admin/Remote')
+			Remote = require('Remote/Admin/Ajax')
 		;
 
 		_.delay(function () {
@@ -73,7 +85,15 @@
 			var
 				f1 = Utils.settingsSaveHelperSimpleFunction(self.attachmentLimitTrigger, self),
 				f2 = Utils.settingsSaveHelperSimpleFunction(self.languageTrigger, self),
-				f3 = Utils.settingsSaveHelperSimpleFunction(self.themeTrigger, self)
+				f3 = Utils.settingsSaveHelperSimpleFunction(self.themeTrigger, self),
+				fReloadLanguageHelper = function (iSaveSettingsStep) {
+					return function() {
+						self.languageAdminTrigger(iSaveSettingsStep);
+						_.delay(function () {
+							self.languageAdminTrigger(Enums.SaveSettingsStep.Idle);
+						}, 1000);
+					};
+				}
 			;
 
 			self.mainAttachmentLimit.subscribe(function (sValue) {
@@ -88,7 +108,23 @@
 				});
 			});
 
+			self.languageAdmin.subscribe(function (sValue) {
+
+				self.languageAdminTrigger(Enums.SaveSettingsStep.Animate);
+
+				Translator.reload(true, sValue,
+					fReloadLanguageHelper(Enums.SaveSettingsStep.TrueResult),
+					fReloadLanguageHelper(Enums.SaveSettingsStep.FalseResult));
+
+				Remote.saveAdminConfig(null, {
+					'LanguageAdmin': Utils.trim(sValue)
+				});
+			});
+
 			self.theme.subscribe(function (sValue) {
+
+				Utils.changeTheme(sValue, self.themeTrigger);
+
 				Remote.saveAdminConfig(f3, {
 					'Theme': Utils.trim(sValue)
 				});
@@ -100,9 +136,9 @@
 				});
 			});
 
-			self.capaAdditionalIdentities.subscribe(function (bValue) {
+			self.capaTemplates.subscribe(function (bValue) {
 				Remote.saveAdminConfig(null, {
-					'CapaAdditionalIdentities': bValue ? '1' : '0'
+					'CapaTemplates': bValue ? '1' : '0'
 				});
 			});
 
@@ -112,12 +148,24 @@
 				});
 			});
 
+			self.capaAttachmentThumbnails.subscribe(function (bValue) {
+				Remote.saveAdminConfig(null, {
+					'CapaAttachmentThumbnails': bValue ? '1' : '0'
+				});
+			});
+
 			self.capaThemes.subscribe(function (bValue) {
 				Remote.saveAdminConfig(null, {
 					'CapaThemes': bValue ? '1' : '0'
 				});
 			});
 
+			self.capaUserBackground.subscribe(function (bValue) {
+				Remote.saveAdminConfig(null, {
+					'CapaUserBackground': bValue ? '1' : '0'
+				});
+			});
+
 			self.allowLanguagesOnSettings.subscribe(function (bValue) {
 				Remote.saveAdminConfig(null, {
 					'AllowLanguagesOnSettings': bValue ? '1' : '0'
@@ -127,19 +175,28 @@
 		}, 50);
 	};
 
-	GeneralAdminSetting.prototype.selectLanguage = function ()
+	GeneralAdminSettings.prototype.selectLanguage = function ()
 	{
-		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Languages'));
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Languages'), [
+			this.language, this.languages(), LanguageStore.userLanguage()
+		]);
+	};
+
+	GeneralAdminSettings.prototype.selectLanguageAdmin = function ()
+	{
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Languages'), [
+			this.languageAdmin, this.languagesAdmin(), LanguageStore.userLanguageAdmin()
+		]);
 	};
 
 	/**
 	 * @return {string}
 	 */
-	GeneralAdminSetting.prototype.phpInfoLink = function ()
+	GeneralAdminSettings.prototype.phpInfoLink = function ()
 	{
-		return LinkBuilder.phpInfo();
+		return Links.phpInfo();
 	};
 
-	module.exports = GeneralAdminSetting;
+	module.exports = GeneralAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/Admin/Licensing.js b/dev/Settings/Admin/Licensing.js
index 9132bfe8a..15bd709f5 100644
--- a/dev/Settings/Admin/Licensing.js
+++ b/dev/Settings/Admin/Licensing.js
@@ -5,23 +5,22 @@
 
 	var
 		ko = require('ko'),
-		moment = require('moment'),
 
 		Settings = require('Storage/Settings'),
-		Data = require('Storage/Admin/Data')
+		LicenseStore = require('Stores/Admin/License')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function LicensingAdminSetting()
+	function LicensingAdminSettings()
 	{
-		this.licensing = Data.licensing;
-		this.licensingProcess = Data.licensingProcess;
-		this.licenseValid = Data.licenseValid;
-		this.licenseExpired = Data.licenseExpired;
-		this.licenseError = Data.licenseError;
-		this.licenseTrigger = Data.licenseTrigger;
+		this.licensing = LicenseStore.licensing;
+		this.licensingProcess = LicenseStore.licensingProcess;
+		this.licenseValid = LicenseStore.licenseValid;
+		this.licenseExpired = LicenseStore.licenseExpired;
+		this.licenseError = LicenseStore.licenseError;
+		this.licenseTrigger = LicenseStore.licenseTrigger;
 
 		this.adminDomain = ko.observable('');
 		this.subscriptionEnabled = ko.observable(!!Settings.settingsGet('SubscriptionEnabled'));
@@ -34,7 +33,7 @@
 		}, this);
 	}
 
-	LicensingAdminSetting.prototype.onBuild = function ()
+	LicensingAdminSettings.prototype.onBuild = function ()
 	{
 		if (this.subscriptionEnabled())
 		{
@@ -42,29 +41,44 @@
 		}
 	};
 
-	LicensingAdminSetting.prototype.onShow = function ()
+	LicensingAdminSettings.prototype.onShow = function ()
 	{
 		this.adminDomain(Settings.settingsGet('AdminDomain'));
 	};
 
-	LicensingAdminSetting.prototype.showActivationForm = function ()
+	LicensingAdminSettings.prototype.showActivationForm = function ()
 	{
 		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Activate'));
 	};
 
-	/**
-	 * @returns {string}
-	 */
-	LicensingAdminSetting.prototype.licenseExpiredMomentValue = function ()
+	LicensingAdminSettings.prototype.showTrialForm = function ()
 	{
-		var
-			iTime = this.licenseExpired(),
-			oDate = moment.unix(iTime)
-		;
-
-		return iTime && 1898625600 === iTime ? 'Never' : (oDate.format('LL') + ' (' + oDate.from(moment()) + ')');
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Activate'), [true]);
 	};
 
-	module.exports = LicensingAdminSetting;
+	/**
+	 * @return {boolean}
+	 */
+	LicensingAdminSettings.prototype.licenseIsUnlim = function ()
+	{
+		return 1898625600 === this.licenseExpired() || 1898625700 === this.licenseExpired();
+	};
+
+	/**
+	 * @return {string}
+	 */
+	LicensingAdminSettings.prototype.licenseExpiredMomentValue = function ()
+	{
+		var
+			moment = require('moment'),
+			iTime = this.licenseExpired(),
+			oM = moment.unix(iTime)
+		;
+
+		return this.licenseIsUnlim() ? 'Never' :
+			(iTime && (oM.format('LL') + ' (' + oM.from(moment()) + ')'));
+	};
+
+	module.exports = LicensingAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/Admin/Login.js b/dev/Settings/Admin/Login.js
index f3e6a0503..c98fc3b1c 100644
--- a/dev/Settings/Admin/Login.js
+++ b/dev/Settings/Admin/Login.js
@@ -10,29 +10,32 @@
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
 
-		Settings = require('Storage/Settings'),
-		Data = require('Storage/Admin/Data')
+		AppAdminStore = require('Stores/Admin/App'),
+
+		Settings = require('Storage/Settings')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function LoginAdminSetting()
+	function LoginAdminSettings()
 	{
-		this.determineUserLanguage = Data.determineUserLanguage;
-		this.determineUserDomain = Data.determineUserDomain;
+		this.determineUserLanguage = AppAdminStore.determineUserLanguage;
+		this.determineUserDomain = AppAdminStore.determineUserDomain;
 
 		this.defaultDomain = ko.observable(Settings.settingsGet('LoginDefaultDomain'));
 
-		this.allowLanguagesOnLogin = Data.allowLanguagesOnLogin;
+		this.allowLanguagesOnLogin = AppAdminStore.allowLanguagesOnLogin;
 		this.defaultDomainTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
+
+		this.dummy = ko.observable(false);
 	}
 
-	LoginAdminSetting.prototype.onBuild = function ()
+	LoginAdminSettings.prototype.onBuild = function ()
 	{
 		var
 			self = this,
-			Remote = require('Storage/Admin/Remote')
+			Remote = require('Remote/Admin/Ajax')
 		;
 
 		_.delay(function () {
@@ -66,6 +69,6 @@
 		}, 50);
 	};
 
-	module.exports = LoginAdminSetting;
+	module.exports = LoginAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/Admin/Packages.js b/dev/Settings/Admin/Packages.js
index 0395083ce..f901f3b01 100644
--- a/dev/Settings/Admin/Packages.js
+++ b/dev/Settings/Admin/Packages.js
@@ -5,26 +5,26 @@
 
 	var
 		window = require('window'),
+		_ = require('_'),
 		ko = require('ko'),
 
 		Enums = require('Common/Enums'),
-		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
 
-		Data = require('Storage/Admin/Data'),
-		Remote = require('Storage/Admin/Remote')
+		PackageStore = require('Stores/Admin/Package'),
+		Remote = require('Remote/Admin/Ajax')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function PackagesAdminSetting()
+	function PackagesAdminSettings()
 	{
 		this.packagesError = ko.observable('');
 
-		this.packages = Data.packages;
-		this.packagesLoading = Data.packagesLoading;
-		this.packagesReal = Data.packagesReal;
-		this.packagesMainUpdatable = Data.packagesMainUpdatable;
+		this.packages = PackageStore.packages;
+		this.packagesReal = PackageStore.packagesReal;
+		this.packagesMainUpdatable = PackageStore.packagesMainUpdatable;
 
 		this.packagesCurrent = this.packages.filter(function (oItem) {
 			return oItem && '' !== oItem['installed'] && !oItem['compare'];
@@ -39,21 +39,21 @@
 		});
 
 		this.visibility = ko.computed(function () {
-			return Data.packagesLoading() ? 'visible' : 'hidden';
+			return PackageStore.packages.loading() ? 'visible' : 'hidden';
 		}, this);
 	}
 
-	PackagesAdminSetting.prototype.onShow = function ()
+	PackagesAdminSettings.prototype.onShow = function ()
 	{
 		this.packagesError('');
 	};
 
-	PackagesAdminSetting.prototype.onBuild = function ()
+	PackagesAdminSettings.prototype.onBuild = function ()
 	{
 		require('App/Admin').reloadPackagesList();
 	};
 
-	PackagesAdminSetting.prototype.requestHelper = function (oPackage, bInstall)
+	PackagesAdminSettings.prototype.requestHelper = function (oPackage, bInstall)
 	{
 		var self = this;
 		return function (sResult, oData) {
@@ -62,16 +62,16 @@
 			{
 				if (oData && oData.ErrorCode)
 				{
-					self.packagesError(Utils.getNotification(oData.ErrorCode));
+					self.packagesError(Translator.getNotification(oData.ErrorCode));
 				}
 				else
 				{
-					self.packagesError(Utils.getNotification(
+					self.packagesError(Translator.getNotification(
 						bInstall ? Enums.Notification.CantInstallPackage : Enums.Notification.CantDeletePackage));
 				}
 			}
 
-			_.each(Data.packages(), function (oItem) {
+			_.each(self.packages(), function (oItem) {
 				if (oItem && oPackage && oItem['loading']() && oPackage['file'] === oItem['file'])
 				{
 					oPackage['loading'](false);
@@ -90,7 +90,7 @@
 		};
 	};
 
-	PackagesAdminSetting.prototype.deletePackage = function (oPackage)
+	PackagesAdminSettings.prototype.deletePackage = function (oPackage)
 	{
 		if (oPackage)
 		{
@@ -99,7 +99,7 @@
 		}
 	};
 
-	PackagesAdminSetting.prototype.installPackage = function (oPackage)
+	PackagesAdminSettings.prototype.installPackage = function (oPackage)
 	{
 		if (oPackage)
 		{
@@ -108,6 +108,6 @@
 		}
 	};
 
-	module.exports = PackagesAdminSetting;
+	module.exports = PackagesAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/Admin/Plugins.js b/dev/Settings/Admin/Plugins.js
index 6841ee7d2..f15c7637c 100644
--- a/dev/Settings/Admin/Plugins.js
+++ b/dev/Settings/Admin/Plugins.js
@@ -9,44 +9,43 @@
 
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
 
 		Settings = require('Storage/Settings'),
-		Data = require('Storage/Admin/Data'),
-		Remote = require('Storage/Admin/Remote')
+		PluginStore = require('Stores/Admin/Plugin'),
+		Remote = require('Remote/Admin/Ajax')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function PluginsAdminSetting()
+	function PluginsAdminSettings()
 	{
 		this.enabledPlugins = ko.observable(!!Settings.settingsGet('EnabledPlugins'));
 
-		this.pluginsError = ko.observable('');
-
-		this.plugins = Data.plugins;
-		this.pluginsLoading = Data.pluginsLoading;
+		this.plugins = PluginStore.plugins;
+		this.pluginsError = PluginStore.plugins.error;
 
 		this.visibility = ko.computed(function () {
-			return Data.pluginsLoading() ? 'visible' : 'hidden';
+			return PluginStore.plugins.loading() ? 'visible' : 'hidden';
 		}, this);
 
 		this.onPluginLoadRequest = _.bind(this.onPluginLoadRequest, this);
 		this.onPluginDisableRequest = _.bind(this.onPluginDisableRequest, this);
 	}
 
-	PluginsAdminSetting.prototype.disablePlugin = function (oPlugin)
+	PluginsAdminSettings.prototype.disablePlugin = function (oPlugin)
 	{
 		oPlugin.disabled(!oPlugin.disabled());
 		Remote.pluginDisable(this.onPluginDisableRequest, oPlugin.name, oPlugin.disabled());
 	};
 
-	PluginsAdminSetting.prototype.configurePlugin = function (oPlugin)
+	PluginsAdminSettings.prototype.configurePlugin = function (oPlugin)
 	{
 		Remote.plugin(this.onPluginLoadRequest, oPlugin.name);
 	};
 
-	PluginsAdminSetting.prototype.onBuild = function (oDom)
+	PluginsAdminSettings.prototype.onBuild = function (oDom)
 	{
 		var self = this;
 
@@ -74,13 +73,13 @@
 		});
 	};
 
-	PluginsAdminSetting.prototype.onShow = function ()
+	PluginsAdminSettings.prototype.onShow = function ()
 	{
-		this.pluginsError('');
+		PluginStore.plugins.error('');
 		require('App/Admin').reloadPluginList();
 	};
 
-	PluginsAdminSetting.prototype.onPluginLoadRequest = function (sResult, oData)
+	PluginsAdminSettings.prototype.onPluginLoadRequest = function (sResult, oData)
 	{
 		if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
 		{
@@ -88,7 +87,7 @@
 		}
 	};
 
-	PluginsAdminSetting.prototype.onPluginDisableRequest = function (sResult, oData)
+	PluginsAdminSettings.prototype.onPluginDisableRequest = function (sResult, oData)
 	{
 		if (Enums.StorageResultType.Success === sResult && oData)
 		{
@@ -96,11 +95,11 @@
 			{
 				if (Enums.Notification.UnsupportedPluginPackage === oData.ErrorCode && oData.ErrorMessage && '' !== oData.ErrorMessage)
 				{
-					this.pluginsError(oData.ErrorMessage);
+					PluginStore.plugins.error(oData.ErrorMessage);
 				}
 				else
 				{
-					this.pluginsError(Utils.getNotification(oData.ErrorCode));
+					PluginStore.plugins.error(Translator.getNotification(oData.ErrorCode));
 				}
 			}
 		}
@@ -108,6 +107,6 @@
 		require('App/Admin').reloadPluginList();
 	};
 
-	module.exports = PluginsAdminSetting;
+	module.exports = PluginsAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/Admin/Security.js b/dev/Settings/Admin/Security.js
index 951ed8fe3..f8bdaa220 100644
--- a/dev/Settings/Admin/Security.js
+++ b/dev/Settings/Admin/Security.js
@@ -9,24 +9,48 @@
 
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
-		LinkBuilder = require('Common/LinkBuilder'),
+		Links = require('Common/Links'),
+
+		AppAdminStore = require('Stores/Admin/App'),
+		CapaAdminStore = require('Stores/Admin/Capa'),
 
 		Settings = require('Storage/Settings'),
-		Data = require('Storage/Admin/Data'),
-		Remote = require('Storage/Admin/Remote')
+		Remote = require('Remote/Admin/Ajax')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function SecurityAdminSetting()
+	function SecurityAdminSettings()
 	{
-		this.useLocalProxyForExternalImages = Data.useLocalProxyForExternalImages;
+		this.useLocalProxyForExternalImages = AppAdminStore.useLocalProxyForExternalImages;
 
-		this.capaOpenPGP = ko.observable(Settings.capa(Enums.Capa.OpenPGP));
-		this.capaTwoFactorAuth = ko.observable(Settings.capa(Enums.Capa.TwoFactor));
+		this.weakPassword = AppAdminStore.weakPassword;
+
+		this.capaOpenPGP = CapaAdminStore.openPGP;
+
+		this.capaTwoFactorAuth = CapaAdminStore.twoFactorAuth;
+		this.capaTwoFactorAuthForce = CapaAdminStore.twoFactorAuthForce;
+
+		this.capaTwoFactorAuth.subscribe(function (bValue) {
+			if (!bValue)
+			{
+				this.capaTwoFactorAuthForce(false);
+			}
+		}, this);
+
+		this.verifySslCertificate = ko.observable(!!Settings.settingsGet('VerifySslCertificate'));
+		this.allowSelfSigned = ko.observable(!!Settings.settingsGet('AllowSelfSigned'));
+
+		this.verifySslCertificate.subscribe(function (bValue) {
+			if (!bValue)
+			{
+				this.allowSelfSigned(true);
+			}
+		}, this);
 
 		this.adminLogin = ko.observable(Settings.settingsGet('AdminLogin'));
+		this.adminLoginError = ko.observable(false);
 		this.adminPassword = ko.observable('');
 		this.adminPasswordNew = ko.observable('');
 		this.adminPasswordNew2 = ko.observable('');
@@ -40,6 +64,10 @@
 			this.adminPasswordUpdateSuccess(false);
 		}, this);
 
+		this.adminLogin.subscribe(function () {
+			this.adminLoginError(false);
+		}, this);
+
 		this.adminPasswordNew.subscribe(function () {
 			this.adminPasswordUpdateError(false);
 			this.adminPasswordUpdateSuccess(false);
@@ -54,6 +82,12 @@
 
 		this.saveNewAdminPasswordCommand = Utils.createCommand(this, function () {
 
+			if ('' === Utils.trim(this.adminLogin()))
+			{
+				this.adminLoginError(true);
+				return false;
+			}
+
 			if (this.adminPasswordNew() !== this.adminPasswordNew2())
 			{
 				this.adminPasswordNewError(true);
@@ -64,18 +98,19 @@
 			this.adminPasswordUpdateSuccess(false);
 
 			Remote.saveNewAdminPassword(this.onNewAdminPasswordResponse, {
+				'Login': this.adminLogin(),
 				'Password': this.adminPassword(),
 				'NewPassword': this.adminPasswordNew()
 			});
 
 		}, function () {
-			return '' !== this.adminPassword() && '' !== this.adminPasswordNew() && '' !== this.adminPasswordNew2();
+			return '' !== Utils.trim(this.adminLogin()) && '' !== this.adminPassword();
 		});
 
 		this.onNewAdminPasswordResponse = _.bind(this.onNewAdminPasswordResponse, this);
 	}
 
-	SecurityAdminSetting.prototype.onNewAdminPasswordResponse = function (sResult, oData)
+	SecurityAdminSettings.prototype.onNewAdminPasswordResponse = function (sResult, oData)
 	{
 		if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
 		{
@@ -84,6 +119,8 @@
 			this.adminPasswordNew2('');
 
 			this.adminPasswordUpdateSuccess(true);
+
+			this.weakPassword(!!oData.Result.Weak);
 		}
 		else
 		{
@@ -91,12 +128,8 @@
 		}
 	};
 
-	SecurityAdminSetting.prototype.onBuild = function ()
+	SecurityAdminSettings.prototype.onBuild = function ()
 	{
-		var
-			Remote = require('Storage/Admin/Remote')
-		;
-
 		this.capaOpenPGP.subscribe(function (bValue) {
 			Remote.saveAdminConfig(Utils.emptyFunction, {
 				'CapaOpenPGP': bValue ? '1' : '0'
@@ -109,14 +142,32 @@
 			});
 		});
 
+		this.capaTwoFactorAuthForce.subscribe(function (bValue) {
+			Remote.saveAdminConfig(Utils.emptyFunction, {
+				'CapaTwoFactorAuthForce': bValue ? '1' : '0'
+			});
+		});
+
 		this.useLocalProxyForExternalImages.subscribe(function (bValue) {
 			Remote.saveAdminConfig(null, {
 				'UseLocalProxyForExternalImages': bValue ? '1' : '0'
 			});
 		});
+
+		this.verifySslCertificate.subscribe(function (bValue) {
+			Remote.saveAdminConfig(null, {
+				'VerifySslCertificate': bValue ? '1' : '0'
+			});
+		});
+
+		this.allowSelfSigned.subscribe(function (bValue) {
+			Remote.saveAdminConfig(null, {
+				'AllowSelfSigned': bValue ? '1' : '0'
+			});
+		});
 	};
 
-	SecurityAdminSetting.prototype.onHide = function ()
+	SecurityAdminSettings.prototype.onHide = function ()
 	{
 		this.adminPassword('');
 		this.adminPasswordNew('');
@@ -126,11 +177,11 @@
 	/**
 	 * @return {string}
 	 */
-	SecurityAdminSetting.prototype.phpInfoLink = function ()
+	SecurityAdminSettings.prototype.phpInfoLink = function ()
 	{
-		return LinkBuilder.phpInfo();
+		return Links.phpInfo();
 	};
 
-	module.exports = SecurityAdminSetting;
+	module.exports = SecurityAdminSettings;
 
 }());
diff --git a/dev/Settings/Admin/Social.js b/dev/Settings/Admin/Social.js
index 8336a1547..ca2d2ad84 100644
--- a/dev/Settings/Admin/Social.js
+++ b/dev/Settings/Admin/Social.js
@@ -14,42 +14,52 @@
 	/**
 	 * @constructor
 	 */
-	function SocialAdminSetting()
+	function SocialAdminSettings()
 	{
-		var Data = require('Storage/Admin/Data');
+		var SocialStore = require('Stores/Social');
+
+		this.googleEnable = SocialStore.google.enabled;
+		this.googleEnableAuth = SocialStore.google.capa.auth;
+		this.googleEnableDrive = SocialStore.google.capa.drive;
+		this.googleEnablePreview = SocialStore.google.capa.preview;
+
+		this.googleEnableRequireClientSettings = SocialStore.google.require.clientSettings;
+		this.googleEnableRequireApiKey = SocialStore.google.require.apiKeySettings;
+
+		this.googleClientID = SocialStore.google.clientID;
+		this.googleClientSecret = SocialStore.google.clientSecret;
+		this.googleApiKey = SocialStore.google.apiKey;
 
-		this.googleEnable = Data.googleEnable;
-		this.googleClientID = Data.googleClientID;
-		this.googleApiKey = Data.googleApiKey;
-		this.googleClientSecret = Data.googleClientSecret;
-		
 		this.googleTrigger1 = ko.observable(Enums.SaveSettingsStep.Idle);
 		this.googleTrigger2 = ko.observable(Enums.SaveSettingsStep.Idle);
 		this.googleTrigger3 = ko.observable(Enums.SaveSettingsStep.Idle);
 
-		this.facebookSupported = Data.facebookSupported;
-		this.facebookEnable = Data.facebookEnable;
-		this.facebookAppID = Data.facebookAppID;
-		this.facebookAppSecret = Data.facebookAppSecret;
+		this.facebookSupported = SocialStore.facebook.supported;
+		this.facebookEnable = SocialStore.facebook.enabled;
+		this.facebookAppID = SocialStore.facebook.appID;
+		this.facebookAppSecret = SocialStore.facebook.appSecret;
+
 		this.facebookTrigger1 = ko.observable(Enums.SaveSettingsStep.Idle);
 		this.facebookTrigger2 = ko.observable(Enums.SaveSettingsStep.Idle);
 
-		this.twitterEnable = Data.twitterEnable;
-		this.twitterConsumerKey = Data.twitterConsumerKey;
-		this.twitterConsumerSecret = Data.twitterConsumerSecret;
+		this.twitterEnable = SocialStore.twitter.enabled;
+		this.twitterConsumerKey = SocialStore.twitter.consumerKey;
+		this.twitterConsumerSecret = SocialStore.twitter.consumerSecret;
+
 		this.twitterTrigger1 = ko.observable(Enums.SaveSettingsStep.Idle);
 		this.twitterTrigger2 = ko.observable(Enums.SaveSettingsStep.Idle);
 
-		this.dropboxEnable = Data.dropboxEnable;
-		this.dropboxApiKey = Data.dropboxApiKey;
+		this.dropboxEnable = SocialStore.dropbox.enabled;
+		this.dropboxApiKey = SocialStore.dropbox.apiKey;
+
 		this.dropboxTrigger1 = ko.observable(Enums.SaveSettingsStep.Idle);
 	}
 
-	SocialAdminSetting.prototype.onBuild = function ()
+	SocialAdminSettings.prototype.onBuild = function ()
 	{
 		var
 			self = this,
-			Remote = require('Storage/Admin/Remote')
+			Remote = require('Remote/Admin/Ajax')
 		;
 
 		_.delay(function () {
@@ -116,6 +126,24 @@
 				});
 			});
 
+			self.googleEnableAuth.subscribe(function (bValue) {
+				Remote.saveAdminConfig(Utils.emptyFunction, {
+					'GoogleEnableAuth': bValue ? '1' : '0'
+				});
+			});
+
+			self.googleEnableDrive.subscribe(function (bValue) {
+				Remote.saveAdminConfig(Utils.emptyFunction, {
+					'GoogleEnableDrive': bValue ? '1' : '0'
+				});
+			});
+
+			self.googleEnablePreview.subscribe(function (bValue) {
+				Remote.saveAdminConfig(Utils.emptyFunction, {
+					'GoogleEnablePreview': bValue ? '1' : '0'
+				});
+			});
+
 			self.googleClientID.subscribe(function (sValue) {
 				Remote.saveAdminConfig(f5, {
 					'GoogleClientID': Utils.trim(sValue)
@@ -149,6 +177,6 @@
 		}, 50);
 	};
 
-	module.exports = SocialAdminSetting;
+	module.exports = SocialAdminSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/App/Accounts.js b/dev/Settings/App/Accounts.js
deleted file mode 100644
index 9f1228cb5..000000000
--- a/dev/Settings/App/Accounts.js
+++ /dev/null
@@ -1,99 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		window = require('window'),
-		_ = require('_'),
-		ko = require('ko'),
-
-		Enums = require('Common/Enums'),
-		Utils = require('Common/Utils'),
-		LinkBuilder = require('Common/LinkBuilder'),
-
-		Data = require('Storage/App/Data'),
-		Remote = require('Storage/App/Remote')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function AccountsAppSetting()
-	{
-		this.accounts = Data.accounts;
-
-		this.processText = ko.computed(function () {
-			return Data.accountsLoading() ? Utils.i18n('SETTINGS_ACCOUNTS/LOADING_PROCESS') : '';
-		}, this);
-
-		this.visibility = ko.computed(function () {
-			return '' === this.processText() ? 'hidden' : 'visible';
-		}, this);
-
-		this.accountForDeletion = ko.observable(null).extend({'falseTimeout': 3000}).extend({'toggleSubscribe': [this,
-			function (oPrev) {
-				if (oPrev)
-				{
-					oPrev.deleteAccess(false);
-				}
-			}, function (oNext) {
-				if (oNext)
-				{
-					oNext.deleteAccess(true);
-				}
-			}
-		]});
-	}
-
-	AccountsAppSetting.prototype.addNewAccount = function ()
-	{
-		require('Knoin/Knoin').showScreenPopup(require('View/Popup/AddAccount'));
-	};
-
-	/**
-	 * @param {AccountModel} oAccountToRemove
-	 */
-	AccountsAppSetting.prototype.deleteAccount = function (oAccountToRemove)
-	{
-		if (oAccountToRemove && oAccountToRemove.deleteAccess())
-		{
-			this.accountForDeletion(null);
-
-			var
-				kn = require('Knoin/Knoin'),
-				fRemoveAccount = function (oAccount) {
-					return oAccountToRemove === oAccount;
-				}
-			;
-
-			if (oAccountToRemove)
-			{
-				this.accounts.remove(fRemoveAccount);
-
-				Remote.accountDelete(function (sResult, oData) {
-
-					if (Enums.StorageResultType.Success === sResult && oData &&
-						oData.Result && oData.Reload)
-					{
-						kn.routeOff();
-						kn.setHash(LinkBuilder.root(), true);
-						kn.routeOff();
-
-						_.defer(function () {
-							window.location.reload();
-						});
-					}
-					else
-					{
-						require('App/App').accountsAndIdentities();
-					}
-
-				}, oAccountToRemove.email);
-			}
-		}
-	};
-
-	module.exports = AccountsAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/App/Filters.js b/dev/Settings/App/Filters.js
deleted file mode 100644
index dde98a067..000000000
--- a/dev/Settings/App/Filters.js
+++ /dev/null
@@ -1,42 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		ko = require('ko'),
-
-		Utils = require('Common/Utils')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function FiltersAppSetting()
-	{
-		this.filters = ko.observableArray([]);
-		this.filters.loading = ko.observable(false);
-
-		this.filters.subscribe(function () {
-			Utils.windowResize();
-		});
-	}
-
-	FiltersAppSetting.prototype.deleteFilter = function (oFilter)
-	{
-		this.filters.remove(oFilter);
-	};
-
-	FiltersAppSetting.prototype.addFilter = function ()
-	{
-		var
-			FilterModel = require('Model/Filter')
-		;
-
-		require('Knoin/Knoin').showScreenPopup(
-			require('View/Popup/Filter'), [new FilterModel()]);
-	};
-
-	module.exports = FiltersAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/App/Folders.js b/dev/Settings/App/Folders.js
deleted file mode 100644
index a3a4262c0..000000000
--- a/dev/Settings/App/Folders.js
+++ /dev/null
@@ -1,214 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		ko = require('ko'),
-
-		Enums = require('Common/Enums'),
-		Utils = require('Common/Utils'),
-
-		Settings = require('Storage/Settings'),
-		Data = require('Storage/App/Data'),
-		Cache = require('Storage/App/Cache'),
-		Remote = require('Storage/App/Remote'),
-		Local = require('Storage/Local')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function FoldersAppSetting()
-	{
-		this.foldersListError = Data.foldersListError;
-		this.folderList = Data.folderList;
-
-		this.processText = ko.computed(function () {
-
-			var
-				bLoading = Data.foldersLoading(),
-				bCreating = Data.foldersCreating(),
-				bDeleting = Data.foldersDeleting(),
-				bRenaming = Data.foldersRenaming()
-			;
-
-			if (bCreating)
-			{
-				return Utils.i18n('SETTINGS_FOLDERS/CREATING_PROCESS');
-			}
-			else if (bDeleting)
-			{
-				return Utils.i18n('SETTINGS_FOLDERS/DELETING_PROCESS');
-			}
-			else if (bRenaming)
-			{
-				return Utils.i18n('SETTINGS_FOLDERS/RENAMING_PROCESS');
-			}
-			else if (bLoading)
-			{
-				return Utils.i18n('SETTINGS_FOLDERS/LOADING_PROCESS');
-			}
-
-			return '';
-
-		}, this);
-
-		this.visibility = ko.computed(function () {
-			return '' === this.processText() ? 'hidden' : 'visible';
-		}, this);
-
-		this.folderForDeletion = ko.observable(null).extend({'falseTimeout': 3000}).extend({'toggleSubscribe': [this,
-			function (oPrev) {
-				if (oPrev)
-				{
-					oPrev.deleteAccess(false);
-				}
-			}, function (oNext) {
-				if (oNext)
-				{
-					oNext.deleteAccess(true);
-				}
-			}
-		]});
-
-		this.folderForEdit = ko.observable(null).extend({'toggleSubscribe': [this,
-			function (oPrev) {
-				if (oPrev)
-				{
-					oPrev.edited(false);
-				}
-			}, function (oNext) {
-				if (oNext && oNext.canBeEdited())
-				{
-					oNext.edited(true);
-				}
-			}
-		]});
-
-		this.useImapSubscribe = !!Settings.settingsGet('UseImapSubscribe');
-	}
-
-	FoldersAppSetting.prototype.folderEditOnEnter = function (oFolder)
-	{
-		var
-			sEditName = oFolder ? Utils.trim(oFolder.nameForEdit()) : ''
-		;
-
-		if ('' !== sEditName && oFolder.name() !== sEditName)
-		{
-			Local.set(Enums.ClientSideKeyName.FoldersLashHash, '');
-
-			Data.foldersRenaming(true);
-			Remote.folderRename(function (sResult, oData) {
-
-				Data.foldersRenaming(false);
-				if (Enums.StorageResultType.Success !== sResult || !oData || !oData.Result)
-				{
-					Data.foldersListError(
-						oData && oData.ErrorCode ? Utils.getNotification(oData.ErrorCode) : Utils.i18n('NOTIFICATIONS/CANT_RENAME_FOLDER'));
-				}
-
-				require('App/App').folders();
-
-			}, oFolder.fullNameRaw, sEditName);
-
-			Cache.removeFolderFromCacheList(oFolder.fullNameRaw);
-
-			oFolder.name(sEditName);
-		}
-
-		oFolder.edited(false);
-	};
-
-	FoldersAppSetting.prototype.folderEditOnEsc = function (oFolder)
-	{
-		if (oFolder)
-		{
-			oFolder.edited(false);
-		}
-	};
-
-	FoldersAppSetting.prototype.onShow = function ()
-	{
-		Data.foldersListError('');
-	};
-
-	FoldersAppSetting.prototype.createFolder = function ()
-	{
-		require('Knoin/Knoin').showScreenPopup(require('View/Popup/FolderCreate'));
-	};
-
-	FoldersAppSetting.prototype.systemFolder = function ()
-	{
-		require('Knoin/Knoin').showScreenPopup(require('View/Popup/FolderSystem'));
-	};
-
-	FoldersAppSetting.prototype.deleteFolder = function (oFolderToRemove)
-	{
-		if (oFolderToRemove && oFolderToRemove.canBeDeleted() && oFolderToRemove.deleteAccess() &&
-			0 === oFolderToRemove.privateMessageCountAll())
-		{
-			this.folderForDeletion(null);
-
-			var
-				fRemoveFolder = function (oFolder) {
-
-					if (oFolderToRemove === oFolder)
-					{
-						return true;
-					}
-
-					oFolder.subFolders.remove(fRemoveFolder);
-					return false;
-				}
-			;
-
-			if (oFolderToRemove)
-			{
-				Local.set(Enums.ClientSideKeyName.FoldersLashHash, '');
-
-				Data.folderList.remove(fRemoveFolder);
-
-				Data.foldersDeleting(true);
-				Remote.folderDelete(function (sResult, oData) {
-
-					Data.foldersDeleting(false);
-					if (Enums.StorageResultType.Success !== sResult || !oData || !oData.Result)
-					{
-						Data.foldersListError(
-							oData && oData.ErrorCode ? Utils.getNotification(oData.ErrorCode) : Utils.i18n('NOTIFICATIONS/CANT_DELETE_FOLDER'));
-					}
-
-					require('App/App').folders();
-
-				}, oFolderToRemove.fullNameRaw);
-
-				Cache.removeFolderFromCacheList(oFolderToRemove.fullNameRaw);
-			}
-		}
-		else if (0 < oFolderToRemove.privateMessageCountAll())
-		{
-			Data.foldersListError(Utils.getNotification(Enums.Notification.CantDeleteNonEmptyFolder));
-		}
-	};
-
-	FoldersAppSetting.prototype.subscribeFolder = function (oFolder)
-	{
-		Local.set(Enums.ClientSideKeyName.FoldersLashHash, '');
-		Remote.folderSetSubscribe(Utils.emptyFunction, oFolder.fullNameRaw, true);
-
-		oFolder.subScribed(true);
-	};
-
-	FoldersAppSetting.prototype.unSubscribeFolder = function (oFolder)
-	{
-		Local.set(Enums.ClientSideKeyName.FoldersLashHash, '');
-		Remote.folderSetSubscribe(Utils.emptyFunction, oFolder.fullNameRaw, false);
-
-		oFolder.subScribed(false);
-	};
-
-	module.exports = FoldersAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/App/General.js b/dev/Settings/App/General.js
deleted file mode 100644
index 00db21280..000000000
--- a/dev/Settings/App/General.js
+++ /dev/null
@@ -1,173 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-		ko = require('ko'),
-
-		Enums = require('Common/Enums'),
-		Consts = require('Common/Consts'),
-		Globals = require('Common/Globals'),
-		Utils = require('Common/Utils'),
-
-		Data = require('Storage/App/Data'),
-		Remote = require('Storage/App/Remote')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function GeneralAppSetting()
-	{
-		this.mainLanguage = Data.mainLanguage;
-		this.mainMessagesPerPage = Data.mainMessagesPerPage;
-		this.mainMessagesPerPageArray = Consts.Defaults.MessagesPerPageArray;
-		this.editorDefaultType = Data.editorDefaultType;
-		this.showImages = Data.showImages;
-		this.interfaceAnimation = Data.interfaceAnimation;
-		this.useDesktopNotifications = Data.useDesktopNotifications;
-		this.threading = Data.threading;
-		this.useThreads = Data.useThreads;
-		this.replySameFolder = Data.replySameFolder;
-		this.layout = Data.layout;
-		this.usePreviewPane = Data.usePreviewPane;
-		this.useCheckboxesInList = Data.useCheckboxesInList;
-		this.allowLanguagesOnSettings = Data.allowLanguagesOnSettings;
-
-		this.isDesktopNotificationsSupported = ko.computed(function () {
-			return Enums.DesktopNotifications.NotSupported !== Data.desktopNotificationsPermisions();
-		});
-
-		this.isDesktopNotificationsDenied = ko.computed(function () {
-			return Enums.DesktopNotifications.NotSupported === Data.desktopNotificationsPermisions() ||
-				Enums.DesktopNotifications.Denied === Data.desktopNotificationsPermisions();
-		});
-
-		this.mainLanguageFullName = ko.computed(function () {
-			return Utils.convertLangName(this.mainLanguage());
-		}, this);
-
-		this.languageTrigger = ko.observable(Enums.SaveSettingsStep.Idle).extend({'throttle': 100});
-		this.mppTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
-
-		this.isAnimationSupported = Globals.bAnimationSupported;
-	}
-
-	GeneralAppSetting.prototype.toggleLayout = function ()
-	{
-		this.layout(Enums.Layout.NoPreview === this.layout() ? Enums.Layout.SidePreview : Enums.Layout.NoPreview);
-	};
-
-	GeneralAppSetting.prototype.onBuild = function ()
-	{
-		var self = this;
-
-		_.delay(function () {
-
-			var
-				f1 = Utils.settingsSaveHelperSimpleFunction(self.mppTrigger, self),
-				fReloadLanguageHelper = function (iSaveSettingsStep) {
-					return function() {
-						self.languageTrigger(iSaveSettingsStep);
-						_.delay(function () {
-							self.languageTrigger(Enums.SaveSettingsStep.Idle);
-						}, 1000);
-					};
-				}
-			;
-
-			Data.language.subscribe(function (sValue) {
-
-				self.languageTrigger(Enums.SaveSettingsStep.Animate);
-
-				Utils.reloadLanguage(sValue,
-					fReloadLanguageHelper(Enums.SaveSettingsStep.TrueResult),
-					fReloadLanguageHelper(Enums.SaveSettingsStep.FalseResult));
-
-				Remote.saveSettings(null, {
-					'Language': sValue
-				});
-			});
-
-			Data.editorDefaultType.subscribe(function (sValue) {
-				Remote.saveSettings(null, {
-					'EditorDefaultType': sValue
-				});
-			});
-
-			Data.messagesPerPage.subscribe(function (iValue) {
-				Remote.saveSettings(f1, {
-					'MPP': iValue
-				});
-			});
-
-			Data.showImages.subscribe(function (bValue) {
-				Remote.saveSettings(null, {
-					'ShowImages': bValue ? '1' : '0'
-				});
-			});
-
-			Data.interfaceAnimation.subscribe(function (sValue) {
-				Remote.saveSettings(null, {
-					'InterfaceAnimation': sValue
-				});
-			});
-
-			Data.useDesktopNotifications.subscribe(function (bValue) {
-				Utils.timeOutAction('SaveDesktopNotifications', function () {
-					Remote.saveSettings(null, {
-						'DesktopNotifications': bValue ? '1' : '0'
-					});
-				}, 3000);
-			});
-
-			Data.replySameFolder.subscribe(function (bValue) {
-				Utils.timeOutAction('SaveReplySameFolder', function () {
-					Remote.saveSettings(null, {
-						'ReplySameFolder': bValue ? '1' : '0'
-					});
-				}, 3000);
-			});
-
-			Data.useThreads.subscribe(function (bValue) {
-
-				Data.messageList([]);
-
-				Remote.saveSettings(null, {
-					'UseThreads': bValue ? '1' : '0'
-				});
-			});
-
-			Data.layout.subscribe(function (nValue) {
-
-				Data.messageList([]);
-
-				Remote.saveSettings(null, {
-					'Layout': nValue
-				});
-			});
-
-			Data.useCheckboxesInList.subscribe(function (bValue) {
-				Remote.saveSettings(null, {
-					'UseCheckboxesInList': bValue ? '1' : '0'
-				});
-			});
-
-		}, 50);
-	};
-
-	GeneralAppSetting.prototype.onShow = function ()
-	{
-		Data.desktopNotifications.valueHasMutated();
-	};
-
-	GeneralAppSetting.prototype.selectLanguage = function ()
-	{
-		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Languages'));
-	};
-
-	module.exports = GeneralAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/App/Identities.js b/dev/Settings/App/Identities.js
deleted file mode 100644
index 7c681981e..000000000
--- a/dev/Settings/App/Identities.js
+++ /dev/null
@@ -1,233 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-		ko = require('ko'),
-
-		Enums = require('Common/Enums'),
-		Utils = require('Common/Utils'),
-		HtmlEditor = require('Common/HtmlEditor'),
-
-		Data = require('Storage/App/Data'),
-		Remote = require('Storage/App/Remote')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function IdentitiesAppSetting()
-	{
-		this.editor = null;
-		this.defautOptionsAfterRender = Utils.defautOptionsAfterRender;
-
-		this.accountEmail = Data.accountEmail;
-		this.displayName = Data.displayName;
-		this.signature = Data.signature;
-		this.signatureToAll = Data.signatureToAll;
-		this.replyTo = Data.replyTo;
-
-		this.signatureDom = ko.observable(null);
-
-		this.defaultIdentityIDTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
-		this.displayNameTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
-		this.replyTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
-		this.signatureTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
-
-		this.identities = Data.identities;
-		this.defaultIdentityID = Data.defaultIdentityID;
-
-		this.identitiesOptions = ko.computed(function () {
-
-			var
-				aList = this.identities(),
-				aResult = []
-			;
-
-			if (0 < aList.length)
-			{
-				aResult.push({
-					'id': this.accountEmail.peek(),
-					'name': this.formattedAccountIdentity(),
-					'seporator': false
-				});
-
-				aResult.push({
-					'id': '---',
-					'name': '---',
-					'seporator': true,
-					'disabled': true
-				});
-
-				_.each(aList, function (oItem) {
-					aResult.push({
-						'id': oItem.id,
-						'name': oItem.formattedNameForEmail(),
-						'seporator': false
-					});
-				});
-			}
-
-			return aResult;
-		}, this);
-
-		this.processText = ko.computed(function () {
-			return Data.identitiesLoading() ? Utils.i18n('SETTINGS_IDENTITIES/LOADING_PROCESS') : '';
-		}, this);
-
-		this.visibility = ko.computed(function () {
-			return '' === this.processText() ? 'hidden' : 'visible';
-		}, this);
-
-		this.identityForDeletion = ko.observable(null).extend({'falseTimeout': 3000}).extend({'toggleSubscribe': [this,
-			function (oPrev) {
-				if (oPrev)
-				{
-					oPrev.deleteAccess(false);
-				}
-			}, function (oNext) {
-				if (oNext)
-				{
-					oNext.deleteAccess(true);
-				}
-			}
-		]});
-	}
-
-	/**
-	 *
-	 * @return {string}
-	 */
-	IdentitiesAppSetting.prototype.formattedAccountIdentity = function ()
-	{
-		var
-			sDisplayName = this.displayName.peek(),
-			sEmail = this.accountEmail.peek()
-		;
-
-		return '' === sDisplayName ? sEmail : '"' + Utils.quoteName(sDisplayName) + '" <' + sEmail + '>';
-	};
-
-	IdentitiesAppSetting.prototype.addNewIdentity = function ()
-	{
-		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Identity'));
-	};
-
-	IdentitiesAppSetting.prototype.editIdentity = function (oIdentity)
-	{
-		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Identity'), [oIdentity]);
-	};
-
-	/**
-	 * @param {IdentityModel} oIdentityToRemove
-	 */
-	IdentitiesAppSetting.prototype.deleteIdentity = function (oIdentityToRemove)
-	{
-		if (oIdentityToRemove && oIdentityToRemove.deleteAccess())
-		{
-			this.identityForDeletion(null);
-
-			var
-				fRemoveFolder = function (oIdentity) {
-					return oIdentityToRemove === oIdentity;
-				}
-			;
-
-			if (oIdentityToRemove)
-			{
-				this.identities.remove(fRemoveFolder);
-
-				Remote.identityDelete(function () {
-					require('App/App').accountsAndIdentities();
-				}, oIdentityToRemove.id);
-			}
-		}
-	};
-
-	IdentitiesAppSetting.prototype.onFocus = function ()
-	{
-		if (!this.editor && this.signatureDom())
-		{
-			var
-				self = this,
-				sSignature = Data.signature()
-			;
-
-			this.editor = new HtmlEditor(self.signatureDom(), function () {
-				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);
-				}
-			});
-		}
-	};
-
-	IdentitiesAppSetting.prototype.onBuild = function (oDom)
-	{
-		var self = this;
-
-		oDom
-			.on('click', '.identity-item .e-action', function () {
-				var oIdentityItem = ko.dataFor(this);
-				if (oIdentityItem)
-				{
-					self.editIdentity(oIdentityItem);
-				}
-			})
-		;
-
-		_.delay(function () {
-
-			var
-				f1 = Utils.settingsSaveHelperSimpleFunction(self.displayNameTrigger, self),
-				f2 = Utils.settingsSaveHelperSimpleFunction(self.replyTrigger, self),
-				f3 = Utils.settingsSaveHelperSimpleFunction(self.signatureTrigger, self),
-				f4 = Utils.settingsSaveHelperSimpleFunction(self.defaultIdentityIDTrigger, self)
-			;
-
-			Data.defaultIdentityID.subscribe(function (sValue) {
-				Remote.saveSettings(f4, {
-					'DefaultIdentityID': sValue
-				});
-			});
-
-			Data.displayName.subscribe(function (sValue) {
-				Remote.saveSettings(f1, {
-					'DisplayName': sValue
-				});
-			});
-
-			Data.replyTo.subscribe(function (sValue) {
-				Remote.saveSettings(f2, {
-					'ReplyTo': sValue
-				});
-			});
-
-			Data.signature.subscribe(function (sValue) {
-				Remote.saveSettings(f3, {
-					'Signature': sValue
-				});
-			});
-
-			Data.signatureToAll.subscribe(function (bValue) {
-				Remote.saveSettings(null, {
-					'SignatureToAll': bValue ? '1' : '0'
-				});
-			});
-
-		}, 50);
-	};
-
-	module.exports = IdentitiesAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/App/Identity.js b/dev/Settings/App/Identity.js
deleted file mode 100644
index a72c9f8aa..000000000
--- a/dev/Settings/App/Identity.js
+++ /dev/null
@@ -1,103 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-		ko = require('ko'),
-
-		Enums = require('Common/Enums'),
-		Utils = require('Common/Utils'),
-		HtmlEditor = require('Common/HtmlEditor'),
-
-		Data = require('Storage/App/Data'),
-		Remote = require('Storage/App/Remote')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function IdentityAppSetting()
-	{
-		this.editor = null;
-
-		this.displayName = Data.displayName;
-		this.signature = Data.signature;
-		this.signatureToAll = Data.signatureToAll;
-		this.replyTo = Data.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);
-	}
-
-	IdentityAppSetting.prototype.onFocus = function ()
-	{
-		if (!this.editor && this.signatureDom())
-		{
-			var
-				self = this,
-				sSignature = Data.signature()
-			;
-
-			this.editor = new HtmlEditor(self.signatureDom(), function () {
-				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);
-				}
-			});
-		}
-	};
-
-	IdentityAppSetting.prototype.onBuild = function ()
-	{
-		var self = this;
-		_.delay(function () {
-
-			var
-				f1 = Utils.settingsSaveHelperSimpleFunction(self.displayNameTrigger, self),
-				f2 = Utils.settingsSaveHelperSimpleFunction(self.replyTrigger, self),
-				f3 = Utils.settingsSaveHelperSimpleFunction(self.signatureTrigger, self)
-			;
-
-			Data.displayName.subscribe(function (sValue) {
-				Remote.saveSettings(f1, {
-					'DisplayName': sValue
-				});
-			});
-
-			Data.replyTo.subscribe(function (sValue) {
-				Remote.saveSettings(f2, {
-					'ReplyTo': sValue
-				});
-			});
-
-			Data.signature.subscribe(function (sValue) {
-				Remote.saveSettings(f3, {
-					'Signature': sValue
-				});
-			});
-
-			Data.signatureToAll.subscribe(function (bValue) {
-				Remote.saveSettings(null, {
-					'SignatureToAll': bValue ? '1' : '0'
-				});
-			});
-
-		}, 50);
-	};
-
-	module.exports = IdentityAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/App/OpenPGP.js b/dev/Settings/App/OpenPGP.js
deleted file mode 100644
index 143bdea07..000000000
--- a/dev/Settings/App/OpenPGP.js
+++ /dev/null
@@ -1,83 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		ko = require('ko'),
-
-		kn = require('Knoin/Knoin'),
-
-		Data = require('Storage/App/Data')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function OpenPGPAppSetting()
-	{
-		this.openpgpkeys = Data.openpgpkeys;
-		this.openpgpkeysPublic = Data.openpgpkeysPublic;
-		this.openpgpkeysPrivate = Data.openpgpkeysPrivate;
-
-		this.openPgpKeyForDeletion = ko.observable(null).extend({'falseTimeout': 3000}).extend({'toggleSubscribe': [this,
-			function (oPrev) {
-				if (oPrev)
-				{
-					oPrev.deleteAccess(false);
-				}
-			}, function (oNext) {
-				if (oNext)
-				{
-					oNext.deleteAccess(true);
-				}
-			}
-		]});
-	}
-
-	OpenPGPAppSetting.prototype.addOpenPgpKey = function ()
-	{
-		kn.showScreenPopup(require('View/Popup/AddOpenPgpKey'));
-	};
-
-	OpenPGPAppSetting.prototype.generateOpenPgpKey = function ()
-	{
-		kn.showScreenPopup(require('View/Popup/NewOpenPgpKey'));
-	};
-
-	OpenPGPAppSetting.prototype.viewOpenPgpKey = function (oOpenPgpKey)
-	{
-		if (oOpenPgpKey)
-		{
-			kn.showScreenPopup(require('View/Popup/ViewOpenPgpKey'), [oOpenPgpKey]);
-		}
-	};
-
-	/**
-	 * @param {OpenPgpKeyModel} oOpenPgpKeyToRemove
-	 */
-	OpenPGPAppSetting.prototype.deleteOpenPgpKey = function (oOpenPgpKeyToRemove)
-	{
-		if (oOpenPgpKeyToRemove && oOpenPgpKeyToRemove.deleteAccess())
-		{
-			this.openPgpKeyForDeletion(null);
-
-			if (oOpenPgpKeyToRemove && Data.openpgpKeyring)
-			{
-				this.openpgpkeys.remove(function (oOpenPgpKey) {
-					return oOpenPgpKeyToRemove === oOpenPgpKey;
-				});
-
-				Data.openpgpKeyring[oOpenPgpKeyToRemove.isPrivate ? 'privateKeys' : 'publicKeys']
-					.removeForId(oOpenPgpKeyToRemove.guid);
-
-				Data.openpgpKeyring.store();
-
-				require('App/App').reloadOpenPgpKeys();
-			}
-		}
-	};
-
-	module.exports = OpenPGPAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/App/Security.js b/dev/Settings/App/Security.js
deleted file mode 100644
index c5d284e3a..000000000
--- a/dev/Settings/App/Security.js
+++ /dev/null
@@ -1,166 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		ko = require('ko'),
-
-		Enums = require('Common/Enums'),
-		Globals = require('Common/Globals'),
-		Utils = require('Common/Utils'),
-
-		Remote = require('Storage/App/Remote')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function SecurityAppSetting()
-	{
-		this.processing = ko.observable(false);
-		this.clearing = ko.observable(false);
-		this.secreting = ko.observable(false);
-
-		this.viewUser = ko.observable('');
-		this.viewEnable = ko.observable(false);
-		this.viewEnable.subs = true;
-		this.twoFactorStatus = ko.observable(false);
-
-		this.viewSecret = ko.observable('');
-		this.viewBackupCodes = ko.observable('');
-		this.viewUrl = ko.observable('');
-
-		this.bFirst = true;
-
-		this.viewTwoFactorStatus = ko.computed(function () {
-			Globals.langChangeTrigger();
-			return Utils.i18n(
-				this.twoFactorStatus() ?
-					'SETTINGS_SECURITY/TWO_FACTOR_SECRET_CONFIGURED_DESC' :
-					'SETTINGS_SECURITY/TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC'
-			);
-		}, this);
-
-		this.onResult = _.bind(this.onResult, this);
-		this.onSecretResult = _.bind(this.onSecretResult, this);
-	}
-
-	SecurityAppSetting.prototype.showSecret = function ()
-	{
-		this.secreting(true);
-		Remote.showTwoFactorSecret(this.onSecretResult);
-	};
-
-	SecurityAppSetting.prototype.hideSecret = function ()
-	{
-		this.viewSecret('');
-		this.viewBackupCodes('');
-		this.viewUrl('');
-	};
-
-	SecurityAppSetting.prototype.createTwoFactor = function ()
-	{
-		this.processing(true);
-		Remote.createTwoFactor(this.onResult);
-	};
-
-	SecurityAppSetting.prototype.enableTwoFactor = function ()
-	{
-		this.processing(true);
-		Remote.enableTwoFactor(this.onResult, this.viewEnable());
-	};
-
-	SecurityAppSetting.prototype.testTwoFactor = function ()
-	{
-		require('Knoin/Knoin').showScreenPopup(require('View/Popup/TwoFactorTest'));
-	};
-
-	SecurityAppSetting.prototype.clearTwoFactor = function ()
-	{
-		this.viewSecret('');
-		this.viewBackupCodes('');
-		this.viewUrl('');
-
-		this.clearing(true);
-		Remote.clearTwoFactor(this.onResult);
-	};
-
-	SecurityAppSetting.prototype.onShow = function ()
-	{
-		this.viewSecret('');
-		this.viewBackupCodes('');
-		this.viewUrl('');
-	};
-
-	SecurityAppSetting.prototype.onResult = function (sResult, oData)
-	{
-		this.processing(false);
-		this.clearing(false);
-
-		if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
-		{
-			this.viewUser(Utils.pString(oData.Result.User));
-			this.viewEnable(!!oData.Result.Enable);
-			this.twoFactorStatus(!!oData.Result.IsSet);
-
-			this.viewSecret(Utils.pString(oData.Result.Secret));
-			this.viewBackupCodes(Utils.pString(oData.Result.BackupCodes).replace(/[\s]+/g, '  '));
-			this.viewUrl(Utils.pString(oData.Result.Url));
-		}
-		else
-		{
-			this.viewUser('');
-			this.viewEnable(false);
-			this.twoFactorStatus(false);
-
-			this.viewSecret('');
-			this.viewBackupCodes('');
-			this.viewUrl('');
-		}
-
-		if (this.bFirst)
-		{
-			this.bFirst = false;
-			var self = this;
-			this.viewEnable.subscribe(function (bValue) {
-				if (this.viewEnable.subs)
-				{
-					Remote.enableTwoFactor(function (sResult, oData) {
-						if (Enums.StorageResultType.Success !== sResult || !oData || !oData.Result)
-						{
-							self.viewEnable.subs = false;
-							self.viewEnable(false);
-							self.viewEnable.subs = true;
-						}
-					}, bValue);
-				}
-			}, this);
-		}
-	};
-
-	SecurityAppSetting.prototype.onSecretResult = function (sResult, oData)
-	{
-		this.secreting(false);
-
-		if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
-		{
-			this.viewSecret(Utils.pString(oData.Result.Secret));
-			this.viewUrl(Utils.pString(oData.Result.Url));
-		}
-		else
-		{
-			this.viewSecret('');
-			this.viewUrl('');
-		}
-	};
-
-	SecurityAppSetting.prototype.onBuild = function ()
-	{
-		this.processing(true);
-		Remote.getTwoFactor(this.onResult);
-	};
-
-	module.exports = SecurityAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/App/Social.js b/dev/Settings/App/Social.js
deleted file mode 100644
index b2236088c..000000000
--- a/dev/Settings/App/Social.js
+++ /dev/null
@@ -1,76 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	/**
-	 * @constructor
-	 */
-	function SocialAppSetting()
-	{
-		var
-			Utils = require('Common/Utils'),
-			Data = require('Storage/App/Data')
-		;
-
-		this.googleEnable = Data.googleEnable;
-
-		this.googleActions = Data.googleActions;
-		this.googleLoggined = Data.googleLoggined;
-		this.googleUserName = Data.googleUserName;
-
-		this.facebookEnable = Data.facebookEnable;
-
-		this.facebookActions = Data.facebookActions;
-		this.facebookLoggined = Data.facebookLoggined;
-		this.facebookUserName = Data.facebookUserName;
-
-		this.twitterEnable = Data.twitterEnable;
-
-		this.twitterActions = Data.twitterActions;
-		this.twitterLoggined = Data.twitterLoggined;
-		this.twitterUserName = Data.twitterUserName;
-
-		this.connectGoogle = Utils.createCommand(this, function () {
-			if (!this.googleLoggined())
-			{
-				require('App/App').googleConnect();
-			}
-		}, function () {
-			return !this.googleLoggined() && !this.googleActions();
-		});
-
-		this.disconnectGoogle = Utils.createCommand(this, function () {
-			require('App/App').googleDisconnect();
-		});
-
-		this.connectFacebook = Utils.createCommand(this, function () {
-			if (!this.facebookLoggined())
-			{
-				require('App/App').facebookConnect();
-			}
-		}, function () {
-			return !this.facebookLoggined() && !this.facebookActions();
-		});
-
-		this.disconnectFacebook = Utils.createCommand(this, function () {
-			require('App/App').facebookDisconnect();
-		});
-
-		this.connectTwitter = Utils.createCommand(this, function () {
-			if (!this.twitterLoggined())
-			{
-				require('App/App').twitterConnect();
-			}
-		}, function () {
-			return !this.twitterLoggined() && !this.twitterActions();
-		});
-
-		this.disconnectTwitter = Utils.createCommand(this, function () {
-			require('App/App').twitterDisconnect();
-		});
-	}
-
-	module.exports = SocialAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/App/Themes.js b/dev/Settings/App/Themes.js
deleted file mode 100644
index a09875c67..000000000
--- a/dev/Settings/App/Themes.js
+++ /dev/null
@@ -1,132 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		window = require('window'),
-		_ = require('_'),
-		$ = require('$'),
-		ko = require('ko'),
-
-		Enums = require('Common/Enums'),
-		Utils = require('Common/Utils'),
-		LinkBuilder = require('Common/LinkBuilder'),
-
-		Data = require('Storage/App/Data'),
-		Remote = require('Storage/App/Remote')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function ThemesAppSetting()
-	{
-		var self = this;
-
-		this.mainTheme = Data.mainTheme;
-		this.themesObjects = ko.observableArray([]);
-
-		this.themeTrigger = ko.observable(Enums.SaveSettingsStep.Idle).extend({'throttle': 100});
-
-		this.iTimer = 0;
-		this.oThemeAjaxRequest = null;
-
-		Data.theme.subscribe(function (sValue) {
-
-			_.each(this.themesObjects(), function (oTheme) {
-				oTheme.selected(sValue === oTheme.name);
-			});
-
-			var
-				oThemeLink = $('#rlThemeLink'),
-				oThemeStyle = $('#rlThemeStyle'),
-				sUrl = oThemeLink.attr('href')
-			;
-
-			if (!sUrl)
-			{
-				sUrl = oThemeStyle.attr('data-href');
-			}
-
-			if (sUrl)
-			{
-				sUrl = sUrl.toString().replace(/\/-\/[^\/]+\/\-\//, '/-/' + sValue + '/-/');
-				sUrl = sUrl.toString().replace(/\/Css\/[^\/]+\/User\//, '/Css/0/User/');
-
-				if ('Json/' !== sUrl.substring(sUrl.length - 5, sUrl.length))
-				{
-					sUrl += 'Json/';
-				}
-
-				window.clearTimeout(self.iTimer);
-				self.themeTrigger(Enums.SaveSettingsStep.Animate);
-
-				if (this.oThemeAjaxRequest && this.oThemeAjaxRequest.abort)
-				{
-					this.oThemeAjaxRequest.abort();
-				}
-
-				this.oThemeAjaxRequest = $.ajax({
-					'url': sUrl,
-					'dataType': 'json'
-				}).done(function(aData) {
-
-					if (aData && Utils.isArray(aData) && 2 === aData.length)
-					{
-						if (oThemeLink && oThemeLink[0] && (!oThemeStyle || !oThemeStyle[0]))
-						{
-							oThemeStyle = $('');
-							oThemeLink.after(oThemeStyle);
-							oThemeLink.remove();
-						}
-
-						if (oThemeStyle && oThemeStyle[0])
-						{
-							oThemeStyle.attr('data-href', sUrl).attr('data-theme', aData[0]);
-							if (oThemeStyle && oThemeStyle[0] && oThemeStyle[0].styleSheet && !Utils.isUnd(oThemeStyle[0].styleSheet.cssText))
-							{
-								oThemeStyle[0].styleSheet.cssText = aData[1];
-							}
-							else
-							{
-								oThemeStyle.text(aData[1]);
-							}
-						}
-
-						self.themeTrigger(Enums.SaveSettingsStep.TrueResult);
-					}
-
-				}).always(function() {
-
-					self.iTimer = window.setTimeout(function () {
-						self.themeTrigger(Enums.SaveSettingsStep.Idle);
-					}, 1000);
-
-					self.oThemeAjaxRequest = null;
-				});
-			}
-
-			Remote.saveSettings(null, {
-				'Theme': sValue
-			});
-
-		}, this);
-	}
-
-	ThemesAppSetting.prototype.onBuild = function ()
-	{
-		var sCurrentTheme = Data.theme();
-		this.themesObjects(_.map(Data.themes(), function (sTheme) {
-			return {
-				'name': sTheme,
-				'nameDisplay': Utils.convertThemeName(sTheme),
-				'selected': ko.observable(sTheme === sCurrentTheme),
-				'themePreviewSrc': LinkBuilder.themePreviewLink(sTheme)
-			};
-		}));
-	};
-
-	module.exports = ThemesAppSetting;
-
-}());
\ No newline at end of file
diff --git a/dev/Settings/User/Accounts.js b/dev/Settings/User/Accounts.js
new file mode 100644
index 000000000..3b79644cd
--- /dev/null
+++ b/dev/Settings/User/Accounts.js
@@ -0,0 +1,161 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		window = require('window'),
+		_ = require('_'),
+		ko = require('ko'),
+
+		Enums = require('Common/Enums'),
+		Links = require('Common/Links'),
+
+		AccountStore = require('Stores/User/Account'),
+		IdentityStore = require('Stores/User/Identity'),
+
+		Settings = require('Storage/Settings'),
+		Remote = require('Remote/User/Ajax')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function AccountsUserSettings()
+	{
+		this.allowAdditionalAccount = Settings.capa(Enums.Capa.AdditionalAccounts);
+		this.allowIdentities = true;
+
+		this.accounts = AccountStore.accounts;
+		this.identities = IdentityStore.identities;
+
+		this.accountForDeletion = ko.observable(null).deleteAccessHelper();
+		this.identityForDeletion = ko.observable(null).deleteAccessHelper();
+	}
+
+	AccountsUserSettings.prototype.scrollableOptions = function ()
+	{
+		return {
+			handle: '.drag-handle'
+		};
+	};
+
+	AccountsUserSettings.prototype.addNewAccount = function ()
+	{
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Account'));
+	};
+
+	AccountsUserSettings.prototype.editAccount = function (oAccountItem)
+	{
+		if (oAccountItem && oAccountItem.canBeEdit())
+		{
+			require('Knoin/Knoin').showScreenPopup(require('View/Popup/Account'), [oAccountItem]);
+		}
+	};
+
+	AccountsUserSettings.prototype.addNewIdentity = function ()
+	{
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Identity'));
+	};
+
+	AccountsUserSettings.prototype.editIdentity = function (oIdentity)
+	{
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Identity'), [oIdentity]);
+	};
+
+	/**
+	 * @param {AccountModel} oAccountToRemove
+	 */
+	AccountsUserSettings.prototype.deleteAccount = function (oAccountToRemove)
+	{
+		if (oAccountToRemove && oAccountToRemove.deleteAccess())
+		{
+			this.accountForDeletion(null);
+
+			var
+				kn = require('Knoin/Knoin'),
+				fRemoveAccount = function (oAccount) {
+					return oAccountToRemove === oAccount;
+				}
+			;
+
+			if (oAccountToRemove)
+			{
+				this.accounts.remove(fRemoveAccount);
+
+				Remote.accountDelete(function (sResult, oData) {
+
+					if (Enums.StorageResultType.Success === sResult && oData &&
+						oData.Result && oData.Reload)
+					{
+						kn.routeOff();
+						kn.setHash(Links.root(), true);
+						kn.routeOff();
+
+						_.defer(function () {
+							window.location.reload();
+						});
+					}
+					else
+					{
+						require('App/User').accountsAndIdentities();
+					}
+
+				}, oAccountToRemove.email);
+			}
+		}
+	};
+
+	/**
+	 * @param {IdentityModel} oIdentityToRemove
+	 */
+	AccountsUserSettings.prototype.deleteIdentity = function (oIdentityToRemove)
+	{
+		if (oIdentityToRemove && oIdentityToRemove.deleteAccess())
+		{
+			this.identityForDeletion(null);
+
+			if (oIdentityToRemove)
+			{
+				IdentityStore.identities.remove(function (oIdentity) {
+					return oIdentityToRemove === oIdentity;
+				});
+
+				Remote.identityDelete(function () {
+					require('App/User').accountsAndIdentities();
+				}, oIdentityToRemove.id);
+			}
+		}
+	};
+
+	AccountsUserSettings.prototype.accountsAndIdentitiesAfterMove = function ()
+	{
+		Remote.accountsAndIdentitiesSortOrder(null,
+			AccountStore.accountsEmails.peek(), IdentityStore.identitiesIDS.peek());
+	};
+
+	AccountsUserSettings.prototype.onBuild = function (oDom)
+	{
+		var self = this;
+
+		oDom
+			.on('click', '.accounts-list .account-item .e-action', function () {
+				var oAccountItem = ko.dataFor(this);
+				if (oAccountItem)
+				{
+					self.editAccount(oAccountItem);
+				}
+			})
+			.on('click', '.identities-list .identity-item .e-action', function () {
+				var oIdentityItem = ko.dataFor(this);
+				if (oIdentityItem)
+				{
+					self.editIdentity(oIdentityItem);
+				}
+			})
+		;
+	};
+
+	module.exports = AccountsUserSettings;
+
+}());
\ No newline at end of file
diff --git a/dev/Settings/App/ChangePassword.js b/dev/Settings/User/ChangePassword.js
similarity index 81%
rename from dev/Settings/App/ChangePassword.js
rename to dev/Settings/User/ChangePassword.js
index 1b77472af..8a53619c4 100644
--- a/dev/Settings/App/ChangePassword.js
+++ b/dev/Settings/User/ChangePassword.js
@@ -9,14 +9,15 @@
 
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
 
-		Remote = require('Storage/App/Remote')
+		Remote = require('Remote/User/Ajax')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function ChangePasswordAppSetting()
+	function ChangePasswordUserSettings()
 	{
 		this.changeProcess = ko.observable(false);
 
@@ -53,7 +54,7 @@
 			if (this.newPassword() !== this.newPassword2())
 			{
 				this.passwordMismatch(true);
-				this.errorDescription(Utils.i18n('SETTINGS_CHANGE_PASSWORD/ERROR_PASSWORD_MISMATCH'));
+				this.errorDescription(Translator.i18n('SETTINGS_CHANGE_PASSWORD/ERROR_PASSWORD_MISMATCH'));
 			}
 			else
 			{
@@ -76,7 +77,7 @@
 		this.onChangePasswordResponse = _.bind(this.onChangePasswordResponse, this);
 	}
 
-	ChangePasswordAppSetting.prototype.onHide = function ()
+	ChangePasswordUserSettings.prototype.onHide = function ()
 	{
 		this.changeProcess(false);
 		this.currentPassword('');
@@ -87,7 +88,7 @@
 		this.currentPassword.error(false);
 	};
 
-	ChangePasswordAppSetting.prototype.onChangePasswordResponse = function (sResult, oData)
+	ChangePasswordUserSettings.prototype.onChangePasswordResponse = function (sResult, oData)
 	{
 		this.changeProcess(false);
 		this.passwordMismatch(false);
@@ -111,11 +112,11 @@
 			}
 
 			this.passwordUpdateError(true);
-			this.errorDescription(oData && oData.ErrorCode ? Utils.getNotification(oData.ErrorCode) :
-				Utils.getNotification(Enums.Notification.CouldNotSaveNewPassword));
+			this.errorDescription(oData && oData.ErrorCode ? Translator.getNotification(oData.ErrorCode) :
+				Translator.getNotification(Enums.Notification.CouldNotSaveNewPassword));
 		}
 	};
 
-	module.exports = ChangePasswordAppSetting;
+	module.exports = ChangePasswordUserSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/App/Contacts.js b/dev/Settings/User/Contacts.js
similarity index 50%
rename from dev/Settings/App/Contacts.js
rename to dev/Settings/User/Contacts.js
index 46c3d3e3c..a20d76156 100644
--- a/dev/Settings/App/Contacts.js
+++ b/dev/Settings/User/Contacts.js
@@ -6,22 +6,24 @@
 	var
 		ko = require('ko'),
 
-		Remote = require('Storage/App/Remote'),
-		Data = require('Storage/App/Data')
+		AppStore = require('Stores/User/App'),
+		ContactStore = require('Stores/User/Contact'),
+
+		Remote = require('Remote/User/Ajax')
 	;
 
 	/**
 	 * @constructor
 	 */
-	function ContactsAppSetting()
+	function ContactsUserSettings()
 	{
-		this.contactsAutosave = Data.contactsAutosave;
+		this.contactsAutosave = AppStore.contactsAutosave;
 
-		this.allowContactsSync = Data.allowContactsSync;
-		this.enableContactsSync = Data.enableContactsSync;
-		this.contactsSyncUrl = Data.contactsSyncUrl;
-		this.contactsSyncUser = Data.contactsSyncUser;
-		this.contactsSyncPass = Data.contactsSyncPass;
+		this.allowContactsSync = ContactStore.allowContactsSync;
+		this.enableContactsSync = ContactStore.enableContactsSync;
+		this.contactsSyncUrl = ContactStore.contactsSyncUrl;
+		this.contactsSyncUser = ContactStore.contactsSyncUser;
+		this.contactsSyncPass = ContactStore.contactsSyncPass;
 
 		this.saveTrigger = ko.computed(function () {
 			return [
@@ -31,6 +33,15 @@
 				this.contactsSyncPass()
 			].join('|');
 		}, this).extend({'throttle': 500});
+	}
+
+	ContactsUserSettings.prototype.onBuild = function ()
+	{
+		this.contactsAutosave.subscribe(function (bValue) {
+			Remote.saveSettings(null, {
+				'ContactsAutosave': bValue ? '1' : '0'
+			});
+		});
 
 		this.saveTrigger.subscribe(function () {
 			Remote.saveContactsSyncData(null,
@@ -40,17 +51,8 @@
 				this.contactsSyncPass()
 			);
 		}, this);
-	}
-
-	ContactsAppSetting.prototype.onBuild = function ()
-	{
-		Data.contactsAutosave.subscribe(function (bValue) {
-			Remote.saveSettings(null, {
-				'ContactsAutosave': bValue ? '1' : '0'
-			});
-		});
 	};
 
-	module.exports = ContactsAppSetting;
+	module.exports = ContactsUserSettings;
 
 }());
\ No newline at end of file
diff --git a/dev/Settings/User/Filters.js b/dev/Settings/User/Filters.js
new file mode 100644
index 000000000..f5f133299
--- /dev/null
+++ b/dev/Settings/User/Filters.js
@@ -0,0 +1,244 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+		_ = require('_'),
+
+		Enums = require('Common/Enums'),
+		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
+
+		FilterStore = require('Stores/User/Filter'),
+
+		Remote = require('Remote/User/Ajax')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function FiltersUserSettings()
+	{
+		var self = this;
+
+		this.modules = FilterStore.modules;
+		this.filters = FilterStore.filters;
+
+		this.inited = ko.observable(false);
+		this.serverError = ko.observable(false);
+		this.serverErrorDesc = ko.observable('');
+		this.haveChanges = ko.observable(false);
+
+		this.saveErrorText = ko.observable('');
+
+		this.filters.subscribe(Utils.windowResizeCallback);
+
+		this.serverError.subscribe(function (bValue) {
+			if (!bValue)
+			{
+				this.serverErrorDesc('');
+			}
+		}, this);
+
+		this.filterRaw = FilterStore.raw;
+		this.filterRaw.capa = FilterStore.capa;
+		this.filterRaw.active = ko.observable(false);
+		this.filterRaw.allow = ko.observable(false);
+		this.filterRaw.error = ko.observable(false);
+
+		this.filterForDeletion = ko.observable(null).extend({'falseTimeout': 3000}).extend(
+			{'toggleSubscribeProperty': [this, 'deleteAccess']});
+
+		this.saveChanges = Utils.createCommand(this, function () {
+
+			if (!this.filters.saving())
+			{
+				if (this.filterRaw.active() && '' === Utils.trim(this.filterRaw()))
+				{
+					this.filterRaw.error(true);
+					return false;
+				}
+
+				this.filters.saving(true);
+				this.saveErrorText('');
+
+				Remote.filtersSave(function (sResult, oData) {
+
+					self.filters.saving(false);
+
+					if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
+					{
+						self.haveChanges(false);
+						self.updateList();
+					}
+					else if (oData && oData.ErrorCode)
+					{
+						self.saveErrorText(oData.ErrorMessageAdditional || Translator.getNotification(oData.ErrorCode));
+					}
+					else
+					{
+						self.saveErrorText(Translator.getNotification(Enums.Notification.CantSaveFilters));
+					}
+
+				}, this.filters(), this.filterRaw(), this.filterRaw.active());
+			}
+
+			return true;
+
+		}, function () {
+			return this.haveChanges();
+		});
+
+		this.filters.subscribe(function () {
+			this.haveChanges(true);
+		}, this);
+
+		this.filterRaw.subscribe(function () {
+			this.haveChanges(true);
+			this.filterRaw.error(false);
+		}, this);
+
+		this.haveChanges.subscribe(function () {
+			this.saveErrorText('');
+		}, this);
+
+		this.filterRaw.active.subscribe(function () {
+			this.haveChanges(true);
+			this.filterRaw.error(false);
+		}, this);
+	}
+
+	FiltersUserSettings.prototype.scrollableOptions = function ()
+	{
+		return {
+			handle: '.drag-handle'
+		};
+	};
+
+	FiltersUserSettings.prototype.updateList = function ()
+	{
+		var
+			self = this,
+			FilterModel = require('Model/Filter')
+		;
+
+		if (!this.filters.loading())
+		{
+			this.filters.loading(true);
+
+			Remote.filtersGet(function (sResult, oData) {
+
+				self.filters.loading(false);
+				self.serverError(false);
+
+				if (Enums.StorageResultType.Success === sResult && oData &&
+					oData.Result && Utils.isArray(oData.Result.Filters))
+				{
+					self.inited(true);
+					self.serverError(false);
+
+					var aResult = _.compact(_.map(oData.Result.Filters, function (aItem) {
+						var oNew = new FilterModel();
+						return (oNew && oNew.parse(aItem)) ? oNew : null;
+					}));
+
+					self.filters(aResult);
+
+					self.modules(oData.Result.Modules ? oData.Result.Modules : {});
+
+					self.filterRaw(oData.Result.Raw || '');
+					self.filterRaw.capa(Utils.isArray(oData.Result.Capa) ? oData.Result.Capa.join(' ') : '');
+					self.filterRaw.active(!!oData.Result.RawIsActive);
+					self.filterRaw.allow(!!oData.Result.RawIsAllow);
+				}
+				else
+				{
+					self.filters([]);
+					self.modules({});
+					self.filterRaw('');
+					self.filterRaw.capa({});
+
+					self.serverError(true);
+					self.serverErrorDesc(oData && oData.ErrorCode ? Translator.getNotification(oData.ErrorCode) :
+						Translator.getNotification(Enums.Notification.CantGetFilters));
+				}
+
+				self.haveChanges(false);
+			});
+		}
+	};
+
+	FiltersUserSettings.prototype.deleteFilter = function (oFilter)
+	{
+		this.filters.remove(oFilter);
+		Utils.delegateRunOnDestroy(oFilter);
+	};
+
+	FiltersUserSettings.prototype.addFilter = function ()
+	{
+		var
+			self = this,
+			FilterModel = require('Model/Filter'),
+			oNew = new FilterModel()
+		;
+
+		oNew.generateID();
+		require('Knoin/Knoin').showScreenPopup(
+			require('View/Popup/Filter'), [oNew, function  () {
+				self.filters.push(oNew);
+				self.filterRaw.active(false);
+			}, false]);
+	};
+
+	FiltersUserSettings.prototype.editFilter = function (oEdit)
+	{
+		var
+			self = this,
+			oCloned = oEdit.cloneSelf()
+		;
+
+		require('Knoin/Knoin').showScreenPopup(
+			require('View/Popup/Filter'), [oCloned, function  () {
+
+				var
+					aFilters = self.filters(),
+					iIndex = aFilters.indexOf(oEdit)
+				;
+
+				if (-1 < iIndex && aFilters[iIndex])
+				{
+					Utils.delegateRunOnDestroy(aFilters[iIndex]);
+					aFilters[iIndex] = oCloned;
+
+					self.filters(aFilters);
+					self.haveChanges(true);
+				}
+
+			}, true]);
+	};
+
+	FiltersUserSettings.prototype.onBuild = function (oDom)
+	{
+		var self = this;
+
+		oDom
+			.on('click', '.filter-item .e-action', function () {
+				var oFilterItem = ko.dataFor(this);
+				if (oFilterItem)
+				{
+					self.editFilter(oFilterItem);
+				}
+			})
+		;
+	};
+
+	FiltersUserSettings.prototype.onShow = function ()
+	{
+		this.updateList();
+	};
+
+	module.exports = FiltersUserSettings;
+
+}());
\ No newline at end of file
diff --git a/dev/Settings/User/Folders.js b/dev/Settings/User/Folders.js
new file mode 100644
index 000000000..c5812c9ed
--- /dev/null
+++ b/dev/Settings/User/Folders.js
@@ -0,0 +1,165 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+
+		Enums = require('Common/Enums'),
+		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
+
+		Cache = require('Common/Cache'),
+
+		Settings = require('Storage/Settings'),
+		Local = require('Storage/Client'),
+
+		FolderStore = require('Stores/User/Folder'),
+
+		Promises = require('Promises/User/Ajax'),
+		Remote = require('Remote/User/Ajax')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function FoldersUserSettings()
+	{
+		this.folderList = FolderStore.folderList;
+
+		this.loading = ko.computed(function () {
+
+			var
+				bLoading = FolderStore.foldersLoading(),
+				bCreating = FolderStore.foldersCreating(),
+				bDeleting = FolderStore.foldersDeleting(),
+				bRenaming = FolderStore.foldersRenaming()
+			;
+
+			return bLoading || bCreating || bDeleting || bRenaming;
+
+		}, this);
+
+		this.folderForDeletion = ko.observable(null).deleteAccessHelper();
+
+		this.folderForEdit = ko.observable(null).extend({'toggleSubscribe': [this,
+			function (oPrev) {
+				if (oPrev)
+				{
+					oPrev.edited(false);
+				}
+			}, function (oNext) {
+				if (oNext && oNext.canBeEdited())
+				{
+					oNext.edited(true);
+				}
+			}
+		]});
+
+		this.useImapSubscribe = !!Settings.settingsGet('UseImapSubscribe');
+	}
+
+	FoldersUserSettings.prototype.folderEditOnEnter = function (oFolder)
+	{
+		var
+			sEditName = oFolder ? Utils.trim(oFolder.nameForEdit()) : ''
+		;
+
+		if ('' !== sEditName && oFolder.name() !== sEditName)
+		{
+			Local.set(Enums.ClientSideKeyName.FoldersLashHash, '');
+
+			require('App/User').foldersPromisesActionHelper(
+				Promises.folderRename(oFolder.fullNameRaw, sEditName, FolderStore.foldersRenaming),
+				Enums.Notification.CantRenameFolder);
+
+			Cache.removeFolderFromCacheList(oFolder.fullNameRaw);
+
+			oFolder.name(sEditName);
+		}
+
+		oFolder.edited(false);
+	};
+
+	FoldersUserSettings.prototype.folderEditOnEsc = function (oFolder)
+	{
+		if (oFolder)
+		{
+			oFolder.edited(false);
+		}
+	};
+
+	FoldersUserSettings.prototype.onShow = function ()
+	{
+		FolderStore.folderList.error('');
+	};
+
+	FoldersUserSettings.prototype.createFolder = function ()
+	{
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/FolderCreate'));
+	};
+
+	FoldersUserSettings.prototype.systemFolder = function ()
+	{
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/FolderSystem'));
+	};
+
+	FoldersUserSettings.prototype.deleteFolder = function (oFolderToRemove)
+	{
+		if (oFolderToRemove && oFolderToRemove.canBeDeleted() && oFolderToRemove.deleteAccess() &&
+			0 === oFolderToRemove.privateMessageCountAll())
+		{
+			this.folderForDeletion(null);
+
+			var
+				fRemoveFolder = function (oFolder) {
+
+					if (oFolderToRemove === oFolder)
+					{
+						return true;
+					}
+
+					oFolder.subFolders.remove(fRemoveFolder);
+					return false;
+				}
+			;
+
+			if (oFolderToRemove)
+			{
+				Local.set(Enums.ClientSideKeyName.FoldersLashHash, '');
+
+				FolderStore.folderList.remove(fRemoveFolder);
+
+				require('App/User').foldersPromisesActionHelper(
+					Promises.folderDelete(oFolderToRemove.fullNameRaw, FolderStore.foldersDeleting),
+					Enums.Notification.CantDeleteFolder);
+
+				Cache.removeFolderFromCacheList(oFolderToRemove.fullNameRaw);
+			}
+		}
+		else if (0 < oFolderToRemove.privateMessageCountAll())
+		{
+			FolderStore.folderList.error(Translator.getNotification(Enums.Notification.CantDeleteNonEmptyFolder));
+		}
+	};
+
+	FoldersUserSettings.prototype.subscribeFolder = function (oFolder)
+	{
+		Local.set(Enums.ClientSideKeyName.FoldersLashHash, '');
+		Remote.folderSetSubscribe(Utils.emptyFunction, oFolder.fullNameRaw, true);
+
+		oFolder.subScribed(true);
+	};
+
+	FoldersUserSettings.prototype.unSubscribeFolder = function (oFolder)
+	{
+		Local.set(Enums.ClientSideKeyName.FoldersLashHash, '');
+		Remote.folderSetSubscribe(Utils.emptyFunction, oFolder.fullNameRaw, false);
+
+		oFolder.subScribed(false);
+	};
+
+	module.exports = FoldersUserSettings;
+
+}());
\ No newline at end of file
diff --git a/dev/Settings/User/General.js b/dev/Settings/User/General.js
new file mode 100644
index 000000000..5d103bdad
--- /dev/null
+++ b/dev/Settings/User/General.js
@@ -0,0 +1,206 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko'),
+
+		Enums = require('Common/Enums'),
+		Consts = require('Common/Consts'),
+		Globals = require('Common/Globals'),
+		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
+
+		AppStore = require('Stores/User/App'),
+		LanguageStore = require('Stores/Language'),
+		SettingsStore = require('Stores/User/Settings'),
+		NotificationStore = require('Stores/User/Notification'),
+		MessageStore = require('Stores/User/Message'),
+
+		Remote = require('Remote/User/Ajax')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function GeneralUserSettings()
+	{
+		this.language = LanguageStore.language;
+		this.languages = LanguageStore.languages;
+		this.messagesPerPage = SettingsStore.messagesPerPage;
+		this.messagesPerPageArray = Consts.Defaults.MessagesPerPageArray;
+
+		this.editorDefaultType = SettingsStore.editorDefaultType;
+		this.layout = SettingsStore.layout;
+		this.usePreviewPane = SettingsStore.usePreviewPane;
+
+		this.soundNotificationIsSupported = NotificationStore.soundNotificationIsSupported;
+		this.enableSoundNotification = NotificationStore.enableSoundNotification;
+
+		this.enableDesktopNotification = NotificationStore.enableDesktopNotification;
+		this.isDesktopNotificationSupported = NotificationStore.isDesktopNotificationSupported;
+		this.isDesktopNotificationDenied = NotificationStore.isDesktopNotificationDenied;
+
+		this.showImages = SettingsStore.showImages;
+		this.useCheckboxesInList = SettingsStore.useCheckboxesInList;
+		this.threadsAllowed = AppStore.threadsAllowed;
+		this.useThreads = SettingsStore.useThreads;
+		this.replySameFolder = SettingsStore.replySameFolder;
+		this.allowLanguagesOnSettings = AppStore.allowLanguagesOnSettings;
+
+		this.languageFullName = ko.computed(function () {
+			return Utils.convertLangName(this.language());
+		}, this);
+
+		this.languageTrigger = ko.observable(Enums.SaveSettingsStep.Idle).extend({'throttle': 100});
+
+		this.mppTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
+		this.editorDefaultTypeTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
+		this.layoutTrigger = ko.observable(Enums.SaveSettingsStep.Idle);
+
+		this.isAnimationSupported = Globals.bAnimationSupported;
+
+		this.editorDefaultTypes = ko.computed(function () {
+			Translator.trigger();
+			return [
+				{'id': Enums.EditorDefaultType.Html, 'name': Translator.i18n('SETTINGS_GENERAL/LABEL_EDITOR_HTML')},
+				{'id': Enums.EditorDefaultType.Plain, 'name': Translator.i18n('SETTINGS_GENERAL/LABEL_EDITOR_PLAIN')},
+				{'id': Enums.EditorDefaultType.HtmlForced, 'name': Translator.i18n('SETTINGS_GENERAL/LABEL_EDITOR_HTML_FORCED')},
+				{'id': Enums.EditorDefaultType.PlainForced, 'name': Translator.i18n('SETTINGS_GENERAL/LABEL_EDITOR_PLAIN_FORCED')}
+			];
+		}, this);
+
+		this.layoutTypes = ko.computed(function () {
+			Translator.trigger();
+			return [
+				{'id': Enums.Layout.NoPreview, 'name': Translator.i18n('SETTINGS_GENERAL/LABEL_LAYOUT_NO_SPLIT')},
+				{'id': Enums.Layout.SidePreview, 'name': Translator.i18n('SETTINGS_GENERAL/LABEL_LAYOUT_VERTICAL_SPLIT')},
+				{'id': Enums.Layout.BottomPreview, 'name': Translator.i18n('SETTINGS_GENERAL/LABEL_LAYOUT_HORIZONTAL_SPLIT')}
+			];
+		}, this);
+	}
+
+	GeneralUserSettings.prototype.testSoundNotification = function ()
+	{
+		NotificationStore.playSoundNotification(true);
+	};
+
+	GeneralUserSettings.prototype.onBuild = function ()
+	{
+		var self = this;
+
+		_.delay(function () {
+
+			var
+				f0 = Utils.settingsSaveHelperSimpleFunction(self.editorDefaultTypeTrigger, self),
+				f1 = Utils.settingsSaveHelperSimpleFunction(self.mppTrigger, self),
+				f2 = Utils.settingsSaveHelperSimpleFunction(self.layoutTrigger, self),
+				fReloadLanguageHelper = function (iSaveSettingsStep) {
+					return function() {
+						self.languageTrigger(iSaveSettingsStep);
+						_.delay(function () {
+							self.languageTrigger(Enums.SaveSettingsStep.Idle);
+						}, 1000);
+					};
+				}
+			;
+
+			self.language.subscribe(function (sValue) {
+
+				self.languageTrigger(Enums.SaveSettingsStep.Animate);
+
+				Translator.reload(false, sValue,
+					fReloadLanguageHelper(Enums.SaveSettingsStep.TrueResult),
+					fReloadLanguageHelper(Enums.SaveSettingsStep.FalseResult));
+
+				Remote.saveSettings(null, {
+					'Language': sValue
+				});
+			});
+
+			self.editorDefaultType.subscribe(function (sValue) {
+				Remote.saveSettings(f0, {
+					'EditorDefaultType': sValue
+				});
+			});
+
+			self.messagesPerPage.subscribe(function (iValue) {
+				Remote.saveSettings(f1, {
+					'MPP': iValue
+				});
+			});
+
+			self.showImages.subscribe(function (bValue) {
+				Remote.saveSettings(null, {
+					'ShowImages': bValue ? '1' : '0'
+				});
+			});
+
+			self.enableDesktopNotification.subscribe(function (bValue) {
+				Utils.timeOutAction('SaveDesktopNotifications', function () {
+					Remote.saveSettings(null, {
+						'DesktopNotifications': bValue ? '1' : '0'
+					});
+				}, 3000);
+			});
+
+			self.enableSoundNotification.subscribe(function (bValue) {
+				Utils.timeOutAction('SaveSoundNotification', function () {
+					Remote.saveSettings(null, {
+						'SoundNotification': bValue ? '1' : '0'
+					});
+				}, 3000);
+			});
+
+			self.replySameFolder.subscribe(function (bValue) {
+				Utils.timeOutAction('SaveReplySameFolder', function () {
+					Remote.saveSettings(null, {
+						'ReplySameFolder': bValue ? '1' : '0'
+					});
+				}, 3000);
+			});
+
+			self.useThreads.subscribe(function (bValue) {
+
+				MessageStore.messageList([]);
+
+				Remote.saveSettings(null, {
+					'UseThreads': bValue ? '1' : '0'
+				});
+			});
+
+			self.layout.subscribe(function (nValue) {
+
+				MessageStore.messageList([]);
+
+				Remote.saveSettings(f2, {
+					'Layout': nValue
+				});
+			});
+
+			self.useCheckboxesInList.subscribe(function (bValue) {
+				Remote.saveSettings(null, {
+					'UseCheckboxesInList': bValue ? '1' : '0'
+				});
+			});
+
+		}, 50);
+	};
+
+	GeneralUserSettings.prototype.onShow = function ()
+	{
+		this.enableDesktopNotification.valueHasMutated();
+	};
+
+	GeneralUserSettings.prototype.selectLanguage = function ()
+	{
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Languages'), [
+			this.language, this.languages(), LanguageStore.userLanguage()
+		]);
+	};
+
+	module.exports = GeneralUserSettings;
+
+}());
\ No newline at end of file
diff --git a/dev/Settings/User/OpenPgp.js b/dev/Settings/User/OpenPgp.js
new file mode 100644
index 000000000..e5c6ef6b9
--- /dev/null
+++ b/dev/Settings/User/OpenPgp.js
@@ -0,0 +1,80 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko'),
+
+		Utils = require('Common/Utils'),
+
+		kn = require('Knoin/Knoin'),
+
+		PgpStore = require('Stores/User/Pgp')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function OpenPgpUserSettings()
+	{
+		this.openpgpkeys = PgpStore.openpgpkeys;
+		this.openpgpkeysPublic = PgpStore.openpgpkeysPublic;
+		this.openpgpkeysPrivate = PgpStore.openpgpkeysPrivate;
+
+		this.openPgpKeyForDeletion = ko.observable(null).deleteAccessHelper();
+	}
+
+	OpenPgpUserSettings.prototype.addOpenPgpKey = function ()
+	{
+		kn.showScreenPopup(require('View/Popup/AddOpenPgpKey'));
+	};
+
+	OpenPgpUserSettings.prototype.generateOpenPgpKey = function ()
+	{
+		kn.showScreenPopup(require('View/Popup/NewOpenPgpKey'));
+	};
+
+	OpenPgpUserSettings.prototype.viewOpenPgpKey = function (oOpenPgpKey)
+	{
+		if (oOpenPgpKey)
+		{
+			kn.showScreenPopup(require('View/Popup/ViewOpenPgpKey'), [oOpenPgpKey]);
+		}
+	};
+
+	/**
+	 * @param {OpenPgpKeyModel} oOpenPgpKeyToRemove
+	 */
+	OpenPgpUserSettings.prototype.deleteOpenPgpKey = function (oOpenPgpKeyToRemove)
+	{
+		if (oOpenPgpKeyToRemove && oOpenPgpKeyToRemove.deleteAccess())
+		{
+			this.openPgpKeyForDeletion(null);
+
+			if (oOpenPgpKeyToRemove && PgpStore.openpgpKeyring)
+			{
+				var oFindedItem = _.find(PgpStore.openpgpkeys(), function (oOpenPgpKey) {
+					return oOpenPgpKeyToRemove === oOpenPgpKey;
+				});
+
+				if (oFindedItem)
+				{
+					PgpStore.openpgpkeys.remove(oFindedItem);
+					Utils.delegateRunOnDestroy(oFindedItem);
+
+					PgpStore.openpgpKeyring[oFindedItem.isPrivate ? 'privateKeys' : 'publicKeys']
+						.removeForId(oFindedItem.guid);
+
+					PgpStore.openpgpKeyring.store();
+				}
+
+				require('App/User').reloadOpenPgpKeys();
+			}
+		}
+	};
+
+	module.exports = OpenPgpUserSettings;
+
+}());
\ No newline at end of file
diff --git a/dev/Settings/User/Security.js b/dev/Settings/User/Security.js
new file mode 100644
index 000000000..d771a8743
--- /dev/null
+++ b/dev/Settings/User/Security.js
@@ -0,0 +1,73 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko'),
+
+		Enums = require('Common/Enums'),
+		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
+
+		SettinsStore = require('Stores/User/Settings'),
+
+		Settings = require('Storage/Settings'),
+
+		Remote = require('Remote/User/Ajax')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function SecurityUserSettings()
+	{
+		this.capaAutoLogout = Settings.capa(Enums.Capa.AutoLogout);
+		this.capaTwoFactor = Settings.capa(Enums.Capa.TwoFactor);
+
+		this.autoLogout = SettinsStore.autoLogout;
+		this.autoLogout.trigger = ko.observable(Enums.SaveSettingsStep.Idle);
+
+		this.autoLogoutOptions = ko.computed(function () {
+			Translator.trigger();
+			return [
+				{'id': 0, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_NEVER_OPTION_NAME')},
+				{'id': 5, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_MINUTES_OPTION_NAME', {'MINUTES': 5})},
+				{'id': 10, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_MINUTES_OPTION_NAME', {'MINUTES': 10})},
+				{'id': 30, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_MINUTES_OPTION_NAME', {'MINUTES': 30})},
+				{'id': 60, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_MINUTES_OPTION_NAME', {'MINUTES': 60})}
+			];
+		});
+	}
+
+	SecurityUserSettings.prototype.configureTwoFactor = function ()
+	{
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/TwoFactorConfiguration'));
+	};
+
+	SecurityUserSettings.prototype.onBuild = function ()
+	{
+		if (this.capaAutoLogout)
+		{
+			var self = this;
+
+			_.delay(function () {
+
+				var
+					f0 = Utils.settingsSaveHelperSimpleFunction(self.autoLogout.trigger, self)
+				;
+
+				self.autoLogout.subscribe(function (sValue) {
+					Remote.saveSettings(f0, {
+						'AutoLogout': Utils.pInt(sValue)
+					});
+				});
+
+			});
+		}
+	};
+
+	module.exports = SecurityUserSettings;
+
+}());
\ No newline at end of file
diff --git a/dev/Settings/User/Social.js b/dev/Settings/User/Social.js
new file mode 100644
index 000000000..b5521a546
--- /dev/null
+++ b/dev/Settings/User/Social.js
@@ -0,0 +1,79 @@
+
+(function () {
+
+	'use strict';
+
+	/**
+	 * @constructor
+	 */
+	function SocialUserSettings()
+	{
+		var
+			Utils = require('Common/Utils'),
+			SocialStore = require('Stores/Social')
+		;
+
+		this.googleEnable = SocialStore.google.enabled;
+		this.googleEnableAuth = SocialStore.google.capa.auth;
+		this.googleEnableDrive = SocialStore.google.capa.drive;
+		this.googleEnablePreview = SocialStore.google.capa.preview;
+
+		this.googleActions = SocialStore.google.loading;
+		this.googleLoggined = SocialStore.google.loggined;
+		this.googleUserName = SocialStore.google.userName;
+
+		this.facebookEnable = SocialStore.facebook.enabled;
+
+		this.facebookActions = SocialStore.facebook.loading;
+		this.facebookLoggined = SocialStore.facebook.loggined;
+		this.facebookUserName = SocialStore.facebook.userName;
+
+		this.twitterEnable = SocialStore.twitter.enabled;
+
+		this.twitterActions = SocialStore.twitter.loading;
+		this.twitterLoggined = SocialStore.twitter.loggined;
+		this.twitterUserName = SocialStore.twitter.userName;
+
+		this.connectGoogle = Utils.createCommand(this, function () {
+			if (!this.googleLoggined())
+			{
+				require('App/User').googleConnect();
+			}
+		}, function () {
+			return !this.googleLoggined() && !this.googleActions();
+		});
+
+		this.disconnectGoogle = Utils.createCommand(this, function () {
+			require('App/User').googleDisconnect();
+		});
+
+		this.connectFacebook = Utils.createCommand(this, function () {
+			if (!this.facebookLoggined())
+			{
+				require('App/User').facebookConnect();
+			}
+		}, function () {
+			return !this.facebookLoggined() && !this.facebookActions();
+		});
+
+		this.disconnectFacebook = Utils.createCommand(this, function () {
+			require('App/User').facebookDisconnect();
+		});
+
+		this.connectTwitter = Utils.createCommand(this, function () {
+			if (!this.twitterLoggined())
+			{
+				require('App/User').twitterConnect();
+			}
+		}, function () {
+			return !this.twitterLoggined() && !this.twitterActions();
+		});
+
+		this.disconnectTwitter = Utils.createCommand(this, function () {
+			require('App/User').twitterDisconnect();
+		});
+	}
+
+	module.exports = SocialUserSettings;
+
+}());
\ No newline at end of file
diff --git a/dev/Settings/User/Templates.js b/dev/Settings/User/Templates.js
new file mode 100644
index 000000000..db6991f7c
--- /dev/null
+++ b/dev/Settings/User/Templates.js
@@ -0,0 +1,105 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+
+		Translator = require('Common/Translator'),
+
+		TemplateStore = require('Stores/User/Template'),
+
+		Remote = require('Remote/User/Ajax')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function TemplatesUserSettings()
+	{
+		this.templates = TemplateStore.templates;
+
+		this.processText = ko.computed(function () {
+			return TemplateStore.templates.loading() ? Translator.i18n('SETTINGS_TEMPLETS/LOADING_PROCESS') : '';
+		}, this);
+
+		this.visibility = ko.computed(function () {
+			return '' === this.processText() ? 'hidden' : 'visible';
+		}, this);
+
+		this.templateForDeletion = ko.observable(null).deleteAccessHelper();
+	}
+
+	TemplatesUserSettings.prototype.scrollableOptions = function ()
+	{
+		return {
+			handle: '.drag-handle'
+		};
+	};
+
+	TemplatesUserSettings.prototype.addNewTemplate = function ()
+	{
+		require('Knoin/Knoin').showScreenPopup(require('View/Popup/Template'));
+	};
+
+	TemplatesUserSettings.prototype.editTemplate = function (oTemplateItem)
+	{
+		if (oTemplateItem)
+		{
+			require('Knoin/Knoin').showScreenPopup(require('View/Popup/Template'), [oTemplateItem]);
+		}
+	};
+
+	/**
+	 * @param {AccountModel} oTemplateToRemove
+	 */
+	TemplatesUserSettings.prototype.deleteTemplate = function (oTemplateToRemove)
+	{
+		if (oTemplateToRemove && oTemplateToRemove.deleteAccess())
+		{
+			this.templateForDeletion(null);
+
+			var
+				self = this,
+				fRemoveAccount = function (oAccount) {
+					return oTemplateToRemove === oAccount;
+				}
+			;
+
+			if (oTemplateToRemove)
+			{
+				this.templates.remove(fRemoveAccount);
+
+				Remote.templateDelete(function () {
+					self.reloadTemplates();
+				}, oTemplateToRemove.id);
+			}
+		}
+	};
+
+	TemplatesUserSettings.prototype.reloadTemplates = function ()
+	{
+		require('App/User').templates();
+	};
+
+	TemplatesUserSettings.prototype.onBuild = function (oDom)
+	{
+		var self = this;
+
+		oDom
+			.on('click', '.templates-list .template-item .e-action', function () {
+				var oTemplateItem = ko.dataFor(this);
+				if (oTemplateItem)
+				{
+					self.editTemplate(oTemplateItem);
+				}
+			})
+		;
+
+		this.reloadTemplates();
+	};
+
+	module.exports = TemplatesUserSettings;
+
+}());
\ No newline at end of file
diff --git a/dev/Settings/User/Themes.js b/dev/Settings/User/Themes.js
new file mode 100644
index 000000000..cbdeee8c6
--- /dev/null
+++ b/dev/Settings/User/Themes.js
@@ -0,0 +1,185 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		$ = require('$'),
+		ko = require('ko'),
+
+		Jua = require('Jua'),
+
+		Enums = require('Common/Enums'),
+		Utils = require('Common/Utils'),
+		Links = require('Common/Links'),
+		Translator = require('Common/Translator'),
+
+		ThemeStore = require('Stores/Theme'),
+
+		Settings = require('Storage/Settings'),
+		
+		Remote = require('Remote/User/Ajax')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function ThemesUserSettings()
+	{
+		this.theme = ThemeStore.theme;
+		this.themes = ThemeStore.themes;
+		this.themesObjects = ko.observableArray([]);
+
+		this.background = {};
+		this.background.name = ThemeStore.themeBackgroundName;
+		this.background.hash = ThemeStore.themeBackgroundHash;
+		this.background.uploaderButton = ko.observable(null);
+		this.background.loading = ko.observable(false);
+		this.background.error = ko.observable('');
+
+		this.capaUserBackground = ko.observable(Settings.capa(Enums.Capa.UserBackground));
+
+		this.themeTrigger = ko.observable(Enums.SaveSettingsStep.Idle).extend({'throttle': 100});
+
+		this.iTimer = 0;
+		this.oThemeAjaxRequest = null;
+
+		this.theme.subscribe(function (sValue) {
+
+			_.each(this.themesObjects(), function (oTheme) {
+				oTheme.selected(sValue === oTheme.name);
+			});
+
+			Utils.changeTheme(sValue, this.themeTrigger);
+
+			Remote.saveSettings(null, {
+				'Theme': sValue
+			});
+
+		}, this);
+
+		this.background.hash.subscribe(function (sValue) {
+
+			var $oBg = $('#rl-bg');
+			if (!sValue)
+			{
+				if ($oBg.data('backstretch'))
+				{
+					$oBg.backstretch('destroy').attr('style', '');
+				}
+			}
+			else
+			{
+				$('#rl-bg').attr('style', 'background-image: none !important;').backstretch(
+					Links.userBackground(sValue), {
+						'fade': 1000, 'centeredX': true, 'centeredY': true
+					}).removeAttr('style');
+			}
+
+		}, this);
+	}
+
+	ThemesUserSettings.prototype.onBuild = function ()
+	{
+		var sCurrentTheme = this.theme();
+		this.themesObjects(_.map(this.themes(), function (sTheme) {
+			return {
+				'name': sTheme,
+				'nameDisplay': Utils.convertThemeName(sTheme),
+				'selected': ko.observable(sTheme === sCurrentTheme),
+				'themePreviewSrc': Links.themePreviewLink(sTheme)
+			};
+		}));
+
+		this.initUploader();
+	};
+
+	ThemesUserSettings.prototype.onShow = function ()
+	{
+		this.background.error('');
+	};
+
+	ThemesUserSettings.prototype.clearBackground = function ()
+	{
+		if (this.capaUserBackground())
+		{
+			var self = this;
+			Remote.clearUserBackground(function () {
+				self.background.name('');
+				self.background.hash('');
+			});
+		}
+	};
+
+	ThemesUserSettings.prototype.initUploader = function ()
+	{
+		if (this.background.uploaderButton() && this.capaUserBackground())
+		{
+			var
+				oJua = new Jua({
+					'action': Links.uploadBackground(),
+					'name': 'uploader',
+					'queueSize': 1,
+					'multipleSizeLimit': 1,
+					'disableDragAndDrop': true,
+					'disableMultiple': true,
+					'clickElement': this.background.uploaderButton()
+				})
+			;
+
+			oJua
+				.on('onStart', _.bind(function () {
+
+					this.background.loading(true);
+					this.background.error('');
+
+					return true;
+
+				}, this))
+				.on('onComplete', _.bind(function (sId, bResult, oData) {
+
+					this.background.loading(false);
+
+					if (bResult && sId && oData && oData.Result && oData.Result.Name && oData.Result.Hash)
+					{
+						this.background.name(oData.Result.Name);
+						this.background.hash(oData.Result.Hash);
+					}
+					else
+					{
+						this.background.name('');
+						this.background.hash('');
+
+						var sError = '';
+						if (oData.ErrorCode)
+						{
+							switch (oData.ErrorCode)
+							{
+								case Enums.UploadErrorCode.FileIsTooBig:
+									sError = Translator.i18n('SETTINGS_THEMES/ERROR_FILE_IS_TOO_BIG');
+									break;
+								case Enums.UploadErrorCode.FileType:
+									sError = Translator.i18n('SETTINGS_THEMES/ERROR_FILE_TYPE_ERROR');
+									break;
+							}
+						}
+
+						if (!sError && oData.ErrorMessage)
+						{
+							sError = oData.ErrorMessage;
+						}
+
+						this.background.error(sError || Translator.i18n('SETTINGS_THEMES/ERROR_UNKNOWN'));
+					}
+
+					return true;
+
+				}, this))
+			;
+		}
+	};
+
+	module.exports = ThemesUserSettings;
+
+}());
\ No newline at end of file
diff --git a/dev/Storage/AbstractData.js b/dev/Storage/AbstractData.js
deleted file mode 100644
index dd9fe8caa..000000000
--- a/dev/Storage/AbstractData.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
-
-(function () {
-
-	'use strict';
-
-	var
-		Enums = require('Common/Enums'),
-		Utils = require('Common/Utils'),
-
-		Settings = require('Storage/Settings')
-	;
-
-	/**
-	 * @constructor
-	 */
-	function AbstractDataStorate()
-	{
-		Utils.initDataConstructorBySettings(this);
-	}
-
-	AbstractDataStorate.prototype.populateDataOnStart = function()
-	{
-		var
-			mLayout = Utils.pInt(Settings.settingsGet('Layout')),
-			aLanguages = Settings.settingsGet('Languages'),
-			aThemes = Settings.settingsGet('Themes')
-		;
-
-		if (Utils.isArray(aLanguages))
-		{
-			this.languages(aLanguages);
-		}
-
-		if (Utils.isArray(aThemes))
-		{
-			this.themes(aThemes);
-		}
-
-		this.mainLanguage(Settings.settingsGet('Language'));
-		this.mainTheme(Settings.settingsGet('Theme'));
-
-		this.capaAdditionalAccounts(Settings.capa(Enums.Capa.AdditionalAccounts));
-		this.capaAdditionalIdentities(Settings.capa(Enums.Capa.AdditionalIdentities));
-		this.capaGravatar(Settings.capa(Enums.Capa.Gravatar));
-		this.determineUserLanguage(!!Settings.settingsGet('DetermineUserLanguage'));
-		this.determineUserDomain(!!Settings.settingsGet('DetermineUserDomain'));
-
-		this.capaThemes(Settings.capa(Enums.Capa.Themes));
-		this.allowLanguagesOnLogin(!!Settings.settingsGet('AllowLanguagesOnLogin'));
-		this.allowLanguagesOnSettings(!!Settings.settingsGet('AllowLanguagesOnSettings'));
-		this.useLocalProxyForExternalImages(!!Settings.settingsGet('UseLocalProxyForExternalImages'));
-
-		this.editorDefaultType(Settings.settingsGet('EditorDefaultType'));
-		this.showImages(!!Settings.settingsGet('ShowImages'));
-		this.contactsAutosave(!!Settings.settingsGet('ContactsAutosave'));
-		this.interfaceAnimation(Settings.settingsGet('InterfaceAnimation'));
-
-		this.mainMessagesPerPage(Settings.settingsGet('MPP'));
-
-		this.desktopNotifications(!!Settings.settingsGet('DesktopNotifications'));
-		this.useThreads(!!Settings.settingsGet('UseThreads'));
-		this.replySameFolder(!!Settings.settingsGet('ReplySameFolder'));
-		this.useCheckboxesInList(!!Settings.settingsGet('UseCheckboxesInList'));
-
-		this.layout(Enums.Layout.SidePreview);
-		if (-1 < Utils.inArray(mLayout, [Enums.Layout.NoPreview, Enums.Layout.SidePreview, Enums.Layout.BottomPreview]))
-		{
-			this.layout(mLayout);
-		}
-		this.facebookSupported(!!Settings.settingsGet('SupportedFacebookSocial'));
-		this.facebookEnable(!!Settings.settingsGet('AllowFacebookSocial'));
-		this.facebookAppID(Settings.settingsGet('FacebookAppID'));
-		this.facebookAppSecret(Settings.settingsGet('FacebookAppSecret'));
-
-		this.twitterEnable(!!Settings.settingsGet('AllowTwitterSocial'));
-		this.twitterConsumerKey(Settings.settingsGet('TwitterConsumerKey'));
-		this.twitterConsumerSecret(Settings.settingsGet('TwitterConsumerSecret'));
-
-		this.googleEnable(!!Settings.settingsGet('AllowGoogleSocial'));
-		this.googleClientID(Settings.settingsGet('GoogleClientID'));
-		this.googleClientSecret(Settings.settingsGet('GoogleClientSecret'));
-		this.googleApiKey(Settings.settingsGet('GoogleApiKey'));
-
-		this.dropboxEnable(!!Settings.settingsGet('AllowDropboxSocial'));
-		this.dropboxApiKey(Settings.settingsGet('DropboxApiKey'));
-
-		this.contactsIsAllowed(!!Settings.settingsGet('ContactsIsAllowed'));
-	};
-
-	module.exports = AbstractDataStorate;
-
-}());
\ No newline at end of file
diff --git a/dev/Storage/AbstractRemote.js b/dev/Storage/AbstractRemote.js
deleted file mode 100644
index a6c34d0cd..000000000
--- a/dev/Storage/AbstractRemote.js
+++ /dev/null
@@ -1,312 +0,0 @@
-/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
-
-(function () {
-
-	'use strict';
-
-	var
-		window = require('window'),
-		_ = require('_'),
-		$ = require('$'),
-
-		Consts = require('Common/Consts'),
-		Enums = require('Common/Enums'),
-		Globals = require('Common/Globals'),
-		Utils = require('Common/Utils'),
-		Plugins = require('Common/Plugins'),
-		LinkBuilder = require('Common/LinkBuilder'),
-
-		Settings = require('Storage/Settings')
-	;
-
-	/**
-	* @constructor
-	*/
-   function AbstractRemoteStorage()
-   {
-	   this.oRequests = {};
-   }
-
-   AbstractRemoteStorage.prototype.oRequests = {};
-
-   /**
-	* @param {?Function} fCallback
-	* @param {string} sRequestAction
-	* @param {string} sType
-	* @param {?AjaxJsonDefaultResponse} oData
-	* @param {boolean} bCached
-	* @param {*=} oRequestParameters
-	*/
-   AbstractRemoteStorage.prototype.defaultResponse = function (fCallback, sRequestAction, sType, oData, bCached, oRequestParameters)
-   {
-	   var
-		   fCall = function () {
-			   if (Enums.StorageResultType.Success !== sType && Globals.bUnload)
-			   {
-				   sType = Enums.StorageResultType.Unload;
-			   }
-
-			   if (Enums.StorageResultType.Success === sType && oData && !oData.Result)
-			   {
-				   if (oData && -1 < Utils.inArray(oData.ErrorCode, [
-					   Enums.Notification.AuthError, Enums.Notification.AccessError,
-					   Enums.Notification.ConnectionError, Enums.Notification.DomainNotAllowed, Enums.Notification.AccountNotAllowed,
-					   Enums.Notification.MailServerError,	Enums.Notification.UnknownNotification, Enums.Notification.UnknownError
-				   ]))
-				   {
-					   Globals.iAjaxErrorCount++;
-				   }
-
-				   if (oData && Enums.Notification.InvalidToken === oData.ErrorCode)
-				   {
-					   Globals.iTokenErrorCount++;
-				   }
-
-				   if (Consts.Values.TokenErrorLimit < Globals.iTokenErrorCount)
-				   {
-					   if (Globals.__APP__)
-					   {
-							Globals.__APP__.loginAndLogoutReload(true);
-					   }
-				   }
-
-				   if (oData.Logout || Consts.Values.AjaxErrorLimit < Globals.iAjaxErrorCount)
-				   {
-					   if (window.__rlah_clear)
-					   {
-						   window.__rlah_clear();
-					   }
-
-					   if (Globals.__APP__)
-					   {
-							Globals.__APP__.loginAndLogoutReload(true);
-					   }
-				   }
-			   }
-			   else if (Enums.StorageResultType.Success === sType && oData && oData.Result)
-			   {
-				   Globals.iAjaxErrorCount = 0;
-				   Globals.iTokenErrorCount = 0;
-			   }
-
-			   if (fCallback)
-			   {
-				   Plugins.runHook('ajax-default-response', [sRequestAction, Enums.StorageResultType.Success === sType ? oData : null, sType, bCached, oRequestParameters]);
-
-				   fCallback(
-					   sType,
-					   Enums.StorageResultType.Success === sType ? oData : null,
-					   bCached,
-					   sRequestAction,
-					   oRequestParameters
-				   );
-			   }
-		   }
-	   ;
-
-	   switch (sType)
-	   {
-		   case 'success':
-			   sType = Enums.StorageResultType.Success;
-			   break;
-		   case 'abort':
-			   sType = Enums.StorageResultType.Abort;
-			   break;
-		   default:
-			   sType = Enums.StorageResultType.Error;
-			   break;
-	   }
-
-	   if (Enums.StorageResultType.Error === sType)
-	   {
-		   _.delay(fCall, 300);
-	   }
-	   else
-	   {
-		   fCall();
-	   }
-   };
-
-   /**
-	* @param {?Function} fResultCallback
-	* @param {Object} oParameters
-	* @param {?number=} iTimeOut = 20000
-	* @param {string=} sGetAdd = ''
-	* @param {Array=} aAbortActions = []
-	* @return {jQuery.jqXHR}
-	*/
-   AbstractRemoteStorage.prototype.ajaxRequest = function (fResultCallback, oParameters, iTimeOut, sGetAdd, aAbortActions)
-   {
-	   var
-		   self = this,
-		   bPost = '' === sGetAdd,
-		   oHeaders = {},
-		   iStart = (new window.Date()).getTime(),
-		   oDefAjax = null,
-		   sAction = ''
-	   ;
-
-	   oParameters = oParameters || {};
-	   iTimeOut = Utils.isNormal(iTimeOut) ? iTimeOut : 20000;
-	   sGetAdd = Utils.isUnd(sGetAdd) ? '' : Utils.pString(sGetAdd);
-	   aAbortActions = Utils.isArray(aAbortActions) ? aAbortActions : [];
-
-	   sAction = oParameters.Action || '';
-
-	   if (sAction && 0 < aAbortActions.length)
-	   {
-		   _.each(aAbortActions, function (sActionToAbort) {
-			   if (self.oRequests[sActionToAbort])
-			   {
-				   self.oRequests[sActionToAbort].__aborted = true;
-				   if (self.oRequests[sActionToAbort].abort)
-				   {
-					   self.oRequests[sActionToAbort].abort();
-				   }
-				   self.oRequests[sActionToAbort] = null;
-			   }
-		   });
-	   }
-
-	   if (bPost)
-	   {
-		   oParameters['XToken'] = Settings.settingsGet('Token');
-	   }
-
-	   oDefAjax = $.ajax({
-		   'type': bPost ? 'POST' : 'GET',
-		   'url': LinkBuilder.ajax(sGetAdd),
-		   'async': true,
-		   'dataType': 'json',
-		   'data': bPost ? oParameters : {},
-		   'headers': oHeaders,
-		   'timeout': iTimeOut,
-		   'global': true
-	   });
-
-	   oDefAjax.always(function (oData, sType) {
-
-		   var bCached = false;
-		   if (oData && oData['Time'])
-		   {
-			   bCached = Utils.pInt(oData['Time']) > (new window.Date()).getTime() - iStart;
-		   }
-
-		   if (sAction && self.oRequests[sAction])
-		   {
-			   if (self.oRequests[sAction].__aborted)
-			   {
-				   sType = 'abort';
-			   }
-
-			   self.oRequests[sAction] = null;
-		   }
-
-		   self.defaultResponse(fResultCallback, sAction, sType, oData, bCached, oParameters);
-	   });
-
-	   if (sAction && 0 < aAbortActions.length && -1 < Utils.inArray(sAction, aAbortActions))
-	   {
-		   if (this.oRequests[sAction])
-		   {
-			   this.oRequests[sAction].__aborted = true;
-			   if (this.oRequests[sAction].abort)
-			   {
-				   this.oRequests[sAction].abort();
-			   }
-			   this.oRequests[sAction] = null;
-		   }
-
-		   this.oRequests[sAction] = oDefAjax;
-	   }
-
-	   return oDefAjax;
-   };
-
-   /**
-	* @param {?Function} fCallback
-	* @param {string} sAction
-	* @param {Object=} oParameters
-	* @param {?number=} iTimeout
-	* @param {string=} sGetAdd = ''
-	* @param {Array=} aAbortActions = []
-	*/
-   AbstractRemoteStorage.prototype.defaultRequest = function (fCallback, sAction, oParameters, iTimeout, sGetAdd, aAbortActions)
-   {
-	   oParameters = oParameters || {};
-	   oParameters.Action = sAction;
-
-	   sGetAdd = Utils.pString(sGetAdd);
-
-	   Plugins.runHook('ajax-default-request', [sAction, oParameters, sGetAdd]);
-
-	   this.ajaxRequest(fCallback, oParameters,
-		   Utils.isUnd(iTimeout) ? Consts.Defaults.DefaultAjaxTimeout : Utils.pInt(iTimeout), sGetAdd, aAbortActions);
-   };
-
-   /**
-	* @param {?Function} fCallback
-	*/
-   AbstractRemoteStorage.prototype.noop = function (fCallback)
-   {
-	   this.defaultRequest(fCallback, 'Noop');
-   };
-
-   /**
-	* @param {?Function} fCallback
-	* @param {string} sMessage
-	* @param {string} sFileName
-	* @param {number} iLineNo
-	* @param {string} sLocation
-	* @param {string} sHtmlCapa
-	* @param {number} iTime
-	*/
-   AbstractRemoteStorage.prototype.jsError = function (fCallback, sMessage, sFileName, iLineNo, sLocation, sHtmlCapa, iTime)
-   {
-	   this.defaultRequest(fCallback, 'JsError', {
-		   'Message': sMessage,
-		   'FileName': sFileName,
-		   'LineNo': iLineNo,
-		   'Location': sLocation,
-		   'HtmlCapa': sHtmlCapa,
-		   'TimeOnPage': iTime
-	   });
-   };
-
-   /**
-	* @param {?Function} fCallback
-	* @param {string} sType
-	* @param {Array=} mData = null
-	* @param {boolean=} bIsError = false
-	*/
-   AbstractRemoteStorage.prototype.jsInfo = function (fCallback, sType, mData, bIsError)
-   {
-	   this.defaultRequest(fCallback, 'JsInfo', {
-		   'Type': sType,
-		   'Data': mData,
-		   'IsError': (Utils.isUnd(bIsError) ? false : !!bIsError) ? '1' : '0'
-	   });
-   };
-
-   /**
-	* @param {?Function} fCallback
-	*/
-   AbstractRemoteStorage.prototype.getPublicKey = function (fCallback)
-   {
-	   this.defaultRequest(fCallback, 'GetPublicKey');
-   };
-
-   /**
-	* @param {?Function} fCallback
-	* @param {string} sVersion
-	*/
-   AbstractRemoteStorage.prototype.jsVersion = function (fCallback, sVersion)
-   {
-	   this.defaultRequest(fCallback, 'Version', {
-		   'Version': sVersion
-	   });
-   };
-
-	module.exports = AbstractRemoteStorage;
-
-}());
\ No newline at end of file
diff --git a/dev/Storage/Admin/Data.js b/dev/Storage/Admin/Data.js
deleted file mode 100644
index 4c1789d3c..000000000
--- a/dev/Storage/Admin/Data.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-		ko = require('ko'),
-
-		AbstractData = require('Storage/AbstractData')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractData
-	 */
-	function DataAdminStorage()
-	{
-		AbstractData.call(this);
-
-		this.domainsLoading = ko.observable(false).extend({'throttle': 100});
-		this.domains = ko.observableArray([]);
-
-		this.pluginsLoading = ko.observable(false).extend({'throttle': 100});
-		this.plugins = ko.observableArray([]);
-
-		this.packagesReal = ko.observable(true);
-		this.packagesMainUpdatable = ko.observable(true);
-		this.packagesLoading = ko.observable(false).extend({'throttle': 100});
-		this.packages = ko.observableArray([]);
-
-		this.coreReal = ko.observable(true);
-		this.coreUpdatable = ko.observable(true);
-		this.coreAccess = ko.observable(true);
-		this.coreChecking = ko.observable(false).extend({'throttle': 100});
-		this.coreUpdating = ko.observable(false).extend({'throttle': 100});
-		this.coreRemoteVersion = ko.observable('');
-		this.coreRemoteRelease = ko.observable('');
-		this.coreVersionCompare = ko.observable(-2);
-
-		this.licensing = ko.observable(false);
-		this.licensingProcess = ko.observable(false);
-		this.licenseValid = ko.observable(false);
-		this.licenseExpired = ko.observable(0);
-		this.licenseError = ko.observable('');
-
-		this.licenseTrigger = ko.observable(false);
-
-		this.adminManLoading = ko.computed(function () {
-			return '000' !== [this.domainsLoading() ? '1' : '0', this.pluginsLoading() ? '1' : '0', this.packagesLoading() ? '1' : '0'].join('');
-		}, this);
-
-		this.adminManLoadingVisibility = ko.computed(function () {
-			return this.adminManLoading() ? 'visible' : 'hidden';
-		}, this).extend({'rateLimit': 300});
-	}
-
-	_.extend(DataAdminStorage.prototype, AbstractData.prototype);
-
-	DataAdminStorage.prototype.populateDataOnStart = function()
-	{
-		AbstractData.prototype.populateDataOnStart.call(this);
-	};
-
-	module.exports = new DataAdminStorage();
-
-}());
\ No newline at end of file
diff --git a/dev/Storage/App/Data.js b/dev/Storage/App/Data.js
deleted file mode 100644
index 90b660a9e..000000000
--- a/dev/Storage/App/Data.js
+++ /dev/null
@@ -1,1062 +0,0 @@
-/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
-
-(function () {
-
-	'use strict';
-
-	var
-		window = require('window'),
-		_ = require('_'),
-		$ = require('$'),
-		ko = require('ko'),
-		moment = require('moment'),
-
-		Consts = require('Common/Consts'),
-		Enums = require('Common/Enums'),
-		Globals = require('Common/Globals'),
-		Utils = require('Common/Utils'),
-		LinkBuilder = require('Common/LinkBuilder'),
-
-		Settings = require('Storage/Settings'),
-		Cache = require('Storage/App/Cache'),
-
-		kn = require('Knoin/Knoin'),
-
-		MessageModel = require('Model/Message'),
-
-		Local = require('Storage/Local'),
-		AbstractData = require('Storage/AbstractData')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractData
-	 */
-	function DataAppStorage()
-	{
-		AbstractData.call(this);
-
-		var
-			fRemoveSystemFolderType = function (observable) {
-				return function () {
-					var oFolder = Cache.getFolderFromCacheList(observable());
-					if (oFolder)
-					{
-						oFolder.type(Enums.FolderType.User);
-					}
-				};
-			},
-			fSetSystemFolderType = function (iType) {
-				return function (sValue) {
-					var oFolder = Cache.getFolderFromCacheList(sValue);
-					if (oFolder)
-					{
-						oFolder.type(iType);
-					}
-				};
-			}
-		;
-
-		this.devEmail = '';
-		this.devPassword = '';
-
-		this.accountEmail = ko.observable('');
-		this.accountIncLogin = ko.observable('');
-		this.accountOutLogin = ko.observable('');
-		this.projectHash = ko.observable('');
-		this.threading = ko.observable(false);
-
-		this.lastFoldersHash = '';
-		this.remoteSuggestions = false;
-
-		// system folders
-		this.sentFolder = ko.observable('');
-		this.draftFolder = ko.observable('');
-		this.spamFolder = ko.observable('');
-		this.trashFolder = ko.observable('');
-		this.archiveFolder = ko.observable('');
-
-		this.sentFolder.subscribe(fRemoveSystemFolderType(this.sentFolder), this, 'beforeChange');
-		this.draftFolder.subscribe(fRemoveSystemFolderType(this.draftFolder), this, 'beforeChange');
-		this.spamFolder.subscribe(fRemoveSystemFolderType(this.spamFolder), this, 'beforeChange');
-		this.trashFolder.subscribe(fRemoveSystemFolderType(this.trashFolder), this, 'beforeChange');
-		this.archiveFolder.subscribe(fRemoveSystemFolderType(this.archiveFolder), this, 'beforeChange');
-
-		this.sentFolder.subscribe(fSetSystemFolderType(Enums.FolderType.SentItems), this);
-		this.draftFolder.subscribe(fSetSystemFolderType(Enums.FolderType.Draft), this);
-		this.spamFolder.subscribe(fSetSystemFolderType(Enums.FolderType.Spam), this);
-		this.trashFolder.subscribe(fSetSystemFolderType(Enums.FolderType.Trash), this);
-		this.archiveFolder.subscribe(fSetSystemFolderType(Enums.FolderType.Archive), this);
-
-		this.draftFolderNotEnabled = ko.computed(function () {
-			return '' === this.draftFolder() || Consts.Values.UnuseOptionValue === this.draftFolder();
-		}, this);
-
-		// personal
-		this.displayName = ko.observable('');
-		this.signature = ko.observable('');
-		this.signatureToAll = ko.observable(false);
-		this.replyTo = ko.observable('');
-
-		// security
-		this.enableTwoFactor = ko.observable(false);
-
-		// accounts
-		this.accounts = ko.observableArray([]);
-		this.accountsLoading = ko.observable(false).extend({'throttle': 100});
-
-		// identities
-		this.defaultIdentityID = ko.observable('');
-		this.identities = ko.observableArray([]);
-		this.identitiesLoading = ko.observable(false).extend({'throttle': 100});
-
-		// contacts
-		this.contactTags = ko.observableArray([]);
-		this.contacts = ko.observableArray([]);
-		this.contacts.loading = ko.observable(false).extend({'throttle': 200});
-		this.contacts.importing = ko.observable(false).extend({'throttle': 200});
-		this.contacts.syncing = ko.observable(false).extend({'throttle': 200});
-		this.contacts.exportingVcf = ko.observable(false).extend({'throttle': 200});
-		this.contacts.exportingCsv = ko.observable(false).extend({'throttle': 200});
-
-		this.allowContactsSync = ko.observable(false);
-		this.enableContactsSync = ko.observable(false);
-		this.contactsSyncUrl = ko.observable('');
-		this.contactsSyncUser = ko.observable('');
-		this.contactsSyncPass = ko.observable('');
-
-		this.allowContactsSync = ko.observable(!!Settings.settingsGet('ContactsSyncIsAllowed'));
-		this.enableContactsSync = ko.observable(!!Settings.settingsGet('EnableContactsSync'));
-		this.contactsSyncUrl = ko.observable(Settings.settingsGet('ContactsSyncUrl'));
-		this.contactsSyncUser = ko.observable(Settings.settingsGet('ContactsSyncUser'));
-		this.contactsSyncPass = ko.observable(Settings.settingsGet('ContactsSyncPassword'));
-
-		// folders
-		this.namespace = '';
-		this.folderList = ko.observableArray([]);
-		this.folderList.focused = ko.observable(false);
-
-		this.foldersListError = ko.observable('');
-
-		this.foldersLoading = ko.observable(false);
-		this.foldersCreating = ko.observable(false);
-		this.foldersDeleting = ko.observable(false);
-		this.foldersRenaming = ko.observable(false);
-
-		this.foldersChanging = ko.computed(function () {
-			var
-				bLoading = this.foldersLoading(),
-				bCreating = this.foldersCreating(),
-				bDeleting = this.foldersDeleting(),
-				bRenaming = this.foldersRenaming()
-			;
-			return bLoading || bCreating || bDeleting || bRenaming;
-		}, this);
-
-		this.foldersInboxUnreadCount = ko.observable(0);
-
-		this.currentFolder = ko.observable(null).extend({'toggleSubscribe': [null,
-			function (oPrev) {
-				if (oPrev)
-				{
-					oPrev.selected(false);
-				}
-			}, function (oNext) {
-				if (oNext)
-				{
-					oNext.selected(true);
-				}
-			}
-		]});
-
-		this.currentFolderFullNameRaw = ko.computed(function () {
-			return this.currentFolder() ? this.currentFolder().fullNameRaw : '';
-		}, this);
-
-		this.currentFolderFullName = ko.computed(function () {
-			return this.currentFolder() ? this.currentFolder().fullName : '';
-		}, this);
-
-		this.currentFolderFullNameHash = ko.computed(function () {
-			return this.currentFolder() ? this.currentFolder().fullNameHash : '';
-		}, this);
-
-		this.currentFolderName = ko.computed(function () {
-			return this.currentFolder() ? this.currentFolder().name() : '';
-		}, this);
-
-		this.folderListSystemNames = ko.computed(function () {
-
-			var
-				aList = ['INBOX'],
-				aFolders = this.folderList(),
-				sSentFolder = this.sentFolder(),
-				sDraftFolder = this.draftFolder(),
-				sSpamFolder = this.spamFolder(),
-				sTrashFolder = this.trashFolder(),
-				sArchiveFolder = this.archiveFolder()
-			;
-
-			if (Utils.isArray(aFolders) && 0 < aFolders.length)
-			{
-				if ('' !== sSentFolder && Consts.Values.UnuseOptionValue !== sSentFolder)
-				{
-					aList.push(sSentFolder);
-				}
-				if ('' !== sDraftFolder && Consts.Values.UnuseOptionValue !== sDraftFolder)
-				{
-					aList.push(sDraftFolder);
-				}
-				if ('' !== sSpamFolder && Consts.Values.UnuseOptionValue !== sSpamFolder)
-				{
-					aList.push(sSpamFolder);
-				}
-				if ('' !== sTrashFolder && Consts.Values.UnuseOptionValue !== sTrashFolder)
-				{
-					aList.push(sTrashFolder);
-				}
-				if ('' !== sArchiveFolder && Consts.Values.UnuseOptionValue !== sArchiveFolder)
-				{
-					aList.push(sArchiveFolder);
-				}
-			}
-
-			return aList;
-
-		}, this);
-
-		this.folderListSystem = ko.computed(function () {
-			return _.compact(_.map(this.folderListSystemNames(), function (sName) {
-				return Cache.getFolderFromCacheList(sName);
-			}));
-		}, this);
-
-		this.folderMenuForMove = ko.computed(function () {
-			return Utils.folderListOptionsBuilder(this.folderListSystem(), this.folderList(), [
-				this.currentFolderFullNameRaw()
-			], null, null, null, null, function (oItem) {
-				return oItem ? oItem.localName() : '';
-			});
-		}, this);
-
-		// message list
-		this.staticMessageList = [];
-
-		this.messageList = ko.observableArray([]).extend({'rateLimit': 0});
-
-		this.messageListCount = ko.observable(0);
-		this.messageListSearch = ko.observable('');
-		this.messageListPage = ko.observable(1);
-
-		this.messageListThreadFolder = ko.observable('');
-		this.messageListThreadUids = ko.observableArray([]);
-
-		this.messageListThreadFolder.subscribe(function () {
-			this.messageListThreadUids([]);
-		}, this);
-
-		this.messageListEndFolder = ko.observable('');
-		this.messageListEndSearch = ko.observable('');
-		this.messageListEndPage = ko.observable(1);
-
-		this.messageListEndHash = ko.computed(function () {
-			return this.messageListEndFolder() + '|' + this.messageListEndSearch() + '|' + this.messageListEndPage();
-		}, this);
-
-		this.messageListPageCount = ko.computed(function () {
-			var iPage = window.Math.ceil(this.messageListCount() / this.messagesPerPage());
-			return 0 >= iPage ? 1 : iPage;
-		}, this);
-
-		this.mainMessageListSearch = ko.computed({
-			'read': this.messageListSearch,
-			'write': function (sValue) {
-				kn.setHash(LinkBuilder.mailBox(
-					this.currentFolderFullNameHash(), 1, Utils.trim(sValue.toString())
-				));
-			},
-			'owner': this
-		});
-
-		this.messageListError = ko.observable('');
-
-		this.messageListLoading = ko.observable(false);
-		this.messageListIsNotCompleted = ko.observable(false);
-		this.messageListCompleteLoadingThrottle = ko.observable(false).extend({'throttle': 200});
-
-		this.messageListCompleteLoading = ko.computed(function () {
-			var
-				bOne = this.messageListLoading(),
-				bTwo = this.messageListIsNotCompleted()
-			;
-			return bOne || bTwo;
-		}, this);
-
-		this.messageListCompleteLoading.subscribe(function (bValue) {
-			this.messageListCompleteLoadingThrottle(bValue);
-		}, this);
-
-		this.messageList.subscribe(_.debounce(function (aList) {
-			_.each(aList, function (oItem) {
-				if (oItem.newForAnimation())
-				{
-					oItem.newForAnimation(false);
-				}
-			});
-		}, 500));
-
-		// message preview
-		this.staticMessageList = new MessageModel();
-		this.message = ko.observable(null);
-		this.messageLoading = ko.observable(false);
-		this.messageLoadingThrottle = ko.observable(false).extend({'throttle': 50});
-
-		this.message.focused = ko.observable(false);
-
-		this.message.subscribe(function (oMessage) {
-			if (!oMessage)
-			{
-				this.message.focused(false);
-				this.messageFullScreenMode(false);
-				this.hideMessageBodies();
-
-				if (Enums.Layout.NoPreview === this.layout() &&
-					-1 < window.location.hash.indexOf('message-preview'))
-				{
-					require('App/App').historyBack();
-				}
-			}
-			else if (Enums.Layout.NoPreview === this.layout())
-			{
-				this.message.focused(true);
-			}
-		}, this);
-
-		this.message.focused.subscribe(function (bValue) {
-			if (bValue)
-			{
-				this.folderList.focused(false);
-				Globals.keyScope(Enums.KeyState.MessageView);
-			}
-			else if (Enums.KeyState.MessageView === Globals.keyScope())
-			{
-				if (Enums.Layout.NoPreview === this.layout() && this.message())
-				{
-					Globals.keyScope(Enums.KeyState.MessageView);
-				}
-				else
-				{
-					Globals.keyScope(Enums.KeyState.MessageList);
-				}
-			}
-		}, this);
-
-		this.folderList.focused.subscribe(function (bValue) {
-			if (bValue)
-			{
-				Globals.keyScope(Enums.KeyState.FolderList);
-			}
-			else if (Enums.KeyState.FolderList === Globals.keyScope())
-			{
-				Globals.keyScope(Enums.KeyState.MessageList);
-			}
-		});
-
-		this.messageLoading.subscribe(function (bValue) {
-			this.messageLoadingThrottle(bValue);
-		}, this);
-
-		this.messageFullScreenMode = ko.observable(false);
-
-		this.messageError = ko.observable('');
-
-		this.messagesBodiesDom = ko.observable(null);
-
-		this.messagesBodiesDom.subscribe(function (oDom) {
-			if (oDom && !(oDom instanceof $))
-			{
-				this.messagesBodiesDom($(oDom));
-			}
-		}, this);
-
-		this.messageActiveDom = ko.observable(null);
-
-		this.isMessageSelected = ko.computed(function () {
-			return null !== this.message();
-		}, this);
-
-		this.currentMessage = ko.observable(null);
-
-		this.messageListChecked = ko.computed(function () {
-			return _.filter(this.messageList(), function (oItem) {
-				return oItem.checked();
-			});
-		}, this).extend({'rateLimit': 0});
-
-		this.hasCheckedMessages = ko.computed(function () {
-			return 0 < this.messageListChecked().length;
-		}, this).extend({'rateLimit': 0});
-
-		this.messageListCheckedOrSelected = ko.computed(function () {
-
-			var
-				aChecked = this.messageListChecked(),
-				oSelectedMessage = this.currentMessage()
-			;
-
-			return _.union(aChecked, oSelectedMessage ? [oSelectedMessage] : []);
-
-		}, this);
-
-		this.messageListCheckedOrSelectedUidsWithSubMails = ko.computed(function () {
-			var aList = [];
-			_.each(this.messageListCheckedOrSelected(), function (oMessage) {
-				if (oMessage)
-				{
-					aList.push(oMessage.uid);
-					if (0 < oMessage.threadsLen() && 0 === oMessage.parentUid() && oMessage.lastInCollapsedThread())
-					{
-						aList = _.union(aList, oMessage.threads());
-					}
-				}
-			});
-			return aList;
-		}, this);
-
-		// quota
-		this.userQuota = ko.observable(0);
-		this.userUsageSize = ko.observable(0);
-		this.userUsageProc = ko.computed(function () {
-
-			var
-				iQuota = this.userQuota(),
-				iUsed = this.userUsageSize()
-			;
-
-			return 0 < iQuota ? window.Math.ceil((iUsed / iQuota) * 100) : 0;
-
-		}, this);
-
-		// other
-		this.capaOpenPGP = ko.observable(false);
-		this.openpgpkeys = ko.observableArray([]);
-		this.openpgp = null;
-		this.openpgpKeyring = null;
-
-		this.openpgpkeysPublic = this.openpgpkeys.filter(function (oItem) {
-			return !!(oItem && !oItem.isPrivate);
-		});
-
-		this.openpgpkeysPrivate = this.openpgpkeys.filter(function (oItem) {
-			return !!(oItem && oItem.isPrivate);
-		});
-
-		// google
-		this.googleActions = ko.observable(false);
-		this.googleLoggined = ko.observable(false);
-		this.googleUserName = ko.observable('');
-
-		// facebook
-		this.facebookActions = ko.observable(false);
-		this.facebookLoggined = ko.observable(false);
-		this.facebookUserName = ko.observable('');
-
-		// twitter
-		this.twitterActions = ko.observable(false);
-		this.twitterLoggined = ko.observable(false);
-		this.twitterUserName = ko.observable('');
-
-		this.customThemeType = ko.observable(Enums.CustomThemeType.Light);
-
-		this.purgeMessageBodyCacheThrottle = _.throttle(this.purgeMessageBodyCache, 1000 * 30);
-	}
-
-	_.extend(DataAppStorage.prototype, AbstractData.prototype);
-
-	DataAppStorage.prototype.purgeMessageBodyCache = function()
-	{
-		var
-			iCount = 0,
-			oMessagesBodiesDom = null,
-			iEnd = Globals.iMessageBodyCacheCount - Consts.Values.MessageBodyCacheLimit
-		;
-
-		if (0 < iEnd)
-		{
-			oMessagesBodiesDom = this.messagesBodiesDom();
-			if (oMessagesBodiesDom)
-			{
-				oMessagesBodiesDom.find('.rl-cache-class').each(function () {
-					var oItem = $(this);
-					if (iEnd > oItem.data('rl-cache-count'))
-					{
-						oItem.addClass('rl-cache-purge');
-						iCount++;
-					}
-				});
-
-				if (0 < iCount)
-				{
-					_.delay(function () {
-						oMessagesBodiesDom.find('.rl-cache-purge').remove();
-					}, 300);
-				}
-			}
-		}
-	};
-
-	DataAppStorage.prototype.populateDataOnStart = function()
-	{
-		AbstractData.prototype.populateDataOnStart.call(this);
-
-		this.accountEmail(Settings.settingsGet('Email'));
-		this.accountIncLogin(Settings.settingsGet('IncLogin'));
-		this.accountOutLogin(Settings.settingsGet('OutLogin'));
-		this.projectHash(Settings.settingsGet('ProjectHash'));
-
-		this.defaultIdentityID(Settings.settingsGet('DefaultIdentityID'));
-
-		this.displayName(Settings.settingsGet('DisplayName'));
-		this.replyTo(Settings.settingsGet('ReplyTo'));
-		this.signature(Settings.settingsGet('Signature'));
-		this.signatureToAll(!!Settings.settingsGet('SignatureToAll'));
-		this.enableTwoFactor(!!Settings.settingsGet('EnableTwoFactor'));
-
-		this.lastFoldersHash = Local.get(Enums.ClientSideKeyName.FoldersLashHash) || '';
-
-		this.remoteSuggestions = !!Settings.settingsGet('RemoteSuggestions');
-
-		this.devEmail = Settings.settingsGet('DevEmail');
-		this.devPassword = Settings.settingsGet('DevPassword');
-	};
-
-	DataAppStorage.prototype.initUidNextAndNewMessages = function (sFolder, sUidNext, aNewMessages)
-	{
-		if ('INBOX' === sFolder && Utils.isNormal(sUidNext) && sUidNext !== '')
-		{
-			if (Utils.isArray(aNewMessages) && 0 < aNewMessages.length)
-			{
-				var
-					self = this,
-					iIndex = 0,
-					iLen = aNewMessages.length,
-					fNotificationHelper = function (sImageSrc, sTitle, sText)
-					{
-						var
-							NotificationClass = Utils.notificationClass(),
-							oNotification = null
-						;
-
-						if (NotificationClass && self.useDesktopNotifications())
-						{
-							oNotification = new NotificationClass(sTitle, {
-								'body': sText,
-								'icon': sImageSrc
-							});
-
-							if (oNotification)
-							{
-								if (oNotification.show)
-								{
-									oNotification.show();
-								}
-
-								window.setTimeout((function (oLocalNotifications) {
-									return function () {
-										if (oLocalNotifications.cancel)
-										{
-											oLocalNotifications.cancel();
-										}
-										else if (oLocalNotifications.close)
-										{
-											oLocalNotifications.close();
-										}
-									};
-								}(oNotification)), 7000);
-							}
-						}
-					}
-				;
-
-				_.each(aNewMessages, function (oItem) {
-					Cache.addNewMessageCache(sFolder, oItem.Uid);
-				});
-
-				if (3 < iLen)
-				{
-					fNotificationHelper(
-						LinkBuilder.notificationMailIcon(),
-						this.accountEmail(),
-						Utils.i18n('MESSAGE_LIST/NEW_MESSAGE_NOTIFICATION', {
-							'COUNT': iLen
-						})
-					);
-				}
-				else
-				{
-					for (; iIndex < iLen; iIndex++)
-					{
-						fNotificationHelper(
-							LinkBuilder.notificationMailIcon(),
-							MessageModel.emailsToLine(MessageModel.initEmailsFromJson(aNewMessages[iIndex].From), false),
-							aNewMessages[iIndex].Subject
-						);
-					}
-				}
-			}
-
-			Cache.setFolderUidNext(sFolder, sUidNext);
-		}
-	};
-
-	DataAppStorage.prototype.hideMessageBodies = function ()
-	{
-		var oMessagesBodiesDom = this.messagesBodiesDom();
-		if (oMessagesBodiesDom)
-		{
-			oMessagesBodiesDom.find('.b-text-part').hide();
-		}
-	};
-
-	/**
-	 * @param {boolean=} bBoot = false
-	 * @returns {Array}
-	 */
-	DataAppStorage.prototype.getNextFolderNames = function (bBoot)
-	{
-		bBoot = Utils.isUnd(bBoot) ? false : !!bBoot;
-
-		var
-			aResult = [],
-			iLimit = 10,
-			iUtc = moment().unix(),
-			iTimeout = iUtc - 60 * 5,
-			aTimeouts = [],
-			fSearchFunction = function (aList) {
-				_.each(aList, function (oFolder) {
-					if (oFolder && 'INBOX' !== oFolder.fullNameRaw &&
-						oFolder.selectable && oFolder.existen &&
-						iTimeout > oFolder.interval &&
-						(!bBoot || oFolder.subScribed()))
-					{
-						aTimeouts.push([oFolder.interval, oFolder.fullNameRaw]);
-					}
-
-					if (oFolder && 0 < oFolder.subFolders().length)
-					{
-						fSearchFunction(oFolder.subFolders());
-					}
-				});
-			}
-		;
-
-		fSearchFunction(this.folderList());
-
-		aTimeouts.sort(function(a, b) {
-			if (a[0] < b[0])
-			{
-				return -1;
-			}
-			else if (a[0] > b[0])
-			{
-				return 1;
-			}
-
-			return 0;
-		});
-
-		_.find(aTimeouts, function (aItem) {
-			var oFolder = Cache.getFolderFromCacheList(aItem[1]);
-			if (oFolder)
-			{
-				oFolder.interval = iUtc;
-				aResult.push(aItem[1]);
-			}
-
-			return iLimit <= aResult.length;
-		});
-
-		return _.uniq(aResult);
-	};
-
-	/**
-	 * @param {string} sFromFolderFullNameRaw
-	 * @param {Array} aUidForRemove
-	 * @param {string=} sToFolderFullNameRaw = ''
-	 * @param {bCopy=} bCopy = false
-	 */
-	DataAppStorage.prototype.removeMessagesFromList = function (
-		sFromFolderFullNameRaw, aUidForRemove, sToFolderFullNameRaw, bCopy)
-	{
-		sToFolderFullNameRaw = Utils.isNormal(sToFolderFullNameRaw) ? sToFolderFullNameRaw : '';
-		bCopy = Utils.isUnd(bCopy) ? false : !!bCopy;
-
-		aUidForRemove = _.map(aUidForRemove, function (mValue) {
-			return Utils.pInt(mValue);
-		});
-
-		var
-			self = this,
-			iUnseenCount = 0,
-			aMessageList = this.messageList(),
-			oFromFolder = Cache.getFolderFromCacheList(sFromFolderFullNameRaw),
-			oToFolder = '' === sToFolderFullNameRaw ? null : Cache.getFolderFromCacheList(sToFolderFullNameRaw || ''),
-			sCurrentFolderFullNameRaw = this.currentFolderFullNameRaw(),
-			oCurrentMessage = this.message(),
-			aMessages = sCurrentFolderFullNameRaw === sFromFolderFullNameRaw ? _.filter(aMessageList, function (oMessage) {
-				return oMessage && -1 < Utils.inArray(Utils.pInt(oMessage.uid), aUidForRemove);
-			}) : []
-		;
-
-		_.each(aMessages, function (oMessage) {
-			if (oMessage && oMessage.unseen())
-			{
-				iUnseenCount++;
-			}
-		});
-
-		if (oFromFolder && !bCopy)
-		{
-			oFromFolder.messageCountAll(0 <= oFromFolder.messageCountAll() - aUidForRemove.length ?
-				oFromFolder.messageCountAll() - aUidForRemove.length : 0);
-
-			if (0 < iUnseenCount)
-			{
-				oFromFolder.messageCountUnread(0 <= oFromFolder.messageCountUnread() - iUnseenCount ?
-					oFromFolder.messageCountUnread() - iUnseenCount : 0);
-			}
-		}
-
-		if (oToFolder)
-		{
-			oToFolder.messageCountAll(oToFolder.messageCountAll() + aUidForRemove.length);
-			if (0 < iUnseenCount)
-			{
-				oToFolder.messageCountUnread(oToFolder.messageCountUnread() + iUnseenCount);
-			}
-
-			oToFolder.actionBlink(true);
-		}
-
-		if (0 < aMessages.length)
-		{
-			if (bCopy)
-			{
-				_.each(aMessages, function (oMessage) {
-					oMessage.checked(false);
-				});
-			}
-			else
-			{
-				this.messageListIsNotCompleted(true);
-
-				_.each(aMessages, function (oMessage) {
-					if (oCurrentMessage && oCurrentMessage.hash === oMessage.hash)
-					{
-						oCurrentMessage = null;
-						self.message(null);
-					}
-
-					oMessage.deleted(true);
-				});
-
-				_.delay(function () {
-					_.each(aMessages, function (oMessage) {
-						self.messageList.remove(oMessage);
-					});
-				}, 400);
-			}
-		}
-
-		if ('' !== sFromFolderFullNameRaw)
-		{
-			Cache.setFolderHash(sFromFolderFullNameRaw, '');
-		}
-
-		if ('' !== sToFolderFullNameRaw)
-		{
-			Cache.setFolderHash(sToFolderFullNameRaw, '');
-		}
-	};
-
-	/**
-	 * @param {Object} oMessageTextBody
-	 */
-	DataAppStorage.prototype.initBlockquoteSwitcher = function (oMessageTextBody)
-	{
-		if (oMessageTextBody)
-		{
-			var $oList = $('blockquote:not(.rl-bq-switcher)', oMessageTextBody).filter(function () {
-				return 0 === $(this).parent().closest('blockquote', oMessageTextBody).length;
-			});
-
-			if ($oList && 0 < $oList.length)
-			{
-				_.delay(function () {
-					$oList.each(function () {
-						var $self = $(this), iH = $self.height();
-						if (0 === iH || 150 < iH)
-						{
-							$self.addClass('rl-bq-switcher hidden-bq');
-							$('')
-								.insertBefore($self)
-								.click(function () {
-									$self.toggleClass('hidden-bq');
-									Utils.windowResize();
-								})
-								.after('
') - .before('
') - ; - } - }); - }, 100); - } - } - }; - - DataAppStorage.prototype.setMessage = function (oData, bCached) - { - var - bIsHtml = false, - bHasExternals = false, - bHasInternals = false, - oBody = null, - oTextBody = null, - sId = '', - sResultHtml = '', - bPgpSigned = false, - bPgpEncrypted = false, - oMessagesBodiesDom = this.messagesBodiesDom(), - oMessage = this.message() - ; - - if (oData && oMessage && oData.Result && 'Object/Message' === oData.Result['@Object'] && - oMessage.folderFullNameRaw === oData.Result.Folder && oMessage.uid === oData.Result.Uid) - { - this.messageError(''); - - oMessage.initUpdateByMessageJson(oData.Result); - Cache.addRequestedMessage(oMessage.folderFullNameRaw, oMessage.uid); - - if (!bCached) - { - oMessage.initFlagsByJson(oData.Result); - } - - oMessagesBodiesDom = oMessagesBodiesDom && oMessagesBodiesDom[0] ? oMessagesBodiesDom : null; - if (oMessagesBodiesDom) - { - sId = 'rl-mgs-' + oMessage.hash.replace(/[^a-zA-Z0-9]/g, ''); - oTextBody = oMessagesBodiesDom.find('#' + sId); - if (!oTextBody || !oTextBody[0]) - { - bHasExternals = !!oData.Result.HasExternals; - bHasInternals = !!oData.Result.HasInternals; - - oBody = $('
').hide().addClass('rl-cache-class'); - oBody.data('rl-cache-count', ++Globals.iMessageBodyCacheCount); - - if (Utils.isNormal(oData.Result.Html) && '' !== oData.Result.Html) - { - bIsHtml = true; - sResultHtml = oData.Result.Html.toString(); - } - else if (Utils.isNormal(oData.Result.Plain) && '' !== oData.Result.Plain) - { - bIsHtml = false; - sResultHtml = Utils.plainToHtml(oData.Result.Plain.toString(), false); - - if ((oMessage.isPgpSigned() || oMessage.isPgpEncrypted()) && this.capaOpenPGP()) - { - oMessage.plainRaw = Utils.pString(oData.Result.Plain); - - bPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(oMessage.plainRaw); - if (!bPgpEncrypted) - { - bPgpSigned = /-----BEGIN PGP SIGNED MESSAGE-----/.test(oMessage.plainRaw) && - /-----BEGIN PGP SIGNATURE-----/.test(oMessage.plainRaw); - } - - Globals.$div.empty(); - if (bPgpSigned && oMessage.isPgpSigned()) - { - sResultHtml = - Globals.$div.append( - $('
').text(oMessage.plainRaw)
-									).html()
-								;
-							}
-							else if (bPgpEncrypted && oMessage.isPgpEncrypted())
-							{
-								sResultHtml =
-									Globals.$div.append(
-										$('
').text(oMessage.plainRaw)
-									).html()
-								;
-							}
-
-							Globals.$div.empty();
-
-							oMessage.isPgpSigned(bPgpSigned);
-							oMessage.isPgpEncrypted(bPgpEncrypted);
-						}
-					}
-					else
-					{
-						bIsHtml = false;
-					}
-
-					oBody
-						.html(Utils.findEmailAndLinks(sResultHtml))
-						.addClass('b-text-part ' + (bIsHtml ? 'html' : 'plain'))
-					;
-
-					oMessage.isHtml(!!bIsHtml);
-					oMessage.hasImages(!!bHasExternals);
-					oMessage.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None);
-					oMessage.pgpSignedVerifyUser('');
-
-					oMessage.body = oBody;
-					if (oMessage.body)
-					{
-						oMessagesBodiesDom.append(oMessage.body);
-					}
-
-					oMessage.storeDataToDom();
-
-					if (bHasInternals)
-					{
-						oMessage.showInternalImages(true);
-					}
-
-					if (oMessage.hasImages() && this.showImages())
-					{
-						oMessage.showExternalImages(true);
-					}
-
-					this.purgeMessageBodyCacheThrottle();
-				}
-				else
-				{
-					oMessage.body = oTextBody;
-					if (oMessage.body)
-					{
-						oMessage.body.data('rl-cache-count', ++Globals.iMessageBodyCacheCount);
-						oMessage.fetchDataToDom();
-					}
-				}
-
-				this.messageActiveDom(oMessage.body);
-
-				this.hideMessageBodies();
-				oMessage.body.show();
-
-				if (oBody)
-				{
-					this.initBlockquoteSwitcher(oBody);
-				}
-			}
-
-			Cache.initMessageFlagsFromCache(oMessage);
-			if (oMessage.unseen())
-			{
-				require('App/App').setMessageSeen(oMessage);
-			}
-
-			Utils.windowResize();
-		}
-	};
-
-	/**
-	 * @param {Array} aList
-	 * @returns {string}
-	 */
-	DataAppStorage.prototype.calculateMessageListHash = function (aList)
-	{
-		return _.map(aList, function (oMessage) {
-			return '' + oMessage.hash + '_' + oMessage.threadsLen() + '_' + oMessage.flagHash();
-		}).join('|');
-	};
-
-	DataAppStorage.prototype.findPublicKeyByHex = function (sHash)
-	{
-		return _.find(this.openpgpkeysPublic(), function (oItem) {
-			return oItem && sHash === oItem.id;
-		});
-	};
-
-	DataAppStorage.prototype.findPublicKeysByEmail = function (sEmail)
-	{
-		var self = this;
-		return _.compact(_.map(this.openpgpkeysPublic(), function (oItem) {
-
-			var oKey = null;
-			if (oItem && sEmail === oItem.email)
-			{
-				try
-				{
-					oKey = self.openpgp.key.readArmored(oItem.armor);
-					if (oKey && !oKey.err && oKey.keys && oKey.keys[0])
-					{
-						return oKey.keys[0];
-					}
-				}
-				catch (e) {}
-			}
-
-			return null;
-
-		}));
-	};
-
-	/**
-	 * @param {string} sEmail
-	 * @param {string=} sPassword
-	 * @returns {?}
-	 */
-	DataAppStorage.prototype.findPrivateKeyByEmail = function (sEmail, sPassword)
-	{
-		var
-			self = this,
-			oPrivateKey = null,
-			oKey = _.find(this.openpgpkeysPrivate(), function (oItem) {
-				return oItem && sEmail === oItem.email;
-			})
-		;
-
-		if (oKey)
-		{
-			try
-			{
-				oPrivateKey = self.openpgp.key.readArmored(oKey.armor);
-				if (oPrivateKey && !oPrivateKey.err && oPrivateKey.keys && oPrivateKey.keys[0])
-				{
-					oPrivateKey = oPrivateKey.keys[0];
-					oPrivateKey.decrypt(Utils.pString(sPassword));
-				}
-				else
-				{
-					oPrivateKey = null;
-				}
-			}
-			catch (e)
-			{
-				oPrivateKey = null;
-			}
-		}
-
-		return oPrivateKey;
-	};
-
-	/**
-	 * @param {string=} sPassword
-	 * @returns {?}
-	 */
-	DataAppStorage.prototype.findSelfPrivateKey = function (sPassword)
-	{
-		return this.findPrivateKeyByEmail(this.accountEmail(), sPassword);
-	};
-
-	module.exports = new DataAppStorage();
-
-}());
diff --git a/dev/Storage/Local.js b/dev/Storage/Client.js
similarity index 64%
rename from dev/Storage/Local.js
rename to dev/Storage/Client.js
index e5ff39955..e37adaa67 100644
--- a/dev/Storage/Local.js
+++ b/dev/Storage/Client.js
@@ -1,4 +1,3 @@
-/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
 
 (function () {
 
@@ -7,12 +6,12 @@
 	/**
 	 * @constructor
 	 */
-	function LocalStorage()
+	function ClientStorage()
 	{
 		var
 			NextStorageDriver = require('_').find([
-				require('Storage/LocalDriver/LocalStorage'),
-				require('Storage/LocalDriver/Cookie')
+				require('Common/ClientStorageDriver/LocalStorage'),
+				require('Common/ClientStorageDriver/Cookie')
 			], function (NextStorageDriver) {
 				return NextStorageDriver && NextStorageDriver.supported();
 			})
@@ -29,14 +28,14 @@
 	/**
 	 * @type {LocalStorageDriver|CookieDriver|null}
 	 */
-	LocalStorage.prototype.oDriver = null;
+	ClientStorage.prototype.oDriver = null;
 
 	/**
 	 * @param {number} iKey
 	 * @param {*} mData
 	 * @return {boolean}
 	 */
-	LocalStorage.prototype.set = function (iKey, mData)
+	ClientStorage.prototype.set = function (iKey, mData)
 	{
 		return this.oDriver ? this.oDriver.set('p' + iKey, mData) : false;
 	};
@@ -45,11 +44,11 @@
 	 * @param {number} iKey
 	 * @return {*}
 	 */
-	LocalStorage.prototype.get = function (iKey)
+	ClientStorage.prototype.get = function (iKey)
 	{
 		return this.oDriver ? this.oDriver.get('p' + iKey) : null;
 	};
 
-	module.exports = new LocalStorage();
+	module.exports = new ClientStorage();
 
 }());
\ No newline at end of file
diff --git a/dev/Storage/Settings.js b/dev/Storage/Settings.js
index 9497d3657..ef12c2a8f 100644
--- a/dev/Storage/Settings.js
+++ b/dev/Storage/Settings.js
@@ -1,4 +1,3 @@
-/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
 
 (function () {
 
@@ -6,7 +5,7 @@
 
 	var
 		window = require('window'),
-		
+
 		Utils = require('Common/Utils')
 	;
 
@@ -17,6 +16,7 @@
 	{
 		this.oSettings = window['rainloopAppData'] || {};
 		this.oSettings = Utils.isNormal(this.oSettings) ? this.oSettings : {};
+//		window.console.log(this.oSettings);
 	}
 
 	SettingsStorage.prototype.oSettings = null;
diff --git a/dev/Stores/Admin/App.js b/dev/Stores/Admin/App.js
new file mode 100644
index 000000000..75782bc0c
--- /dev/null
+++ b/dev/Stores/Admin/App.js
@@ -0,0 +1,41 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+
+		Settings = require('Storage/Settings'),
+
+		AppStore = require('Stores/App')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function AppAdminStore()
+	{
+		AppStore.call(this);
+
+		this.determineUserLanguage = ko.observable(false);
+		this.determineUserDomain = ko.observable(false);
+
+		this.weakPassword = ko.observable(false);
+		this.useLocalProxyForExternalImages = ko.observable(false);
+	}
+
+	AppAdminStore.prototype.populate = function()
+	{
+		AppStore.prototype.populate.call(this);
+
+		this.determineUserLanguage(!!Settings.settingsGet('DetermineUserLanguage'));
+		this.determineUserDomain(!!Settings.settingsGet('DetermineUserDomain'));
+
+		this.weakPassword(!!Settings.settingsGet('WeakPassword'));
+		this.useLocalProxyForExternalImages(!!Settings.settingsGet('UseLocalProxyForExternalImages'));
+	};
+
+	module.exports = new AppAdminStore();
+
+}());
diff --git a/dev/Stores/Admin/Capa.js b/dev/Stores/Admin/Capa.js
new file mode 100644
index 000000000..a2dfcf963
--- /dev/null
+++ b/dev/Stores/Admin/Capa.js
@@ -0,0 +1,49 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+
+		Enums = require('Common/Enums'),
+
+		Settings = require('Storage/Settings')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function CapaAdminStore()
+	{
+		this.additionalAccounts = ko.observable(false);
+		this.gravatar = ko.observable(false);
+		this.attachmentThumbnails = ko.observable(false);
+		this.sieve = ko.observable(false);
+		this.filters = ko.observable(false);
+		this.themes = ko.observable(true);
+		this.userBackground = ko.observable(false);
+		this.openPGP = ko.observable(false);
+		this.twoFactorAuth = ko.observable(false);
+		this.twoFactorAuthForce = ko.observable(false);
+		this.templates = ko.observable(false);
+	}
+
+	CapaAdminStore.prototype.populate = function()
+	{
+		this.additionalAccounts(Settings.capa(Enums.Capa.AdditionalAccounts));
+		this.gravatar(Settings.capa(Enums.Capa.Gravatar));
+		this.attachmentThumbnails(Settings.capa(Enums.Capa.AttachmentThumbnails));
+		this.sieve(Settings.capa(Enums.Capa.Sieve));
+		this.filters(Settings.capa(Enums.Capa.Filters));
+		this.themes(Settings.capa(Enums.Capa.Themes));
+		this.userBackground(Settings.capa(Enums.Capa.UserBackground));
+		this.openPGP(Settings.capa(Enums.Capa.OpenPGP));
+		this.twoFactorAuth(Settings.capa(Enums.Capa.TwoFactor));
+		this.twoFactorAuthForce(Settings.capa(Enums.Capa.TwoFactorForce));
+		this.templates(Settings.capa(Enums.Capa.Templates));
+	};
+
+	module.exports = new CapaAdminStore();
+
+}());
diff --git a/dev/Stores/Admin/Core.js b/dev/Stores/Admin/Core.js
new file mode 100644
index 000000000..cd1b71c3a
--- /dev/null
+++ b/dev/Stores/Admin/Core.js
@@ -0,0 +1,31 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function CoreAdminStore()
+	{
+		this.coreReal = ko.observable(true);
+		this.coreChannel = ko.observable('stable');
+		this.coreType = ko.observable('stable');
+		this.coreUpdatable = ko.observable(true);
+		this.coreAccess = ko.observable(true);
+		this.coreWarning = ko.observable(false);
+		this.coreChecking = ko.observable(false).extend({'throttle': 100});
+		this.coreUpdating = ko.observable(false).extend({'throttle': 100});
+		this.coreVersion = ko.observable('');
+		this.coreRemoteVersion = ko.observable('');
+		this.coreRemoteRelease = ko.observable('');
+		this.coreVersionCompare = ko.observable(-2);
+	}
+
+	module.exports = new CoreAdminStore();
+
+}());
diff --git a/dev/Stores/Admin/Domain.js b/dev/Stores/Admin/Domain.js
new file mode 100644
index 000000000..9ac93ef21
--- /dev/null
+++ b/dev/Stores/Admin/Domain.js
@@ -0,0 +1,21 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function DomainAdminStore()
+	{
+		this.domains = ko.observableArray([]);
+		this.domains.loading = ko.observable(false).extend({'throttle': 100});
+	}
+
+	module.exports = new DomainAdminStore();
+
+}());
diff --git a/dev/Stores/Admin/License.js b/dev/Stores/Admin/License.js
new file mode 100644
index 000000000..b2adc2a74
--- /dev/null
+++ b/dev/Stores/Admin/License.js
@@ -0,0 +1,26 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function LicenseAdminStore()
+	{
+		this.licensing = ko.observable(false);
+		this.licensingProcess = ko.observable(false);
+		this.licenseValid = ko.observable(false);
+		this.licenseExpired = ko.observable(0);
+		this.licenseError = ko.observable('');
+
+		this.licenseTrigger = ko.observable(false);
+	}
+
+	module.exports = new LicenseAdminStore();
+
+}());
diff --git a/dev/Stores/Admin/Package.js b/dev/Stores/Admin/Package.js
new file mode 100644
index 000000000..8c593d5d4
--- /dev/null
+++ b/dev/Stores/Admin/Package.js
@@ -0,0 +1,24 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function PackageAdminStore()
+	{
+		this.packages = ko.observableArray([]);
+		this.packages.loading = ko.observable(false).extend({'throttle': 100});
+
+		this.packagesReal = ko.observable(true);
+		this.packagesMainUpdatable = ko.observable(true);
+	}
+
+	module.exports = new PackageAdminStore();
+
+}());
diff --git a/dev/Stores/Admin/Plugin.js b/dev/Stores/Admin/Plugin.js
new file mode 100644
index 000000000..6bda31676
--- /dev/null
+++ b/dev/Stores/Admin/Plugin.js
@@ -0,0 +1,22 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function PluginAdminStore()
+	{
+		this.plugins = ko.observableArray([]);
+		this.plugins.loading = ko.observable(false).extend({'throttle': 100});
+		this.plugins.error = ko.observable('');
+	}
+
+	module.exports = new PluginAdminStore();
+
+}());
diff --git a/dev/Stores/App.js b/dev/Stores/App.js
new file mode 100644
index 000000000..45b560689
--- /dev/null
+++ b/dev/Stores/App.js
@@ -0,0 +1,46 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+
+		Globals = require('Common/Globals'),
+
+		Settings = require('Storage/Settings')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function AppStore()
+	{
+		this.allowLanguagesOnSettings = ko.observable(true);
+		this.allowLanguagesOnLogin = ko.observable(true);
+
+		this.interfaceAnimation = ko.observable(true);
+
+		this.interfaceAnimation.subscribe(function (bValue) {
+			var bAnim = Globals.bMobileDevice || !bValue;
+			Globals.$html.toggleClass('rl-anim', !bAnim).toggleClass('no-rl-anim', bAnim);
+		});
+
+		this.interfaceAnimation.valueHasMutated();
+
+		this.prem = ko.observable(false);
+	}
+
+	AppStore.prototype.populate = function()
+	{
+		this.allowLanguagesOnLogin(!!Settings.settingsGet('AllowLanguagesOnLogin'));
+		this.allowLanguagesOnSettings(!!Settings.settingsGet('AllowLanguagesOnSettings'));
+
+		this.interfaceAnimation(!!Settings.settingsGet('InterfaceAnimation'));
+
+		this.prem(!!Settings.settingsGet('PremType'));
+	};
+
+	module.exports = AppStore;
+
+}());
diff --git a/dev/Stores/Language.js b/dev/Stores/Language.js
new file mode 100644
index 000000000..2659f42a0
--- /dev/null
+++ b/dev/Stores/Language.js
@@ -0,0 +1,54 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+
+		Utils = require('Common/Utils'),
+
+		Settings = require('Storage/Settings')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function LanguageStore()
+	{
+		this.languages = ko.observableArray([]);
+		this.languagesAdmin = ko.observableArray([]);
+
+		this.language = ko.observable('')
+			.extend({'limitedList': this.languages});
+
+		this.userLanguage = ko.observable('')
+			.extend({'limitedList': this.languages});
+
+		this.languageAdmin = ko.observable('')
+			.extend({'limitedList': this.languagesAdmin});
+
+		this.userLanguageAdmin = ko.observable('')
+			.extend({'limitedList': this.languagesAdmin});
+	}
+
+	LanguageStore.prototype.populate = function ()
+	{
+		var
+			aLanguages = Settings.settingsGet('Languages'),
+			aLanguagesAdmin = Settings.settingsGet('LanguagesAdmin')
+		;
+
+		this.languages(Utils.isArray(aLanguages) ? aLanguages : []);
+		this.languagesAdmin(Utils.isArray(aLanguagesAdmin) ? aLanguagesAdmin : []);
+
+		this.language(Settings.settingsGet('Language'));
+		this.languageAdmin(Settings.settingsGet('LanguageAdmin'));
+
+		this.userLanguage(Settings.settingsGet('UserLanguage'));
+		this.userLanguageAdmin(Settings.settingsGet('UserLanguageAdmin'));
+	};
+
+	module.exports = new LanguageStore();
+
+}());
diff --git a/dev/Stores/Social.js b/dev/Stores/Social.js
new file mode 100644
index 000000000..0e870ce5b
--- /dev/null
+++ b/dev/Stores/Social.js
@@ -0,0 +1,109 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function SocialStore()
+	{
+		this.google = {};
+		this.twitter = {};
+		this.facebook = {};
+		this.dropbox = {};
+
+		// Google
+		this.google.enabled = ko.observable(false);
+
+		this.google.clientID = ko.observable('');
+		this.google.clientSecret = ko.observable('');
+		this.google.apiKey = ko.observable('');
+
+		this.google.loading = ko.observable(false);
+		this.google.userName = ko.observable('');
+
+		this.google.loggined = ko.computed(function () {
+			return '' !== this.google.userName();
+		}, this);
+
+		this.google.capa = {};
+		this.google.capa.auth = ko.observable(false);
+		this.google.capa.drive = ko.observable(false);
+		this.google.capa.preview = ko.observable(false);
+
+		this.google.require = {};
+		this.google.require.clientSettings = ko.computed(function () {
+			return this.google.enabled() && (this.google.capa.auth() || this.google.capa.drive());
+		}, this);
+
+		this.google.require.apiKeySettings = ko.computed(function () {
+			return this.google.enabled() && this.google.capa.drive();
+		}, this);
+
+		// Facebook
+		this.facebook.enabled = ko.observable(false);
+		this.facebook.appID = ko.observable('');
+		this.facebook.appSecret = ko.observable('');
+		this.facebook.loading = ko.observable(false);
+		this.facebook.userName = ko.observable('');
+		this.facebook.supported = ko.observable(false);
+
+		this.facebook.loggined = ko.computed(function () {
+			return '' !== this.facebook.userName();
+		}, this);
+
+		// Twitter
+		this.twitter.enabled = ko.observable(false);
+		this.twitter.consumerKey = ko.observable('');
+		this.twitter.consumerSecret = ko.observable('');
+		this.twitter.loading = ko.observable(false);
+		this.twitter.userName = ko.observable('');
+
+		this.twitter.loggined = ko.computed(function () {
+			return '' !== this.twitter.userName();
+		}, this);
+
+		// Dropbox
+		this.dropbox.enabled = ko.observable(false);
+		this.dropbox.apiKey = ko.observable('');
+	}
+
+	SocialStore.prototype.google = {};
+	SocialStore.prototype.twitter = {};
+	SocialStore.prototype.facebook = {};
+	SocialStore.prototype.dropbox = {};
+
+	SocialStore.prototype.populate = function ()
+	{
+		var Settings = require('Storage/Settings');
+
+		this.google.enabled(!!Settings.settingsGet('AllowGoogleSocial'));
+		this.google.clientID(Settings.settingsGet('GoogleClientID'));
+		this.google.clientSecret(Settings.settingsGet('GoogleClientSecret'));
+		this.google.apiKey(Settings.settingsGet('GoogleApiKey'));
+
+		this.google.capa.auth(!!Settings.settingsGet('AllowGoogleSocialAuth'));
+		this.google.capa.drive(!!Settings.settingsGet('AllowGoogleSocialDrive'));
+		this.google.capa.preview(!!Settings.settingsGet('AllowGoogleSocialPreview'));
+
+		this.facebook.enabled(!!Settings.settingsGet('AllowFacebookSocial'));
+		this.facebook.appID(Settings.settingsGet('FacebookAppID'));
+		this.facebook.appSecret(Settings.settingsGet('FacebookAppSecret'));
+		this.facebook.supported(!!Settings.settingsGet('SupportedFacebookSocial'));
+
+		this.twitter.enabled = ko.observable(!!Settings.settingsGet('AllowTwitterSocial'));
+		this.twitter.consumerKey = ko.observable(Settings.settingsGet('TwitterConsumerKey'));
+		this.twitter.consumerSecret = ko.observable(Settings.settingsGet('TwitterConsumerSecret'));
+
+		this.dropbox.enabled(!!Settings.settingsGet('AllowDropboxSocial'));
+		this.dropbox.apiKey(Settings.settingsGet('DropboxApiKey'));
+	};
+
+	module.exports = new SocialStore();
+
+}());
diff --git a/dev/Stores/Theme.js b/dev/Stores/Theme.js
new file mode 100644
index 000000000..ba459f09e
--- /dev/null
+++ b/dev/Stores/Theme.js
@@ -0,0 +1,39 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+
+		Utils = require('Common/Utils'),
+
+		Settings = require('Storage/Settings')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function ThemeStore()
+	{
+		this.themes = ko.observableArray([]);
+		this.themeBackgroundName = ko.observable('');
+		this.themeBackgroundHash = ko.observable('');
+
+		this.theme = ko.observable('')
+			.extend({'limitedList': this.themes});
+	}
+
+	ThemeStore.prototype.populate = function ()
+	{
+		var aThemes = Settings.settingsGet('Themes');
+
+		this.themes(Utils.isArray(aThemes) ? aThemes : []);
+		this.theme(Settings.settingsGet('Theme'));
+		this.themeBackgroundName(Settings.settingsGet('UserBackgroundName'));
+		this.themeBackgroundHash(Settings.settingsGet('UserBackgroundHash'));
+	};
+
+	module.exports = new ThemeStore();
+
+}());
diff --git a/dev/Stores/User/Account.js b/dev/Stores/User/Account.js
new file mode 100644
index 000000000..daf93bee0
--- /dev/null
+++ b/dev/Stores/User/Account.js
@@ -0,0 +1,71 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko'),
+
+		Settings = require('Storage/Settings')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function AccountUserStore()
+	{
+		this.email = ko.observable('');
+		this.parentEmail = ko.observable('');
+//		this.incLogin = ko.observable('');
+//		this.outLogin = ko.observable('');
+
+		this.signature = ko.observable('');
+
+		this.accounts = ko.observableArray([]);
+		this.accounts.loading = ko.observable(false).extend({'throttle': 100});
+
+		this.computers();
+	}
+
+	AccountUserStore.prototype.computers = function ()
+	{
+		this.accountsEmails = ko.computed(function () {
+			return _.compact(_.map(this.accounts(), function (oItem) {
+				return oItem ? oItem.email : null;
+			}));
+		}, this);
+
+		this.accountsUnreadCount = ko.computed(function () {
+
+			var iResult = 0;
+
+//			_.each(this.accounts(), function (oItem) {
+//				if (oItem)
+//				{
+//					iResult += oItem.count();
+//				}
+//			});
+
+			return iResult;
+
+		}, this);
+	};
+
+	AccountUserStore.prototype.populate = function ()
+	{
+		this.email(Settings.settingsGet('Email'));
+		this.parentEmail(Settings.settingsGet('ParentEmail'));
+	};
+
+	/**
+	 * @return {boolean}
+	 */
+	AccountUserStore.prototype.isRootAccount = function ()
+	{
+		return '' === this.parentEmail();
+	};
+
+	module.exports = new AccountUserStore();
+
+}());
diff --git a/dev/Stores/User/App.js b/dev/Stores/User/App.js
new file mode 100644
index 000000000..e42fb074d
--- /dev/null
+++ b/dev/Stores/User/App.js
@@ -0,0 +1,76 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+
+		Enums = require('Common/Enums'),
+		Globals = require('Common/Globals'),
+
+		Settings = require('Storage/Settings'),
+
+		AppStore = require('Stores/App')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function AppUserStore()
+	{
+		AppStore.call(this);
+
+		this.currentAudio = ko.observable('');
+
+		this.focusedState = ko.observable(Enums.Focused.None);
+
+		this.focusedState.subscribe(function (mValue) {
+
+			switch (mValue)
+			{
+				case Enums.Focused.MessageList:
+					Globals.keyScope(Enums.KeyState.MessageList);
+					break;
+				case Enums.Focused.MessageView:
+					Globals.keyScope(Enums.KeyState.MessageView);
+					break;
+				case Enums.Focused.FolderList:
+					Globals.keyScope(Enums.KeyState.FolderList);
+					break;
+			}
+
+		}, this);
+
+		this.projectHash = ko.observable('');
+		this.threadsAllowed = ko.observable(false);
+
+		this.composeInEdit = ko.observable(false);
+
+		this.contactsAutosave = ko.observable(false);
+		this.useLocalProxyForExternalImages = ko.observable(false);
+
+		this.contactsIsAllowed = ko.observable(false);
+
+		this.devEmail = '';
+		this.devPassword = '';
+	}
+
+	AppUserStore.prototype.populate = function()
+	{
+		AppStore.prototype.populate.call(this);
+
+		this.projectHash(Settings.settingsGet('ProjectHash'));
+
+		this.contactsAutosave(!!Settings.settingsGet('ContactsAutosave'));
+		this.useLocalProxyForExternalImages(!!Settings.settingsGet('UseLocalProxyForExternalImages'));
+
+		this.contactsIsAllowed(!!Settings.settingsGet('ContactsIsAllowed'));
+
+		this.devEmail = Settings.settingsGet('DevEmail');
+		this.devPassword = Settings.settingsGet('DevPassword');
+	};
+
+	module.exports = new AppUserStore();
+
+}());
diff --git a/dev/Stores/User/Contact.js b/dev/Stores/User/Contact.js
new file mode 100644
index 000000000..7af3fd388
--- /dev/null
+++ b/dev/Stores/User/Contact.js
@@ -0,0 +1,43 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko'),
+
+		Settings = require('Storage/Settings')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function ContactUserStore()
+	{
+		this.contacts = ko.observableArray([]);
+		this.contacts.loading = ko.observable(false).extend({'throttle': 200});
+		this.contacts.importing = ko.observable(false).extend({'throttle': 200});
+		this.contacts.syncing = ko.observable(false).extend({'throttle': 200});
+		this.contacts.exportingVcf = ko.observable(false).extend({'throttle': 200});
+		this.contacts.exportingCsv = ko.observable(false).extend({'throttle': 200});
+
+		this.allowContactsSync = ko.observable(false);
+		this.enableContactsSync = ko.observable(false);
+		this.contactsSyncUrl = ko.observable('');
+		this.contactsSyncUser = ko.observable('');
+		this.contactsSyncPass = ko.observable('');
+	}
+
+	ContactUserStore.prototype.populate = function ()
+	{
+		this.allowContactsSync(!!Settings.settingsGet('ContactsSyncIsAllowed'));
+		this.enableContactsSync(!!Settings.settingsGet('EnableContactsSync'));
+
+		this.contactsSyncUrl(Settings.settingsGet('ContactsSyncUrl'));
+		this.contactsSyncUser(Settings.settingsGet('ContactsSyncUser'));
+		this.contactsSyncPass(Settings.settingsGet('ContactsSyncPassword'));
+	};
+
+	module.exports = new ContactUserStore();
+
+}());
\ No newline at end of file
diff --git a/dev/Stores/User/Filter.js b/dev/Stores/User/Filter.js
new file mode 100644
index 000000000..c2a8d7a3b
--- /dev/null
+++ b/dev/Stores/User/Filter.js
@@ -0,0 +1,28 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		ko = require('ko')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function FilterUserStore()
+	{
+		this.capa = ko.observable('');
+		this.modules = ko.observable({});
+
+		this.filters = ko.observableArray([]);
+
+		this.filters.loading = ko.observable(false).extend({'throttle': 200});
+		this.filters.saving = ko.observable(false).extend({'throttle': 200});
+
+		this.raw = ko.observable('');
+	}
+
+	module.exports = new FilterUserStore();
+
+}());
diff --git a/dev/Stores/User/Folder.js b/dev/Stores/User/Folder.js
new file mode 100644
index 000000000..8b04773fe
--- /dev/null
+++ b/dev/Stores/User/Folder.js
@@ -0,0 +1,244 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko'),
+
+		Enums = require('Common/Enums'),
+		Consts = require('Common/Consts'),
+		Utils = require('Common/Utils'),
+
+		Cache = require('Common/Cache')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function FolderUserStore()
+	{
+		this.sentFolder = ko.observable('');
+		this.draftFolder = ko.observable('');
+		this.spamFolder = ko.observable('');
+		this.trashFolder = ko.observable('');
+		this.archiveFolder = ko.observable('');
+
+		this.namespace = '';
+
+		this.folderList = ko.observableArray([]);
+		this.folderList.optimized = ko.observable(false);
+		this.folderList.error = ko.observable('');
+
+		this.foldersLoading = ko.observable(false);
+		this.foldersCreating = ko.observable(false);
+		this.foldersDeleting = ko.observable(false);
+		this.foldersRenaming = ko.observable(false);
+
+		this.foldersInboxUnreadCount = ko.observable(0);
+
+		this.currentFolder = ko.observable(null).extend({'toggleSubscribe': [null,
+			function (oPrev) { if (oPrev) { oPrev.selected(false); }},
+			function (oNext) { if (oNext) { oNext.selected(true); }}
+		]});
+
+		this.computers();
+		this.subscribers();
+	}
+
+	FolderUserStore.prototype.computers = function ()
+	{
+		this.draftFolderNotEnabled = ko.computed(function () {
+			return '' === this.draftFolder() || Consts.Values.UnuseOptionValue === this.draftFolder();
+		}, this);
+
+		this.foldersListWithSingleInboxRootFolder = ko.computed(function () {
+			return !_.find(this.folderList(), function (oFolder) {
+				return oFolder && !oFolder.isSystemFolder() && oFolder.visible();
+			});
+		}, this);
+
+		this.currentFolderFullNameRaw = ko.computed(function () {
+			return this.currentFolder() ? this.currentFolder().fullNameRaw : '';
+		}, this);
+
+		this.currentFolderFullName = ko.computed(function () {
+			return this.currentFolder() ? this.currentFolder().fullName : '';
+		}, this);
+
+		this.currentFolderFullNameHash = ko.computed(function () {
+			return this.currentFolder() ? this.currentFolder().fullNameHash : '';
+		}, this);
+
+		this.foldersChanging = ko.computed(function () {
+			var
+				bLoading = this.foldersLoading(),
+				bCreating = this.foldersCreating(),
+				bDeleting = this.foldersDeleting(),
+				bRenaming = this.foldersRenaming()
+			;
+			return bLoading || bCreating || bDeleting || bRenaming;
+		}, this);
+
+		this.folderListSystemNames = ko.computed(function () {
+
+			var
+				aList = [Cache.getFolderInboxName()],
+				aFolders = this.folderList(),
+				sSentFolder = this.sentFolder(),
+				sDraftFolder = this.draftFolder(),
+				sSpamFolder = this.spamFolder(),
+				sTrashFolder = this.trashFolder(),
+				sArchiveFolder = this.archiveFolder()
+			;
+
+			if (Utils.isArray(aFolders) && 0 < aFolders.length)
+			{
+				if ('' !== sSentFolder && Consts.Values.UnuseOptionValue !== sSentFolder)
+				{
+					aList.push(sSentFolder);
+				}
+				if ('' !== sDraftFolder && Consts.Values.UnuseOptionValue !== sDraftFolder)
+				{
+					aList.push(sDraftFolder);
+				}
+				if ('' !== sSpamFolder && Consts.Values.UnuseOptionValue !== sSpamFolder)
+				{
+					aList.push(sSpamFolder);
+				}
+				if ('' !== sTrashFolder && Consts.Values.UnuseOptionValue !== sTrashFolder)
+				{
+					aList.push(sTrashFolder);
+				}
+				if ('' !== sArchiveFolder && Consts.Values.UnuseOptionValue !== sArchiveFolder)
+				{
+					aList.push(sArchiveFolder);
+				}
+			}
+
+			return aList;
+
+		}, this);
+
+		this.folderListSystem = ko.computed(function () {
+			return _.compact(_.map(this.folderListSystemNames(), function (sName) {
+				return Cache.getFolderFromCacheList(sName);
+			}));
+		}, this);
+
+		this.folderMenuForMove = ko.computed(function () {
+			return Utils.folderListOptionsBuilder(this.folderListSystem(), this.folderList(), [
+				this.currentFolderFullNameRaw()
+			], null, null, null, null, function (oItem) {
+				return oItem ? oItem.localName() : '';
+			});
+		}, this);
+
+		this.folderMenuForFilters = ko.computed(function () {
+			return Utils.folderListOptionsBuilder(this.folderListSystem(), this.folderList(),
+				['INBOX'], [['', '']], null, null, null, function (oItem) {
+					return oItem ? oItem.localName() : '';
+				}
+			);
+		}, this);
+	};
+
+	FolderUserStore.prototype.subscribers = function ()
+	{
+		var
+			fRemoveSystemFolderType = function (observable) {
+				return function () {
+					var oFolder = Cache.getFolderFromCacheList(observable());
+					if (oFolder)
+					{
+						oFolder.type(Enums.FolderType.User);
+					}
+				};
+			},
+			fSetSystemFolderType = function (iType) {
+				return function (sValue) {
+					var oFolder = Cache.getFolderFromCacheList(sValue);
+					if (oFolder)
+					{
+						oFolder.type(iType);
+					}
+				};
+			}
+		;
+
+		this.sentFolder.subscribe(fRemoveSystemFolderType(this.sentFolder), this, 'beforeChange');
+		this.draftFolder.subscribe(fRemoveSystemFolderType(this.draftFolder), this, 'beforeChange');
+		this.spamFolder.subscribe(fRemoveSystemFolderType(this.spamFolder), this, 'beforeChange');
+		this.trashFolder.subscribe(fRemoveSystemFolderType(this.trashFolder), this, 'beforeChange');
+		this.archiveFolder.subscribe(fRemoveSystemFolderType(this.archiveFolder), this, 'beforeChange');
+
+		this.sentFolder.subscribe(fSetSystemFolderType(Enums.FolderType.SentItems), this);
+		this.draftFolder.subscribe(fSetSystemFolderType(Enums.FolderType.Draft), this);
+		this.spamFolder.subscribe(fSetSystemFolderType(Enums.FolderType.Spam), this);
+		this.trashFolder.subscribe(fSetSystemFolderType(Enums.FolderType.Trash), this);
+		this.archiveFolder.subscribe(fSetSystemFolderType(Enums.FolderType.Archive), this);
+	};
+
+	/**
+	 * @param {boolean=} bBoot = false
+	 * @return {Array}
+	 */
+	FolderUserStore.prototype.getNextFolderNames = function (bBoot)
+	{
+		bBoot = Utils.isUnd(bBoot) ? false : !!bBoot;
+
+		var
+			aResult = [],
+			iLimit = 5,
+			iUtc = require('Common/Momentor').momentNowUnix(),
+			iTimeout = iUtc - 60 * 5,
+			aTimeouts = [],
+			sInboxFolderName = Cache.getFolderInboxName(),
+			fSearchFunction = function (aList) {
+				_.each(aList, function (oFolder) {
+					if (oFolder && sInboxFolderName !== oFolder.fullNameRaw &&
+						oFolder.selectable && oFolder.existen &&
+						iTimeout > oFolder.interval &&
+						(!bBoot || oFolder.subScribed()))
+					{
+						aTimeouts.push([oFolder.interval, oFolder.fullNameRaw]);
+					}
+
+					if (oFolder && 0 < oFolder.subFolders().length)
+					{
+						fSearchFunction(oFolder.subFolders());
+					}
+				});
+			}
+		;
+
+		fSearchFunction(this.folderList());
+
+		aTimeouts.sort(function(a, b) {
+			if (a[0] < b[0]) {
+				return -1;
+			} else if (a[0] > b[0]) {
+				return 1;
+			}
+
+			return 0;
+		});
+
+		_.find(aTimeouts, function (aItem) {
+			var oFolder = Cache.getFolderFromCacheList(aItem[1]);
+			if (oFolder)
+			{
+				oFolder.interval = iUtc;
+				aResult.push(aItem[1]);
+			}
+
+			return iLimit <= aResult.length;
+		});
+
+		return _.uniq(aResult);
+	};
+
+	module.exports = new FolderUserStore();
+
+}());
diff --git a/dev/Stores/User/Identity.js b/dev/Stores/User/Identity.js
new file mode 100644
index 000000000..1f0a2acc8
--- /dev/null
+++ b/dev/Stores/User/Identity.js
@@ -0,0 +1,28 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function IdentityUserStore()
+	{
+		this.identities = ko.observableArray([]);
+		this.identities.loading = ko.observable(false).extend({'throttle': 100});
+
+		this.identitiesIDS = ko.computed(function () {
+			return _.compact(_.map(this.identities(), function (oItem) {
+				return oItem ? oItem.id : null;
+			}));
+		}, this);
+	}
+
+	module.exports = new IdentityUserStore();
+
+}());
diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js
new file mode 100644
index 000000000..55c503324
--- /dev/null
+++ b/dev/Stores/User/Message.js
@@ -0,0 +1,866 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko'),
+		window = require('window'),
+		$ = require('$'),
+
+		kn = require('Knoin/Knoin'),
+
+		Enums = require('Common/Enums'),
+		Consts = require('Common/Consts'),
+		Globals = require('Common/Globals'),
+		Utils = require('Common/Utils'),
+		Links = require('Common/Links'),
+		Translator = require('Common/Translator'),
+
+		Cache = require('Common/Cache'),
+
+		AppStore = require('Stores/User/App'),
+		FolderStore = require('Stores/User/Folder'),
+		SettingsStore = require('Stores/User/Settings'),
+
+		Remote = require('Remote/User/Ajax'),
+
+		MessageModel = require('Model/Message'),
+		MessageHelper = require('Helper/Message')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function MessageUserStore()
+	{
+		this.staticMessage = new MessageModel();
+
+		this.messageList = ko.observableArray([]).extend({'rateLimit': 0});
+
+		this.messageListCount = ko.observable(0);
+		this.messageListSearch = ko.observable('');
+		this.messageListPage = ko.observable(1);
+		this.messageListError = ko.observable('');
+
+		this.messageListEndFolder = ko.observable('');
+		this.messageListEndSearch = ko.observable('');
+		this.messageListEndPage = ko.observable(1);
+
+		this.messageListLoading = ko.observable(false);
+		this.messageListIsNotCompleted = ko.observable(false);
+		this.messageListCompleteLoadingThrottle = ko.observable(false).extend({'throttle': 200});
+		this.messageListCompleteLoadingThrottleForAnimation = ko.observable(false).extend({'specialThrottle': 700});
+
+		this.messageListDisableAutoSelect = ko.observable(false).extend({'falseTimeout': 500});
+
+		this.selectorMessageSelected = ko.observable(null);
+		this.selectorMessageFocused = ko.observable(null);
+
+		// message viewer
+		this.message = ko.observable(null);
+
+		this.message.viewTrigger = ko.observable(false);
+
+		this.messageThreadList = ko.observableArray([]);
+
+		this.messageLastThreadUidsData = ko.observable(null);
+
+		this.messageError = ko.observable('');
+
+		this.messageCurrentLoading = ko.observable(false);
+		this.messageThreadLoading = ko.observable(false);
+
+		this.messageLoading = ko.computed(function () {
+			return !!(this.messageCurrentLoading() || this.messageThreadLoading());
+		}, this);
+
+		this.messageLoadingThrottle = ko.observable(false).extend({'throttle': 50});
+
+		this.messageFullScreenMode = ko.observable(false);
+
+		this.messagesBodiesDom = ko.observable(null);
+		this.messageActiveDom = ko.observable(null);
+
+		this.computers();
+		this.subscribers();
+
+		this.onMessageResponse = _.bind(this.onMessageResponse, this);
+
+		this.purgeMessageBodyCacheThrottle = _.throttle(this.purgeMessageBodyCache, 1000 * 30);
+	}
+
+	MessageUserStore.prototype.computers = function ()
+	{
+		this.messageListEndHash = ko.computed(function () {
+			return this.messageListEndFolder() + '|' + this.messageListEndSearch() + '|' + this.messageListEndPage();
+		}, this);
+
+		this.messageListPageCount = ko.computed(function () {
+			var iPage = window.Math.ceil(this.messageListCount() /
+				SettingsStore.messagesPerPage());
+			return 0 >= iPage ? 1 : iPage;
+		}, this);
+
+		this.mainMessageListSearch = ko.computed({
+			'read': this.messageListSearch,
+			'write': function (sValue) {
+				kn.setHash(Links.mailBox(
+					FolderStore.currentFolderFullNameHash(), 1, Utils.trim(sValue.toString())
+				));
+			},
+			'owner': this
+		});
+
+		this.messageListCompleteLoading = ko.computed(function () {
+			var
+				bOne = this.messageListLoading(),
+				bTwo = this.messageListIsNotCompleted()
+			;
+			return bOne || bTwo;
+		}, this);
+
+		this.isMessageSelected = ko.computed(function () {
+			return null !== this.message();
+		}, this);
+
+		this.messageListChecked = ko.computed(function () {
+			return _.filter(this.messageList(), function (oItem) {
+				return oItem.checked();
+			});
+		}, this).extend({'rateLimit': 0});
+
+		this.hasCheckedMessages = ko.computed(function () {
+			return 0 < this.messageListChecked().length;
+		}, this).extend({'rateLimit': 0});
+
+		this.messageListCheckedOrSelected = ko.computed(function () {
+
+			var
+				aChecked = this.messageListChecked(),
+				oSelectedMessage = this.selectorMessageSelected()
+			;
+
+			return _.union(aChecked, oSelectedMessage ? [oSelectedMessage] : []);
+
+		}, this);
+
+		this.messageListCheckedOrSelectedUidsWithSubMails = ko.computed(function () {
+			var aList = [];
+			_.each(this.messageListCheckedOrSelected(), function (oMessage) {
+				if (oMessage)
+				{
+					aList.push(oMessage.uid);
+					if (1 < oMessage.threadsLen())
+					{
+						aList = _.union(aList, oMessage.threads());
+					}
+				}
+			});
+			return aList;
+		}, this);
+	};
+
+	MessageUserStore.prototype.subscribers = function ()
+	{
+		this.messageListCompleteLoading.subscribe(function (bValue) {
+			bValue = !!bValue;
+			this.messageListCompleteLoadingThrottle(bValue);
+			this.messageListCompleteLoadingThrottleForAnimation(bValue);
+		}, this);
+
+		this.messageList.subscribe(_.debounce(function (aList) {
+			_.each(aList, function (oItem) {
+				if (oItem && oItem.newForAnimation())
+				{
+					oItem.newForAnimation(false);
+				}
+			});
+		}, 500));
+
+		this.message.subscribe(function (oMessage) {
+
+			if (oMessage)
+			{
+				 if (Enums.Layout.NoPreview === SettingsStore.layout())
+				{
+					AppStore.focusedState(Enums.Focused.MessageView);
+				}
+			}
+			else
+			{
+				AppStore.focusedState(Enums.Focused.MessageList);
+
+				this.messageFullScreenMode(false);
+				this.hideMessageBodies();
+			}
+
+		}, this);
+
+		this.messageLoading.subscribe(function (bValue) {
+			this.messageLoadingThrottle(bValue);
+		}, this);
+
+		this.messagesBodiesDom.subscribe(function (oDom) {
+			if (oDom && !(oDom instanceof $))
+			{
+				this.messagesBodiesDom($(oDom));
+			}
+		}, this);
+
+		this.messageListEndFolder.subscribe(function (sFolder) {
+			var oMessage = this.message();
+			if (oMessage && sFolder && sFolder !== oMessage.folderFullNameRaw)
+			{
+				this.message(null);
+			}
+		}, this);
+	};
+
+	MessageUserStore.prototype.purgeMessageBodyCache = function()
+	{
+		var
+			iCount = 0,
+			oMessagesBodiesDom = null,
+			iEnd = Globals.iMessageBodyCacheCount - Consts.Values.MessageBodyCacheLimit
+		;
+
+		if (0 < iEnd)
+		{
+			oMessagesBodiesDom = this.messagesBodiesDom();
+			if (oMessagesBodiesDom)
+			{
+				oMessagesBodiesDom.find('.rl-cache-class').each(function () {
+					var oItem = $(this);
+					if (iEnd > oItem.data('rl-cache-count'))
+					{
+						oItem.addClass('rl-cache-purge');
+						iCount++;
+					}
+				});
+
+				if (0 < iCount)
+				{
+					_.delay(function () {
+						oMessagesBodiesDom.find('.rl-cache-purge').remove();
+					}, 300);
+				}
+			}
+		}
+	};
+
+	MessageUserStore.prototype.initUidNextAndNewMessages = function (sFolder, sUidNext, aNewMessages)
+	{
+		if (Cache.getFolderInboxName() === sFolder && Utils.isNormal(sUidNext) && sUidNext !== '')
+		{
+			if (Utils.isArray(aNewMessages) && 0 < aNewMessages.length)
+			{
+				var
+					iIndex = 0,
+					iLen = aNewMessages.length,
+					NotificationStore = require('Stores/User/Notification')
+				;
+
+				_.each(aNewMessages, function (oItem) {
+					Cache.addNewMessageCache(sFolder, oItem.Uid);
+				});
+
+				NotificationStore.playSoundNotification();
+
+				if (3 < iLen)
+				{
+					NotificationStore.displayDesktopNotification(
+						Links.notificationMailIcon(),
+						require('Stores/User/Account').email(),
+						Translator.i18n('MESSAGE_LIST/NEW_MESSAGE_NOTIFICATION', {
+							'COUNT': iLen
+						})
+					);
+				}
+				else
+				{
+					for (; iIndex < iLen; iIndex++)
+					{
+						NotificationStore.displayDesktopNotification(
+							Links.notificationMailIcon(),
+							MessageHelper.emailArrayToString(
+								MessageHelper.emailArrayFromJson(aNewMessages[iIndex].From), false),
+							aNewMessages[iIndex].Subject
+						);
+					}
+				}
+			}
+
+			Cache.setFolderUidNext(sFolder, sUidNext);
+		}
+	};
+
+	MessageUserStore.prototype.hideMessageBodies = function ()
+	{
+		var oMessagesBodiesDom = this.messagesBodiesDom();
+		if (oMessagesBodiesDom)
+		{
+			oMessagesBodiesDom.find('.b-text-part').hide();
+		}
+	};
+
+	/**
+	 * @param {string} sFromFolderFullNameRaw
+	 * @param {Array} aUidForRemove
+	 * @param {string=} sToFolderFullNameRaw = ''
+	 * @param {bCopy=} bCopy = false
+	 */
+	MessageUserStore.prototype.removeMessagesFromList = function (
+		sFromFolderFullNameRaw, aUidForRemove, sToFolderFullNameRaw, bCopy)
+	{
+		sToFolderFullNameRaw = Utils.isNormal(sToFolderFullNameRaw) ? sToFolderFullNameRaw : '';
+		bCopy = Utils.isUnd(bCopy) ? false : !!bCopy;
+
+		aUidForRemove = _.map(aUidForRemove, function (mValue) {
+			return Utils.pInt(mValue);
+		});
+
+		var
+			self = this,
+			iUnseenCount = 0,
+			aMessageList = this.messageList(),
+			oFromFolder = Cache.getFolderFromCacheList(sFromFolderFullNameRaw),
+			oToFolder = '' === sToFolderFullNameRaw ? null : Cache.getFolderFromCacheList(sToFolderFullNameRaw || ''),
+			sCurrentFolderFullNameRaw = FolderStore.currentFolderFullNameRaw(),
+			oCurrentMessage = this.message(),
+			aMessages = sCurrentFolderFullNameRaw === sFromFolderFullNameRaw ? _.filter(aMessageList, function (oMessage) {
+				return oMessage && -1 < Utils.inArray(Utils.pInt(oMessage.uid), aUidForRemove);
+			}) : []
+		;
+
+		_.each(aMessages, function (oMessage) {
+			if (oMessage && oMessage.unseen())
+			{
+				iUnseenCount++;
+			}
+		});
+
+		if (oFromFolder && !bCopy)
+		{
+			oFromFolder.messageCountAll(0 <= oFromFolder.messageCountAll() - aUidForRemove.length ?
+				oFromFolder.messageCountAll() - aUidForRemove.length : 0);
+
+			if (0 < iUnseenCount)
+			{
+				oFromFolder.messageCountUnread(0 <= oFromFolder.messageCountUnread() - iUnseenCount ?
+					oFromFolder.messageCountUnread() - iUnseenCount : 0);
+			}
+		}
+
+		if (oToFolder)
+		{
+			oToFolder.messageCountAll(oToFolder.messageCountAll() + aUidForRemove.length);
+			if (0 < iUnseenCount)
+			{
+				oToFolder.messageCountUnread(oToFolder.messageCountUnread() + iUnseenCount);
+			}
+
+			oToFolder.actionBlink(true);
+		}
+
+		if (0 < aMessages.length)
+		{
+			if (bCopy)
+			{
+				_.each(aMessages, function (oMessage) {
+					oMessage.checked(false);
+				});
+			}
+			else
+			{
+				this.messageListIsNotCompleted(true);
+
+				_.each(aMessages, function (oMessage) {
+					if (oCurrentMessage && oCurrentMessage.hash === oMessage.hash)
+					{
+						oCurrentMessage = null;
+						self.message(null);
+					}
+
+					oMessage.deleted(true);
+				});
+
+				_.delay(function () {
+					_.each(aMessages, function (oMessage) {
+						self.messageList.remove(oMessage);
+					});
+				}, 400);
+			}
+		}
+
+		if ('' !== sFromFolderFullNameRaw)
+		{
+			Cache.setFolderHash(sFromFolderFullNameRaw, '');
+		}
+
+		if ('' !== sToFolderFullNameRaw)
+		{
+			Cache.setFolderHash(sToFolderFullNameRaw, '');
+		}
+	};
+
+	MessageUserStore.prototype.addBlockquoteSwitcherCallback = function ()
+	{
+		var $self = $(this);
+		if ('' !== Utils.trim(($self.text())))
+		{
+			$self.addClass('rl-bq-switcher hidden-bq');
+			$('')
+				.insertBefore($self)
+				.on('click.rlBlockquoteSwitcher', function () {
+					$self.toggleClass('hidden-bq');
+					Utils.windowResize();
+				})
+				.after('
') + .before('
') + ; + } + }; + + /** + * @param {Object} oMessageTextBody + */ + MessageUserStore.prototype.initBlockquoteSwitcher = function (oMessageTextBody) + { + if (oMessageTextBody) + { + var $oList = $('blockquote:not(.rl-bq-switcher)', oMessageTextBody).filter(function () { + return 0 === $(this).parent().closest('blockquote', oMessageTextBody).length; + }); + + if ($oList && 0 < $oList.length) + { + $oList.each(this.addBlockquoteSwitcherCallback); + } + } + }; + + MessageUserStore.prototype.setMessage = function (oData, bCached) + { + var + bNew = false, + bIsHtml = false, + bHasExternals = false, + bHasInternals = false, + oBody = null, + oTextBody = null, + sId = '', + sResultHtml = '', + bPgpSigned = false, + bPgpEncrypted = false, + oMessagesBodiesDom = this.messagesBodiesDom(), + oSelectedMessage = this.selectorMessageSelected(), + oMessage = this.message(), + aThreads = [] + ; + + if (oData && oMessage && oData.Result && 'Object/Message' === oData.Result['@Object'] && + oMessage.folderFullNameRaw === oData.Result.Folder) + { + aThreads = oMessage.threads(); + if (oMessage.uid !== oData.Result.Uid && 1 < aThreads.length && + -1 < Utils.inArray(oData.Result.Uid, aThreads)) + { + oMessage = MessageModel.newInstanceFromJson(oData.Result); + if (oMessage) + { + oMessage.threads(aThreads); + Cache.initMessageFlagsFromCache(oMessage); + + this.message(this.staticMessage.populateByMessageListItem(oMessage)); + oMessage = this.message(); + + bNew = true; + } + } + + if (oMessage && oMessage.uid === oData.Result.Uid) + { + this.messageError(''); + + oMessage.initUpdateByMessageJson(oData.Result); + Cache.addRequestedMessage(oMessage.folderFullNameRaw, oMessage.uid); + + if (!bCached) + { + oMessage.initFlagsByJson(oData.Result); + } + + oMessagesBodiesDom = oMessagesBodiesDom && oMessagesBodiesDom[0] ? oMessagesBodiesDom : null; + if (oMessagesBodiesDom) + { + sId = 'rl-mgs-' + oMessage.hash.replace(/[^a-zA-Z0-9]/g, ''); + oTextBody = oMessagesBodiesDom.find('#' + sId); + if (!oTextBody || !oTextBody[0]) + { + bHasExternals = !!oData.Result.HasExternals; + bHasInternals = !!oData.Result.HasInternals; + + oBody = $('
').hide().addClass('rl-cache-class'); + oBody.data('rl-cache-count', ++Globals.iMessageBodyCacheCount); + + if (Utils.isNormal(oData.Result.Html) && '' !== oData.Result.Html) + { + bIsHtml = true; + sResultHtml = oData.Result.Html.toString(); + } + else if (Utils.isNormal(oData.Result.Plain) && '' !== oData.Result.Plain) + { + bIsHtml = false; + sResultHtml = Utils.plainToHtml(oData.Result.Plain.toString(), false); + + if ((oMessage.isPgpSigned() || oMessage.isPgpEncrypted()) && require('Stores/User/Pgp').capaOpenPGP()) + { + oMessage.plainRaw = Utils.pString(oData.Result.Plain); + + bPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(oMessage.plainRaw); + if (!bPgpEncrypted) + { + bPgpSigned = /-----BEGIN PGP SIGNED MESSAGE-----/.test(oMessage.plainRaw) && + /-----BEGIN PGP SIGNATURE-----/.test(oMessage.plainRaw); + } + + Globals.$div.empty(); + if (bPgpSigned && oMessage.isPgpSigned()) + { + sResultHtml = + Globals.$div.append( + $('
').text(oMessage.plainRaw)
+										).html()
+									;
+								}
+								else if (bPgpEncrypted && oMessage.isPgpEncrypted())
+								{
+									sResultHtml =
+										Globals.$div.append(
+											$('
').text(oMessage.plainRaw)
+										).html()
+									;
+								}
+
+								Globals.$div.empty();
+
+								oMessage.isPgpSigned(bPgpSigned);
+								oMessage.isPgpEncrypted(bPgpEncrypted);
+							}
+						}
+						else
+						{
+							bIsHtml = false;
+						}
+
+						oBody
+							.html(Utils.findEmailAndLinks(sResultHtml))
+							.addClass('b-text-part ' + (bIsHtml ? 'html' : 'plain'))
+						;
+
+						oMessage.isHtml(!!bIsHtml);
+						oMessage.hasImages(!!bHasExternals);
+						oMessage.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None);
+						oMessage.pgpSignedVerifyUser('');
+
+						oMessage.body = oBody;
+						if (oMessage.body)
+						{
+							oMessagesBodiesDom.append(oMessage.body);
+						}
+
+						oMessage.storeDataToDom();
+
+						if (bHasInternals)
+						{
+							oMessage.showInternalImages(true);
+						}
+
+						if (oMessage.hasImages() && SettingsStore.showImages())
+						{
+							oMessage.showExternalImages(true);
+						}
+
+						this.purgeMessageBodyCacheThrottle();
+					}
+					else
+					{
+						oMessage.body = oTextBody;
+						if (oMessage.body)
+						{
+							oMessage.body.data('rl-cache-count', ++Globals.iMessageBodyCacheCount);
+							oMessage.fetchDataToDom();
+						}
+					}
+
+					this.messageActiveDom(oMessage.body);
+
+					this.hideMessageBodies();
+
+					if (oBody)
+					{
+						this.initBlockquoteSwitcher(oBody);
+					}
+
+					oMessage.body.show();
+				}
+
+				Cache.initMessageFlagsFromCache(oMessage);
+				if (oMessage.unseen() || oMessage.hasUnseenSubMessage())
+				{
+					require('App/User').messageListAction(oMessage.folderFullNameRaw,
+						oMessage.uid, Enums.MessageSetAction.SetSeen, [oMessage]);
+				}
+
+				if (bNew)
+				{
+					oMessage = this.message();
+
+					if (oSelectedMessage && oMessage && (
+						oMessage.folderFullNameRaw !== oSelectedMessage.folderFullNameRaw ||
+						oMessage.uid !== oSelectedMessage.uid
+					))
+					{
+						this.selectorMessageSelected(null);
+						if (1 === this.messageList().length)
+						{
+							this.selectorMessageFocused(null);
+						}
+					}
+					else if (!oSelectedMessage && oMessage)
+					{
+						oSelectedMessage = _.find(this.messageList(), function (oSubMessage) {
+							return oSubMessage &&
+								oSubMessage.folderFullNameRaw === oMessage.folderFullNameRaw &&
+								oSubMessage.uid === oMessage.uid;
+						});
+
+						if (oSelectedMessage)
+						{
+							this.selectorMessageSelected(oSelectedMessage);
+							this.selectorMessageFocused(oSelectedMessage);
+						}
+					}
+
+				}
+
+				Utils.windowResize();
+			}
+		}
+	};
+
+	MessageUserStore.prototype.selectMessage = function (oMessage)
+	{
+		if (oMessage)
+		{
+			this.message(this.staticMessage.populateByMessageListItem(oMessage));
+
+			this.populateMessageBody(this.message());
+		}
+		else
+		{
+			this.message(null);
+		}
+	};
+
+	MessageUserStore.prototype.selectThreadMessage = function (sFolder, sUid)
+	{
+		if (Remote.message(this.onMessageResponse, sFolder, sUid))
+		{
+			this.messageThreadLoading(true);
+		}
+	};
+
+	MessageUserStore.prototype.populateMessageBody = function (oMessage)
+	{
+		if (oMessage)
+		{
+			if (Remote.message(this.onMessageResponse, oMessage.folderFullNameRaw, oMessage.uid))
+			{
+				this.messageCurrentLoading(true);
+			}
+		}
+	};
+
+	/**
+	 * @param {string} sResult
+	 * @param {AjaxJsonDefaultResponse} oData
+	 * @param {boolean} bCached
+	 */
+	MessageUserStore.prototype.onMessageResponse = function (sResult, oData, bCached)
+	{
+		this.hideMessageBodies();
+
+		this.messageCurrentLoading(false);
+		this.messageThreadLoading(false);
+
+		if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
+		{
+			this.setMessage(oData, bCached);
+		}
+		else if (Enums.StorageResultType.Unload === sResult)
+		{
+			this.message(null);
+			this.messageError('');
+		}
+		else if (Enums.StorageResultType.Abort !== sResult)
+		{
+			this.message(null);
+			this.messageError((oData && oData.ErrorCode ?
+				Translator.getNotification(oData.ErrorCode) :
+				Translator.getNotification(Enums.Notification.UnknownError)));
+		}
+	};
+
+	/**
+	 * @param {Array} aList
+	 * @return {string}
+	 */
+	MessageUserStore.prototype.calculateMessageListHash = function (aList)
+	{
+		return _.map(aList, function (oMessage) {
+			return '' + oMessage.hash + '_' + oMessage.threadsLen() + '_' + oMessage.flagHash();
+		}).join('|');
+	};
+
+	MessageUserStore.prototype.setMessageList = function (oData, bCached)
+	{
+		if (oData && oData.Result && 'Collection/MessageCollection' === oData.Result['@Object'] &&
+			oData.Result['@Collection'] && Utils.isArray(oData.Result['@Collection']))
+		{
+			var
+				self = this,
+				iIndex = 0,
+				iLen = 0,
+				iCount = 0,
+				iOffset = 0,
+				aList = [],
+				oJsonMessage = null,
+				oMessage = null,
+				oFolder = null,
+				iNewCount = 0,
+				iUtc = require('Common/Momentor').momentNowUnix(),
+				bUnreadCountChange = false
+			;
+
+			iCount = Utils.pInt(oData.Result.MessageResultCount);
+			iOffset = Utils.pInt(oData.Result.Offset);
+
+			oFolder = Cache.getFolderFromCacheList(
+				Utils.isNormal(oData.Result.Folder) ? oData.Result.Folder : '');
+
+			if (oFolder && !bCached)
+			{
+				oFolder.interval = iUtc;
+
+				Cache.setFolderHash(oData.Result.Folder, oData.Result.FolderHash);
+
+				if (Utils.isNormal(oData.Result.MessageCount))
+				{
+					oFolder.messageCountAll(oData.Result.MessageCount);
+				}
+
+				if (Utils.isNormal(oData.Result.MessageUnseenCount))
+				{
+					if (Utils.pInt(oFolder.messageCountUnread()) !== Utils.pInt(oData.Result.MessageUnseenCount))
+					{
+						bUnreadCountChange = true;
+					}
+
+					oFolder.messageCountUnread(oData.Result.MessageUnseenCount);
+				}
+
+				this.initUidNextAndNewMessages(oFolder.fullNameRaw, oData.Result.UidNext, oData.Result.NewMessages);
+			}
+
+			if (bUnreadCountChange && oFolder)
+			{
+				Cache.clearMessageFlagsFromCacheByFolder(oFolder.fullNameRaw);
+			}
+
+			for (iIndex = 0, iLen = oData.Result['@Collection'].length; iIndex < iLen; iIndex++)
+			{
+				oJsonMessage = oData.Result['@Collection'][iIndex];
+				if (oJsonMessage && 'Object/Message' === oJsonMessage['@Object'])
+				{
+					oMessage = MessageModel.newInstanceFromJson(oJsonMessage);
+					if (oMessage)
+					{
+						if (Cache.hasNewMessageAndRemoveFromCache(oMessage.folderFullNameRaw, oMessage.uid) && 5 >= iNewCount)
+						{
+							iNewCount++;
+							oMessage.newForAnimation(true);
+						}
+
+						oMessage.deleted(false);
+
+						if (bCached)
+						{
+							Cache.initMessageFlagsFromCache(oMessage);
+						}
+						else
+						{
+							Cache.storeMessageFlagsToCache(oMessage);
+						}
+
+						aList.push(oMessage);
+					}
+				}
+			}
+
+			this.messageListCount(iCount);
+			this.messageListSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : '');
+			this.messageListPage(window.Math.ceil((iOffset / SettingsStore.messagesPerPage()) + 1));
+
+			this.messageListEndFolder(Utils.isNormal(oData.Result.Folder) ? oData.Result.Folder : '');
+			this.messageListEndSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : '');
+			this.messageListEndPage(this.messageListPage());
+
+			this.messageListDisableAutoSelect(true);
+
+			this.messageList(aList);
+			this.messageListIsNotCompleted(false);
+
+			Cache.clearNewMessageCache();
+
+			if (AppStore.threadsAllowed() && SettingsStore.useThreads())
+			{
+				oMessage = this.message();
+				if (oMessage)
+				{
+					Remote.messageThreadsFromCache(function (sResult, oData) {
+
+						if (Enums.StorageResultType.Success === sResult && oData &&  oData.Result && oData.Result.ThreadUids)
+						{
+							self.messageLastThreadUidsData({
+								'Folder': oData.Result.Folder,
+								'Uid': oData.Result.Uid,
+								'Uids': oData.Result.ThreadUids
+							});
+						}
+
+					}, oMessage.folderFullNameRaw, oMessage.uid);
+				}
+			}
+
+			if (oFolder && (bCached || bUnreadCountChange || SettingsStore.useThreads()))
+			{
+				require('App/User').folderInformation(oFolder.fullNameRaw, aList);
+			}
+		}
+		else
+		{
+			this.messageListCount(0);
+			this.messageList([]);
+			this.messageListError(Translator.getNotification(
+				oData && oData.ErrorCode ? oData.ErrorCode : Enums.Notification.CantGetMessageList
+			));
+		}
+	};
+
+	module.exports = new MessageUserStore();
+
+}());
+
diff --git a/dev/Stores/User/Notification.js b/dev/Stores/User/Notification.js
new file mode 100644
index 000000000..e17e10821
--- /dev/null
+++ b/dev/Stores/User/Notification.js
@@ -0,0 +1,215 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		window = require('window'),
+		ko = require('ko'),
+
+		Enums = require('Common/Enums'),
+		Audio = require('Common/Audio'),
+
+		Settings = require('Storage/Settings')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function NotificationUserStore()
+	{
+		var self = this;
+
+		this.enableSoundNotification = ko.observable(false);
+		this.soundNotificationIsSupported = ko.observable(false);
+
+		this.allowDesktopNotification = ko.observable(false);
+
+		this.desktopNotificationPermissions = ko.computed(function () {
+
+			this.allowDesktopNotification();
+
+			var
+				NotificationClass = this.notificationClass(),
+				iResult = Enums.DesktopNotification.NotSupported
+			;
+
+			if (NotificationClass && NotificationClass.permission)
+			{
+				switch (NotificationClass.permission.toLowerCase())
+				{
+					case 'granted':
+						iResult = Enums.DesktopNotification.Allowed;
+						break;
+					case 'denied':
+						iResult = Enums.DesktopNotification.Denied;
+						break;
+					case 'default':
+						iResult = Enums.DesktopNotification.NotAllowed;
+						break;
+				}
+			}
+			else if (window.webkitNotifications && window.webkitNotifications.checkPermission)
+			{
+				iResult = window.webkitNotifications.checkPermission();
+			}
+
+			return iResult;
+
+		}, this).extend({'notify': 'always'});
+
+		this.enableDesktopNotification = ko.computed({
+			'owner': this,
+			'read': function () {
+				return this.allowDesktopNotification() &&
+					Enums.DesktopNotification.Allowed === this.desktopNotificationPermissions();
+			},
+			'write': function (bValue) {
+				if (bValue)
+				{
+					var
+						NotificationClass = this.notificationClass(),
+						iPermission = this.desktopNotificationPermissions()
+					;
+
+					if (NotificationClass && Enums.DesktopNotification.Allowed === iPermission)
+					{
+						this.allowDesktopNotification(true);
+					}
+					else if (NotificationClass && Enums.DesktopNotification.NotAllowed === iPermission)
+					{
+						NotificationClass.requestPermission(function () {
+							self.allowDesktopNotification.valueHasMutated();
+							if (Enums.DesktopNotification.Allowed === self.desktopNotificationPermissions())
+							{
+								if (self.allowDesktopNotification())
+								{
+									self.allowDesktopNotification.valueHasMutated();
+								}
+								else
+								{
+									self.allowDesktopNotification(true);
+								}
+							}
+							else
+							{
+								if (self.allowDesktopNotification())
+								{
+									self.allowDesktopNotification(false);
+								}
+								else
+								{
+									self.allowDesktopNotification.valueHasMutated();
+								}
+							}
+						});
+					}
+					else
+					{
+						this.allowDesktopNotification(false);
+					}
+				}
+				else
+				{
+					this.allowDesktopNotification(false);
+				}
+			}
+		}).extend({'notify': 'always'});
+
+		if (!this.enableDesktopNotification.valueHasMutated)
+		{
+			this.enableDesktopNotification.valueHasMutated = function () {
+				self.allowDesktopNotification.valueHasMutated();
+			};
+		}
+
+		this.computers();
+
+		this.initNotificationPlayer();
+	}
+
+	NotificationUserStore.prototype.computers = function ()
+	{
+		this.isDesktopNotificationSupported = ko.computed(function () {
+			return Enums.DesktopNotification.NotSupported !== this.desktopNotificationPermissions();
+		}, this);
+
+		this.isDesktopNotificationDenied = ko.computed(function () {
+			return Enums.DesktopNotification.NotSupported === this.desktopNotificationPermissions() ||
+				Enums.DesktopNotification.Denied === this.desktopNotificationPermissions();
+		}, this);
+	};
+
+	NotificationUserStore.prototype.initNotificationPlayer = function ()
+	{
+		if (Audio && Audio.supportedNotification)
+		{
+			this.soundNotificationIsSupported(true);
+		}
+		else
+		{
+			this.enableSoundNotification(false);
+			this.soundNotificationIsSupported(false);
+		}
+	};
+
+	NotificationUserStore.prototype.playSoundNotification = function (bSkipSetting)
+	{
+		if (Audio && Audio.supportedNotification && (bSkipSetting ? true : this.enableSoundNotification()))
+		{
+			Audio.playNotification();
+		}
+	};
+
+	NotificationUserStore.prototype.displayDesktopNotification = function (sImageSrc, sTitle, sText)
+	{
+		if (this.enableDesktopNotification())
+		{
+			var
+				NotificationClass = this.notificationClass(),
+				oNotification = NotificationClass ? new NotificationClass(sTitle, {
+					'body': sText,
+					'icon': sImageSrc
+				}) : null
+			;
+
+			if (oNotification)
+			{
+				if (oNotification.show)
+				{
+					oNotification.show();
+				}
+
+				window.setTimeout((function (oLocalNotifications) {
+					return function () {
+						if (oLocalNotifications.cancel)
+						{
+							oLocalNotifications.cancel();
+						}
+						else if (oLocalNotifications.close)
+						{
+							oLocalNotifications.close();
+						}
+					};
+				}(oNotification)), 7000);
+			}
+		}
+	};
+
+	NotificationUserStore.prototype.populate = function ()
+	{
+		this.enableSoundNotification(!!Settings.settingsGet('SoundNotification'));
+		this.enableDesktopNotification(!!Settings.settingsGet('DesktopNotifications'));
+	};
+
+	/**
+	 * @return {*|null}
+	 */
+	NotificationUserStore.prototype.notificationClass = function ()
+	{
+		return window.Notification && window.Notification.requestPermission ? window.Notification : null;
+	};
+
+	module.exports = new NotificationUserStore();
+
+}());
diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js
new file mode 100644
index 000000000..a6e62e6e0
--- /dev/null
+++ b/dev/Stores/User/Pgp.js
@@ -0,0 +1,122 @@
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko'),
+
+		Utils = require('Common/Utils')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function PgpUserStore()
+	{
+		this.capaOpenPGP = ko.observable(false);
+
+		this.openpgp = null;
+
+		this.openpgpkeys = ko.observableArray([]);
+		this.openpgpKeyring = null;
+
+		this.openpgpkeysPublic = this.openpgpkeys.filter(function (oItem) {
+			return !!(oItem && !oItem.isPrivate);
+		});
+
+		this.openpgpkeysPrivate = this.openpgpkeys.filter(function (oItem) {
+			return !!(oItem && oItem.isPrivate);
+		});
+	}
+
+	/**
+	 * @return {boolean}
+	 */
+	PgpUserStore.prototype.isSupported = function ()
+	{
+		return !!this.openpgp;
+	};
+
+	PgpUserStore.prototype.findPublicKeyByHex = function (sHash)
+	{
+		return _.find(this.openpgpkeysPublic(), function (oItem) {
+			return oItem && sHash === oItem.id;
+		});
+	};
+
+	PgpUserStore.prototype.findPublicKeysByEmail = function (sEmail)
+	{
+		var self = this;
+		return _.compact(_.map(this.openpgpkeysPublic(), function (oItem) {
+
+			var oKey = null;
+			if (oItem && sEmail === oItem.email)
+			{
+				try
+				{
+					oKey = self.openpgp.key.readArmored(oItem.armor);
+					if (oKey && !oKey.err && oKey.keys && oKey.keys[0])
+					{
+						return oKey.keys[0];
+					}
+				}
+				catch (e) {}
+			}
+
+			return null;
+
+		}));
+	};
+
+	/**
+	 * @param {string} sEmail
+	 * @param {string=} sPassword
+	 * @return {?}
+	 */
+	PgpUserStore.prototype.findPrivateKeyByEmail = function (sEmail, sPassword)
+	{
+		var
+			oPrivateKey = null,
+			oKey = _.find(this.openpgpkeysPrivate(), function (oItem) {
+				return oItem && sEmail === oItem.email;
+			})
+		;
+
+		if (oKey)
+		{
+			try
+			{
+				oPrivateKey = this.openpgp.key.readArmored(oKey.armor);
+				if (oPrivateKey && !oPrivateKey.err && oPrivateKey.keys && oPrivateKey.keys[0])
+				{
+					oPrivateKey = oPrivateKey.keys[0];
+					oPrivateKey.decrypt(Utils.pString(sPassword));
+				}
+				else
+				{
+					oPrivateKey = null;
+				}
+			}
+			catch (e)
+			{
+				oPrivateKey = null;
+			}
+		}
+
+		return oPrivateKey;
+	};
+
+	/**
+	 * @param {string=} sPassword
+	 * @return {?}
+	 */
+	PgpUserStore.prototype.findSelfPrivateKey = function (sPassword)
+	{
+		return this.findPrivateKeyByEmail(require('Stores/User/Account').email(), sPassword);
+	};
+
+	module.exports = new PgpUserStore();
+
+}());
+
diff --git a/dev/Stores/User/Quota.js b/dev/Stores/User/Quota.js
new file mode 100644
index 000000000..5f21638bf
--- /dev/null
+++ b/dev/Stores/User/Quota.js
@@ -0,0 +1,43 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		window = require('window'),
+		ko = require('ko')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function QuotaUserStore()
+	{
+		this.quota = ko.observable(0);
+		this.usage = ko.observable(0);
+
+		this.percentage = ko.computed(function () {
+
+			var
+				iQuota = this.quota(),
+				iUsed = this.usage()
+			;
+
+			return 0 < iQuota ? window.Math.ceil((iUsed / iQuota) * 100) : 0;
+
+		}, this);
+	}
+
+	/**
+	 * @param {number} iQuota
+	 * @param {number} iUsage
+	 */
+	QuotaUserStore.prototype.populateData = function(iQuota, iUsage)
+	{
+		this.quota(iQuota * 1024);
+		this.usage(iUsage * 1024);
+	};
+
+	module.exports = new QuotaUserStore();
+
+}());
diff --git a/dev/Stores/User/Settings.js b/dev/Stores/User/Settings.js
new file mode 100644
index 000000000..0534f3914
--- /dev/null
+++ b/dev/Stores/User/Settings.js
@@ -0,0 +1,101 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		window = require('window'),
+		ko = require('ko'),
+
+		Consts = require('Common/Consts'),
+		Enums = require('Common/Enums'),
+		Globals = require('Common/Globals'),
+		Utils = require('Common/Utils'),
+		Events = require('Common/Events'),
+
+		Settings = require('Storage/Settings')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function SettingsUserStore()
+	{
+		this.iAutoLogoutTimer = 0;
+
+		this.layout = ko.observable(Enums.Layout.SidePreview)
+			.extend({'limitedList': [
+				Enums.Layout.SidePreview, Enums.Layout.BottomPreview, Enums.Layout.NoPreview
+			]});
+
+		this.editorDefaultType = ko.observable(Enums.EditorDefaultType.Html)
+			.extend({'limitedList': [
+				Enums.EditorDefaultType.Html, Enums.EditorDefaultType.Plain,
+				Enums.EditorDefaultType.HtmlForced, Enums.EditorDefaultType.PlainForced
+			]});
+
+		this.messagesPerPage = ko.observable(Consts.Defaults.MessagesPerPage)
+			.extend({'limitedList': Consts.Defaults.MessagesPerPageArray});
+
+		this.showImages = ko.observable(false);
+		this.useCheckboxesInList = ko.observable(true);
+		this.useThreads = ko.observable(false);
+		this.replySameFolder = ko.observable(false);
+
+		this.autoLogout = ko.observable(30);
+
+		this.computers();
+		this.subscribers();
+	}
+
+	SettingsUserStore.prototype.computers = function ()
+	{
+		this.usePreviewPane = ko.computed(function () {
+			return Enums.Layout.NoPreview !== this.layout();
+		}, this);
+	};
+
+	SettingsUserStore.prototype.subscribers = function ()
+	{
+		this.layout.subscribe(function (nValue) {
+
+			Globals.$html.toggleClass('rl-no-preview-pane', Enums.Layout.NoPreview === nValue);
+			Globals.$html.toggleClass('rl-side-preview-pane', Enums.Layout.SidePreview === nValue);
+			Globals.$html.toggleClass('rl-bottom-preview-pane', Enums.Layout.BottomPreview === nValue);
+			Globals.$html.toggleClass('rl-mobile-layout', Enums.Layout.Mobile === nValue);
+
+			Events.pub('layout', [nValue]);
+		});
+	};
+
+	SettingsUserStore.prototype.populate = function ()
+	{
+		this.layout(Utils.pInt(Settings.settingsGet('Layout')));
+		this.editorDefaultType(Settings.settingsGet('EditorDefaultType'));
+
+		this.autoLogout(Utils.pInt(Settings.settingsGet('AutoLogout')));
+		this.messagesPerPage(Settings.settingsGet('MPP'));
+
+		this.showImages(!!Settings.settingsGet('ShowImages'));
+		this.useCheckboxesInList(!!Settings.settingsGet('UseCheckboxesInList'));
+		this.useThreads(!!Settings.settingsGet('UseThreads'));
+		this.replySameFolder(!!Settings.settingsGet('ReplySameFolder'));
+
+		var self = this;
+
+		Events.sub('rl.auto-logout-refresh', function () {
+			window.clearTimeout(self.iAutoLogoutTimer);
+			if (0 < self.autoLogout())
+			{
+				self.iAutoLogoutTimer = window.setTimeout(function () {
+					Events.pub('rl.auto-logout');
+				}, self.autoLogout() * 1000 * 60);
+			}
+		});
+
+		Events.pub('rl.auto-logout-refresh');
+	};
+
+	module.exports = new SettingsUserStore();
+
+}());
diff --git a/dev/Stores/User/Template.js b/dev/Stores/User/Template.js
new file mode 100644
index 000000000..315fe1e1e
--- /dev/null
+++ b/dev/Stores/User/Template.js
@@ -0,0 +1,49 @@
+
+(function () {
+
+	'use strict';
+
+	var
+		_ = require('_'),
+		ko = require('ko')
+
+//		Remote = require('Remote/User/Ajax')
+	;
+
+	/**
+	 * @constructor
+	 */
+	function TemplateUserStore()
+	{
+		this.templates = ko.observableArray([]);
+		this.templates.loading = ko.observable(false).extend({'throttle': 100});
+
+		this.templatesNames = ko.observableArray([]).extend({'throttle': 1000});
+		this.templatesNames.skipFirst = true;
+
+		this.subscribers();
+	}
+
+	TemplateUserStore.prototype.subscribers = function ()
+	{
+		this.templates.subscribe(function (aList) {
+			this.templatesNames(_.compact(_.map(aList, function (oItem) {
+				return oItem ? oItem.name : null;
+			})));
+		}, this);
+
+//		this.templatesNames.subscribe(function (aList) {
+//			if (this.templatesNames.skipFirst)
+//			{
+//				this.templatesNames.skipFirst = false;
+//			}
+//			else if (aList && 1 < aList.length)
+//			{
+//				Remote.templatesSortOrder(null, aList);
+//			}
+//		}, this);
+	};
+
+	module.exports = new TemplateUserStore();
+
+}());
diff --git a/dev/Styles/@Main.less b/dev/Styles/@Main.less
index 3080d2587..9e94d2306 100644
--- a/dev/Styles/@Main.less
+++ b/dev/Styles/@Main.less
@@ -1,4 +1,3 @@
-/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
 
 @import "../../vendors/bootstrap/less/variables.less";
 @import "../../vendors/bootstrap/less/mixins.less";
@@ -52,8 +51,10 @@
 @import "Main.less";
 @import "Layout.less";
 @import "Scroll.less";
+@import "Components.less";
 @import "SystemDropDown.less";
 @import "Login.less";
+@import "WelcomPage.less";
 @import "Ask.less";
 @import "Shortcuts.less";
 @import "FolderList.less";
@@ -62,17 +63,20 @@
 @import "FolderSystem.less";
 @import "Filter.less";
 @import "Languages.less";
-@import "AddAccount.less";
+@import "Account.less";
+@import "Template.less";
 @import "OpenPgpKey.less";
 @import "TwoFactor.less";
 @import "Identity.less";
 @import "AdvancedSearch.less";
+@import "Attachmnets.less";
 @import "MessageList.less";
 @import "MessageView.less";
 @import "Contacts.less";
 @import "Compose.less";
 @import "Admin.less";
 @import "AdminGeneral.less";
+@import "AdminBranding.less";
 @import "AdminDomains.less";
 @import "AdminDomain.less";
 @import "AdminPackages.less";
@@ -83,11 +87,11 @@
 @import "Settings.less";
 @import "SettingsGeneral.less";
 @import "SettingsAccounts.less";
-@import "SettingsIdentity.less";
-@import "SettingsIdentities.less";
+@import "SettingsTemplates.less";
 @import "SettingsOpenPGP.less";
 @import "SettingsFolders.less";
 @import "SettingsThemes.less";
+@import "SettingsFilters.less";
 
 @import "Animations.less";
 //@import "Responsive.less";
diff --git a/dev/Styles/AddAccount.less b/dev/Styles/Account.less
similarity index 92%
rename from dev/Styles/AddAccount.less
rename to dev/Styles/Account.less
index 46ad6813d..c556708ac 100644
--- a/dev/Styles/AddAccount.less
+++ b/dev/Styles/Account.less
@@ -1,7 +1,7 @@
-.popups {
-	.b-account-add-content {
-		.modal-header {
-			background-color: #fff;
-		}
-	}
-}
+.popups {
+	.b-account-add-content {
+		.modal-header {
+			background-color: #fff;
+		}
+	}
+}
diff --git a/dev/Styles/Admin.less b/dev/Styles/Admin.less
index ef8adbad4..12a30c2b4 100644
--- a/dev/Styles/Admin.less
+++ b/dev/Styles/Admin.less
@@ -73,6 +73,7 @@
 		height: 34px;
 		padding: 8px @rlLowMargin;
 		color: #fff;
+		text-shadow: 0px 1px 1px #000;
 	}
 
 	.b-content {
@@ -98,5 +99,13 @@
 	.b-settings-content {
 		padding: 20px;
 		padding-left: 30px;
+
+		.legend {
+			width: 670px;
+		}
+
+		.nav-tabs {
+			width: 670px;
+		}
 	}
 }
diff --git a/dev/Styles/AdminBranding.less b/dev/Styles/AdminBranding.less
new file mode 100644
index 000000000..ccad55da7
--- /dev/null
+++ b/dev/Styles/AdminBranding.less
@@ -0,0 +1,12 @@
+
+.b-admin-branding {
+
+	.disabled-form {
+		opacity: 0.8;
+	}
+
+	.custom-css-wrapper textarea {
+		font-family: Monaco, Menlo, Consolas, 'Courier New', monospace;
+	}
+}
+
diff --git a/dev/Styles/AdminDomain.less b/dev/Styles/AdminDomain.less
index ce40a68ba..1dd3467a7 100644
--- a/dev/Styles/AdminDomain.less
+++ b/dev/Styles/AdminDomain.less
@@ -20,7 +20,7 @@
 		height: 320px;
 	}
 
-	&.domain-white-list-page .modal-body {
+	&.domain-second-page .modal-body {
 		left: -800px;
 	}
 
@@ -30,14 +30,14 @@
 	}
 
 	.testing-done {
-		.imap-header, .smtp-header {
+		&.imap-header, &.sieve-header, &.smtp-header {
 			color: green;
 			font-weight: bold;
 		}
 	}
 
 	.testing-error {
-		.imap-header, .smtp-header {
+		&.imap-header, &.sieve-header, &.smtp-header {
 			color: red;
 		}
 	}
diff --git a/dev/Styles/AdminGeneral.less b/dev/Styles/AdminGeneral.less
index d3f3719f1..79bb229a1 100644
--- a/dev/Styles/AdminGeneral.less
+++ b/dev/Styles/AdminGeneral.less
@@ -1,12 +1,13 @@
 
 .b-admin-general {
-	
+
 	.flag-selector {
 		padding-top: 5px;
 	}
 
 	.flag-name {
 		border-bottom: 1px dashed #555;
+		cursor: pointer;
 	}
 }
 
diff --git a/dev/Styles/Animations.less b/dev/Styles/Animations.less
index 74e9c8dd9..fb9940869 100644
--- a/dev/Styles/Animations.less
+++ b/dev/Styles/Animations.less
@@ -9,6 +9,10 @@
 	0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); }
 }
 
+@keyframes bounce-me {
+	0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); }
+}
+
 @keyframes textLoadingAnimationKeyFrame {
 	0% { opacity: 1; } 33% { opacity: 0; } 100% { opacity: 1; }
 }
@@ -17,21 +21,32 @@
 	0% {background-position: 0 0;} 100% {background-position: 60px 0;}
 }
 
-html.rl-started-trigger.no-mobile .b-login-content .loginFormWrapper {
-	transform: translateY(-30px);
+@keyframes login-form-shake  {
+  0% {transform: translateX(0);}
+  12.5% {transform: translateX(-6px) rotateY(-5deg)}
+  37.5% {transform: translateX(5px) rotateY(4deg)}
+  62.5% {transform: translateX(-3px) rotateY(-2deg)}
+  87.5% {transform: translateX(2px) rotateY(1deg)}
+  100% {transform: translateX(0)}
 }
 
-html.rl-started-trigger.no-mobile #rl-content {
-	opacity: 0.7;
+html.csstransitions.rl-started-trigger.no-mobile .b-login-content .loginFormWrapper {
+	/*transform: scale(1.1);*/
+	transform: translateY(-20px);
+	opacity: 0.5;
 }
 
+/*html.rl-started-trigger.no-mobile #rl-content {
+	opacity: 0.7;
+}*/
+
 #rl-loading {
 	.transition(opacity 0.5s linear);
 }
 
 .rl-anim {
 
-	&.csstransitions.no-mobile {
+	&.csstransitions.no-mobile.rl-started-delay {
 
 		#rl-left {
 			.transition(width 0.3s ease-out);
@@ -40,20 +55,32 @@ html.rl-started-trigger.no-mobile #rl-content {
 		#rl-right {
 			.transition(left 0.3s ease-out);
 		}
-		
+
 		#rl-sub-left, #rl-sub-left .messageList .inputSearch {
 			.transition(width 0.3s ease-out);
 		}
-		
+
 		#rl-sub-right {
 			.transition(left 0.3s ease-out);
 		}
 	}
 
-	&.csstransitions.no-mobile #rl-content {
-		.transition(opacity 0.3s ease-out);
+	&.cssanimations.csstransitions.no-mobile .b-login-content .errorAnimated {
+		animation: login-form-shake 400ms ease-in-out;
 	}
-	
+
+	&.cssanimations.csstransitions.no-mobile .b-login-content .afterLoginHide {
+		opacity: 0;
+	}
+
+/*	&.csstransitions.no-mobile #rl-content {
+		.transition(opacity 0.3s ease-out);
+	}*/
+
+	&.csstransitions.no-mobile .btn-group.dropdown.colored-toggle.open .animate-this-icon-on-open {
+		animation: bounce-me .5s linear;
+	}
+
 	&.csstransitions.no-mobile .b-login-content .loginFormWrapper {
 		.transition(all 0.3s ease-out);
 	}
@@ -101,9 +128,14 @@ html.rl-started-trigger.no-mobile #rl-content {
 		}
 	}
 
+	&.csstransitions.rl-modal.rl-modal-animation .popups {
+		overflow: hidden;
+	}
+
 	&.csstransitions .modal.fade {
-		
-		.transition(all 0.2s ease-out);
+
+		.transition(all 200ms ease-out);
+		/*transform: scale(0.95);*/
 		transform: translateY(-20px);
 
 		&.in {
diff --git a/dev/Styles/Attachmnets.less b/dev/Styles/Attachmnets.less
new file mode 100644
index 000000000..282a5cc05
--- /dev/null
+++ b/dev/Styles/Attachmnets.less
@@ -0,0 +1,192 @@
+.attachmentItem {
+
+	position: relative;
+
+	display: inline-block;
+	margin: 5px;
+	max-width: 200px;
+	min-width: 60px;
+	overflow: hidden;
+	list-style: none;
+	line-height: 24px;
+	border: 0;
+	background-color: #fff;
+
+	box-shadow: 0 1px 4px #ccc;
+	box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
+	box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04), 0 1px 5px rgba(0, 0, 0, 0.1);
+
+	border-radius: 3px;
+
+	&.waiting {
+		opacity: 0.6;
+	}
+
+	&.checked {
+		box-shadow: 0 1px 4px #00a;
+		box-shadow: 0 1px 5px rgba(0, 0, 255, 0.3);
+		box-shadow: 0 0 0 1px rgba(0, 0, 255, 0.1), 0 1px 5px rgba(0, 0, 255, 0.2);
+	}
+
+	.checkedParent {
+		position: absolute;
+		top: 0;
+		right: 0;
+		padding: 1px 4px 0px 5px;
+		background: #FAFAFA;
+		border: 0px solid #CCC;
+	}
+
+	.attachmentSize {
+		font-size: 12px;
+		color: #999;
+	}
+
+	&.error {
+		.attachmentIcon, .attachmentSize, .attachmentName {
+			color: red;
+		}
+	}
+
+	.attachmentIconParent {
+
+		position: relative;
+
+		height: 56px;
+		width: 60px;
+
+		background: none;
+
+		.iconPreview, .iconBG, .iconMain, .iconProgress {
+			position: absolute;
+			top: 0;
+			left: 0;
+
+			display: inline-block;
+			width: 100%;
+			height: 100%;
+		}
+
+		.iconProgress {
+			background: #eee;
+			width: 0%;
+		}
+
+		.iconBG {
+			font-size: 18px;
+			text-align: center;
+			color: #999;
+			font-weight: bold;
+			line-height: 55px;
+
+			text-shadow: 0 1px 0 #FFF;
+			text-shadow: 0 1px 0 rgba(255, 255, 255, 0.8);
+		}
+
+		.iconPreview {
+			display: none;
+
+			background: #555;
+			background-image: none;
+			background: rgba(0, 0, 0, .5) !important;
+
+			.attachmentIcon {
+				color: #fff;
+				text-shadow: 0 1px 0 #000;
+			}
+		}
+	}
+
+	.attachmentNameParent {
+
+		position: relative;
+
+		margin-left: 60px;
+		padding: 4px 4px 3px 6px;
+		min-width: 90px;
+
+		cursor: pointer;
+
+		color: #333;
+		background: #fafafa;
+		border-left: 1px solid #ddd;
+	}
+
+	.attachmentIcon {
+		margin: 6px 0 0 13px;
+		font-size: 36px;
+		width: 36px;
+		height: 36px;
+		color: #aaa;
+	}
+
+	.attachmentIcon  {
+		&.icon-none {
+			display: none;
+		}
+		&.icon-file-certificate {
+			margin-left: 15px;
+		}
+	}
+
+	.attachmentIconText {
+		display: inline-block;
+		font-size: 28px;
+		width: 60px;
+		height: 56px;
+		color: #aaa;
+		line-height: 56px;
+		text-align: center;
+		font-style: normal;
+	}
+
+/*
+	.attachmentIconParent.hasPreview.isImage {
+		.iconMain {
+			display: none;
+		}
+	}
+*/
+
+	.attachmentIconParent.hasPreview:hover {
+		.iconPreview {
+			display: inline-block;
+		}
+		.iconMain {
+			display: none;
+		}
+	}
+
+	.attachmentIconParent.hasPreplay:hover {
+		.iconPreview {
+			display: inline-block;
+		}
+		.iconMain {
+			display: none;
+		}
+	}
+
+	.showPreview, .showPreplay {
+		display: none;
+		cursor: pointer;
+	}
+
+	.attachmentIconParent.hasPreview {
+		.showPreview {
+			display: inline;
+		}
+
+		.hidePreview {
+			display: none;
+		}
+	}
+	.attachmentIconParent.hasPreplay {
+		.showPreplay {
+			display: inline;
+		}
+
+		.hidePreview {
+			display: none;
+		}
+	}
+}
diff --git a/dev/Styles/Components.less b/dev/Styles/Components.less
new file mode 100644
index 000000000..cef5608ba
--- /dev/null
+++ b/dev/Styles/Components.less
@@ -0,0 +1,155 @@
+
+.checkbox-box-sizes() {
+	top: 0px;
+	left: 0px;
+	width: 18px;
+	height: 18px;
+	border: solid 2px #999;
+
+	transform: rotate(0deg);
+}
+
+.checkbox-mark-sizes() {
+/*	top: -4px;
+	left: 6px;
+	width: 10px;
+	height: 21px;*/
+
+	top: -1px;
+	left: 5px;
+	width: 10px;
+	height: 18px;
+
+	border-right-width: 2px;
+	border-bottom-width: 2px;
+
+	transform: rotate(45deg);
+}
+
+.checkbox-result-sizes() {
+	top: 13px;
+	left: 5px;
+	width: 4px;
+	height: 4px;
+
+	transform: rotate(45deg);
+}
+
+.e-component {
+
+	&.e-checkbox {
+
+		cursor: pointer;
+
+		&.disabled {
+			cursor: default;
+			color: #999;
+		}
+	}
+
+	&.e-radio {
+
+		cursor: pointer;
+
+		&.disabled {
+			cursor: default;
+			color: #999;
+		}
+	}
+}
+
+.e-component.material-design {
+
+	margin-top: 2px;
+	margin-bottom: 6px;
+
+	&.e-checkbox {
+
+		.sub-checkbox-container {
+			display: inline-block;
+			position: relative;
+			transform: translateZ(0);
+			width: 18px;
+			height: 18px;
+			vertical-align: bottom;
+			margin-bottom: 3px;
+		}
+
+		.sub-label {
+			padding-left: 12px;
+		}
+
+		.sub-checkbox {
+			position: absolute;
+			box-sizing: border-box;
+			margin-top: 1px;
+
+			.checkbox-box-sizes();
+		}
+
+		.sub-checkbox.checked {
+			border-top: none;
+			border-left: none;
+			border-color: #0F9D58;
+
+			.checkbox-mark-sizes();
+		}
+
+		// animation
+		.sub-checkbox.checked {
+			&.box {
+				border: solid 2px;
+				animation: box-shrink 140ms ease-out forwards;
+			}
+			&.checkmark {
+				border-left: none;
+				border-top: none;
+				animation: checkmark-expand 140ms ease-out forwards;
+			}
+		}
+
+		.sub-checkbox.unchecked {
+			&.box {
+				animation: box-expand 140ms ease-out forwards;
+			}
+			&.checkmark {
+				border-left: none;
+				border-top: none;
+				transform: rotate(45deg);
+				animation: checkmark-shrink 140ms ease-out forwards;
+			}
+		}
+
+		&.disabled {
+			.sub-checkbox {
+				border-color: #aaa;
+				cursor: not-allowed;
+				color: #aaa;
+			}
+			.sub-label {
+				cursor: not-allowed;
+				color: #aaa;
+			}
+		}
+	}
+}
+
+@keyframes box-shrink {
+	0% { .checkbox-box-sizes(); }
+	100% { .checkbox-result-sizes(); }
+}
+
+@keyframes checkmark-expand {
+	0% { .checkbox-result-sizes(); }
+	100% { .checkbox-mark-sizes(); }
+}
+
+@keyframes checkmark-shrink {
+	0% { .checkbox-mark-sizes(); }
+	100% { .checkbox-result-sizes(); }
+}
+
+@keyframes box-expand {
+	0% { .checkbox-result-sizes(); }
+	100% { .checkbox-box-sizes(); }
+}
\ No newline at end of file
diff --git a/dev/Styles/Compose.less b/dev/Styles/Compose.less
index 343a053bf..06f26b599 100644
--- a/dev/Styles/Compose.less
+++ b/dev/Styles/Compose.less
@@ -12,8 +12,34 @@
 		}
 	}
 
-	.textAreaParent {
+	.textAreaParent, .attachmentAreaParent {
 		overflow: hidden;
+		position: relative;
+	}
+
+	.attachmentAreaParent {
+		padding: 10px 10px 6px 10px;
+		background: #ddd;
+		border-top: 1px solid #ccc;
+
+		overflow-y: auto;
+	}
+
+	.attachmentAreaParent {
+
+		.no-attachments-desc {
+			padding: 50px 10px;
+			text-align: center;
+			font-size: 24px;
+			color: #666;
+			text-shadow: 0 1px 0 #fff;
+			text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+		}
+
+		.attachmentList {
+			margin: 0;
+			padding: 10px;
+		}
 	}
 
 	.b-header-toolbar {
@@ -26,9 +52,13 @@
 		background-color: #333;
 		background-color: rgba(0,0,0,0.8) !important;
 
-		.close {
-			color: #fff;
+		.close, .close-custom, .minimize-custom {
 			opacity: 1;
+			color: #fff;
+			border-color: #eee;
+
+			font-size: 24px;
+			line-height: 24px;
 		}
 
 		.btn.disabled {
@@ -41,6 +71,10 @@
 			margin-left: 8px;
 		}
 
+		.button-close, .button-skip {
+			margin-left: 8px;
+		}
+
 		.disabled.button-delete {
 			margin-left: 0px;
 		}
@@ -68,7 +102,7 @@
 				border-bottom: 1px dashed #555;
 			}
 		}
-		
+
 		.e-row {
 			line-height: 30px;
 		}
@@ -96,6 +130,11 @@
 			color: red;
 		}
 
+		.error-to {
+			color: red;
+			font-weight: bold;
+		}
+
 		.b-appachments {
 
 			.b-attacment {
@@ -127,7 +166,7 @@
 				.error {
 					color: red;
 				}
-				.close {
+				.close, .close-custom {
 					float: left;
 					padding-right: 13px;
 				}
@@ -135,6 +174,12 @@
 		}
 	}
 
+	.cke_chrome {
+		border-left-width: 0 !important;
+		border-right-width: 0 !important;
+		border-bottom-width: 0 !important;
+	}
+
 	.b-attachment-button {
 		display: inline-block;
 	}
@@ -142,19 +187,23 @@
 	.b-attachment-place {
 
 		position: absolute;
-		height: 120px;
+		left: 5px;
+		right: 5px;
+		top: 5px;
+		bottom: 5px;
+
 		border: 2px #777 dashed;
+		z-index: 300;
+
 		line-height: 119px;
 		text-align: center;
-		background-color: #fff;
-		z-index: 300;
-		left: 20px;
-		right: 20px;
+		background-color: #efefef;
 		font-size: 24px;
 
-		&.drag-and-drop-over {
-			background: #777;
-			color: #fff;
+		border-radius: 10px;
+
+		&.dragAndDropOver {
+			background-color: #fff;
 		}
 	}
 }
\ No newline at end of file
diff --git a/dev/Styles/Contacts.less b/dev/Styles/Contacts.less
index aa3a31e23..02b27013f 100644
--- a/dev/Styles/Contacts.less
+++ b/dev/Styles/Contacts.less
@@ -41,7 +41,7 @@
 			background-color: #333;
 			background-color: rgba(0,0,0,0.8) !important;
 
-			.close {
+			.close, .close-custom {
 				color: #fff;
 				opacity: 1;
 			}
@@ -271,27 +271,6 @@
 				-webkit-overflow-scrolling: touch;
 			}
 
-			.tags-property-container {
-				font-size: 18px;
-				width: 400px;
-
-				.inputosaurus-container {
-					border-width: 1px;
-					border-color: transparent;
-					box-shadow: none;
-
-					&:hover {
-						box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-						border-color: #ccc;
-					}
-
-					&.inputosaurus-focused {
-						box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-						border-color: #999;
-					}
-				}
-			}
-
 			.contactValueStatic, .contactValueLargeStatic, .contactValueTextAreaStatic {
 				height: 20px;
 				line-height: 20px;
diff --git a/dev/Styles/Filter.less b/dev/Styles/Filter.less
index fd2cebb19..d4860ab6e 100644
--- a/dev/Styles/Filter.less
+++ b/dev/Styles/Filter.less
@@ -1,10 +1,14 @@
 .popups {
 	.b-filter-content {
 
-		width: 800px;
+		width: 750px;
 
 		.modal-header {
 			background-color: #fff;
 		}
+
+		.button-delete {
+			cursor: pointer;
+		}
 	}
 }
diff --git a/dev/Styles/FolderList.less b/dev/Styles/FolderList.less
index 19fd08f3d..d11993121 100644
--- a/dev/Styles/FolderList.less
+++ b/dev/Styles/FolderList.less
@@ -7,6 +7,12 @@
 	&.focused .b-content {
 	}
 
+	.b-folders-user {
+		.e-link.print-count.system .badge {
+			display: none !important;
+		}
+	}
+
 	.b-toolbar {
 		position: absolute;
 		top: 0;
@@ -144,27 +150,42 @@
 	.b-sub-folders .e-item .e-link {
 		padding-left: @subPadding * 1 + @folderItemPadding;
 	}
-	.b-sub-folders.unpaddig-folder .e-item .e-link {
-		padding-left: @folderItemPadding;
-	}
 	.b-sub-folders .b-sub-folders .e-item .e-link {
 		padding-left: @subPadding * 2 + @folderItemPadding;
 	}
-	.b-sub-folders.unpaddig-folder .b-sub-folders .e-item .e-link {
-		padding-left: @subPadding * 1 + @folderItemPadding;
-	}
 	.b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link {
 		padding-left: @subPadding * 3 + @folderItemPadding;
 	}
-	.b-sub-folders.unpaddig-folder .b-sub-folders .b-sub-folders .e-item .e-link {
-		padding-left: @subPadding * 2 + @folderItemPadding;
-	}
 	.b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link {
 		padding-left: @subPadding * 4 + @folderItemPadding;
 	}
-	.b-sub-folders.unpaddig-folder .b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link {
-		padding-left: @subPadding * 3 + @folderItemPadding;
+
+/**/
+	&.single-root-inbox .i-am-inbox.e-link {
+		display: none !important;
 	}
+
+	&.single-root-inbox .i-am-inbox-wrapper > .b-sub-folders {
+		max-height: none !important;
+		height: inherit !important;
+		display: block !important;
+	}
+
+	&.single-root-inbox .i-am-inbox-wrapper {
+		.b-sub-folders .e-item .e-link {
+			padding-left: @folderItemPadding;
+		}
+		.b-sub-folders .b-sub-folders .e-item .e-link {
+			padding-left: @subPadding * 1 + @folderItemPadding;
+		}
+		.b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link {
+			padding-left: @subPadding * 2 + @folderItemPadding;
+		}
+		.b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link {
+			padding-left: @subPadding * 3 + @folderItemPadding;
+		}
+	}
+/**/
 }
 
 html.rl-left-panel-disabled {
diff --git a/dev/Styles/Identity.less b/dev/Styles/Identity.less
index 5de93b5d9..325f46864 100644
--- a/dev/Styles/Identity.less
+++ b/dev/Styles/Identity.less
@@ -1,9 +1,30 @@
 .popups {
 	.b-identity-content {
+
+		&.modal {
+			width: 750px;
+		}
+
 		.modal-header {
 			background-color: #fff;
 		}
 
+		.modal-body {
+			overflow: hidden;
+		}
+
+		.control-label {
+			width: 100px;
+		}
+
+		.controls {
+			margin-left: 120px;
+		}
+
+		.e-signature-place {
+			height: 200px;
+		}
+
 		.textEmail {
 			margin-top: 5px;
 			font-weight: bold;
diff --git a/dev/Styles/Languages.less b/dev/Styles/Languages.less
index 3adac67d6..7f889776e 100644
--- a/dev/Styles/Languages.less
+++ b/dev/Styles/Languages.less
@@ -21,7 +21,11 @@
 			background-color: #fff;
 			text-align: left;
 			border: 1px solid transparent;
+			border-radius: 2px;
 
+			&.user {
+				background-color: #fffddd;
+			}
 			&.selected {
 				background-color: #f5f5f5;
 				border-color: #ccc;
diff --git a/dev/Styles/Layout.less b/dev/Styles/Layout.less
index e5cfe4818..d183aded1 100644
--- a/dev/Styles/Layout.less
+++ b/dev/Styles/Layout.less
@@ -139,7 +139,7 @@ html.ssm-state-tablet, html.ssm-state-mobile {
 	}
 
 	.b-compose.modal {
-		width: 700px;
+		width: 720px;
 	}
 
 	.b-contacts-content.modal {
@@ -156,7 +156,7 @@ html.ssm-state-tablet, html.ssm-state-mobile {
 html.ssm-state-tablet {
 
 	.b-compose.modal {
-		width: 700px;
+		width: 720px;
 	}
 
 	.b-contacts-content.modal {
@@ -205,11 +205,21 @@ html.rl-left-panel-disabled {
 	}
 }
 
-.ui-resizable-helper {
+.ui-resizable-helper-w {
 	border-right: 5px solid #777;
 	border-right-color: rgba(255,255,255,0.7);
 }
 
+.ui-resizable-helper-h {
+	border-bottom: 5px solid #ccc;
+	border-bottom-color: rgba(0,0,0,0.3);
+}
+
+.ui-resizable-handle:hover {
+	background: #aaa;
+	background: rgba(255,255,255,0.5);
+}
+
 html.rl-no-preview-pane {
 
 	#rl-sub-left {
@@ -230,6 +240,47 @@ html.rl-no-preview-pane {
 	}
 }
 
+html.rl-side-preview-pane #rl-right .ui-resizable-handle {
+	display: none !important;
+}
+
+html.rl-bottom-preview-pane {
+
+	#rl-sub-left {
+		right: @rlBottomMargin !important;
+		width: inherit;
+
+		.b-message-list-wrapper {
+			bottom: inherit;
+			height: 300px;
+
+			box-shadow: none;
+		}
+
+		.messageList .inputSearch {
+			width: 300px !important;
+		}
+	}
+
+	#rl-sub-right {
+
+		left: 0 !important;
+
+		.messageView .top-toolbar {
+			display: none;
+		}
+
+		.b-message-view-wrapper {
+			top: 356px;
+			left: 0;
+			right: 5px;
+
+
+			box-shadow: none;
+		}
+	}
+}
+
 .visible-on-ctrl, .visible-on-ctrl-btn {
 	display: none;
 }
@@ -260,6 +311,7 @@ html.rl-ctrl-key-pressed {
 #rl-loading, #rl-loading-error {
 	position: absolute;
 	font-size: 30px;
+	line-height: 100%;
 	top: 50%;
 	width: 100%;
 	height: 65px;
@@ -274,3 +326,8 @@ html.rl-ctrl-key-pressed {
 	background-image: none;
 	display: none;
 }
+
+#rl-loading-error-additional {
+	margin-top: 20px;
+	font-size: 20px;
+}
diff --git a/dev/Styles/Login.less b/dev/Styles/Login.less
index 0e53544d2..2a913bb65 100644
--- a/dev/Styles/Login.less
+++ b/dev/Styles/Login.less
@@ -9,6 +9,7 @@
 		display: inline-block;
 		vertical-align: middle;
 		text-align: center;
+		perspective: 500px;
 
 		.descWrapper {
 
@@ -30,19 +31,20 @@
 			color: #333;
 			margin: 0;
 			float: none;
+			width: 303px;
 		}
 
-		.control-label {
+/*		.control-label {
 			font-size: 16px;
 			line-height: 30px;
-		}
+		}*/
 
-		.control-group {
+		.controls, .control-group {
 			margin-bottom: 25px;
 		}
 
 		.wrapper {
-			padding: 40px 40px 10px 40px;
+			padding: 40px 40px 12px 40px;
 		}
 
 		#recaptcha_image img {
@@ -50,10 +52,12 @@
 			border-radius: 3px;
 		}
 
-		.inputLoginForm, .inputEmail, .inputLogin, .inputPassword, .inputAdditionalCode {
-			font-size: 18px;
-			height: 30px;
-			line-height: 29px;
+		.controls {
+			.inputLoginForm, .inputEmail, .inputLogin, .inputPassword, .inputAdditionalCode {
+				font-size: 18px;
+				height: 40px;
+				line-height: 20px;
+			}
 		}
 
 		.signMeLabel, .languageLabel {
@@ -79,7 +83,7 @@
 			color: #999;
 		}
 
-		.control-group.error .add-on i {
+		.controls.error .add-on i, .control-group.error .add-on i {
 			color: #b94a48;
 		}
 	}
@@ -103,4 +107,13 @@
 	.flag-selector {
 		margin-bottom: 0;
 	}
+
+	.social-buttons {
+		margin-top: 5px;
+	}
+
+	.social-button, .language-button {
+		padding: 5px;
+		outline: none;
+	}
 }
diff --git a/dev/Styles/Main.less b/dev/Styles/Main.less
index cacade686..1146ebecb 100644
--- a/dev/Styles/Main.less
+++ b/dev/Styles/Main.less
@@ -53,4 +53,9 @@ select:focus {
 
 html.mobile * {
 	-webkit-tap-highlight-color: rgba(0,0,0,0);
+}
+
+input::-ms-clear,
+input::-ms-reveal {
+	display: none;
 }
\ No newline at end of file
diff --git a/dev/Styles/MessageList.less b/dev/Styles/MessageList.less
index 831f2262f..4c7dba60a 100644
--- a/dev/Styles/MessageList.less
+++ b/dev/Styles/MessageList.less
@@ -52,7 +52,7 @@ html.rl-no-preview-pane {
 		padding-left: 8px;
 		padding-right: 8px;
 	}
-	
+
 	.b-message-list-wrapper {
 		position: absolute;
 		top: 50px;
@@ -242,6 +242,24 @@ html.rl-no-preview-pane {
 				background-color: #ccc !important;
 			}
 
+			.importantMark {
+				display: none;
+
+				color:red;
+				margin-right:5px
+			}
+
+			&.deleted-mark {
+				opacity: .7;
+				.sender, .subject, .subject-prefix, .subject-suffix {
+					text-decoration: line-through;
+				}
+			}
+
+			&.important .importantMark {
+				display: inline;
+			}
+
 			&.e-single-line {
 				height: 35px;
 			}
@@ -275,7 +293,7 @@ html.rl-no-preview-pane {
 			.flagParent {
 				display: inline-block;
 				float: right;
-				padding: 0 8px 0 5px;
+				padding: 0 10px 0 5px;
 			}
 
 			&.e-single-line .flagParent {
@@ -299,12 +317,12 @@ html.rl-no-preview-pane {
 				display: inline-block;
 				float: right;
 				position: relative;
-				margin: 2px 8px 0 5px;
+				margin: 2px 10px 0 5px;
 			}
 
 			&.e-single-line .attachmentParent {
 				float: left;
-				margin: 0 5px 0 0;
+				margin: 0 8px 0 0;
 			}
 
 			.senderParent {
@@ -374,7 +392,17 @@ html.rl-no-preview-pane {
 				overflow: hidden;
 				text-overflow: ellipsis;
 			}
-			
+
+			.threads-len {
+				.threads-len-data {
+					background-color: #999;
+					color: #fff;
+					border-radius: 6px;
+					padding: 1px 6px 1px 5px;
+					margin-right: 5px;
+				}
+			}
+
 			.subject-prefix {
 				color: #888;
 				/*font-style: italic;*/
@@ -419,6 +447,16 @@ html.rl-no-preview-pane {
 				text-shadow: 0px 1px 0px #eee;
 			}
 
+			&.hasUnseenSubMessage {
+				background-color: #FFFFD9;
+				.sidebarParent {
+					background-color: lighten(orange, 30%);
+				}
+				&.focused .sidebarParent {
+					background-color: darken(orange, 10%) !important;
+				}
+			}
+
 			&.unseen {
 				background-color: #FFFFD9;
 				.sender, .subject, .subject-suffix {
@@ -433,27 +471,18 @@ html.rl-no-preview-pane {
 				}
 			}
 
-			&.hasUnseenSubMessage {
-				background-color: #FFFFD9;
-				.sidebarParent {
-					background-color: lighten(orange, 30%);
-				}
-				&.focused .sidebarParent {
-					background-color: darken(orange, 10%) !important;
-				}
-			}
-
+/*
 			&.hasParentMessage {
 				background-color: #ecf0f1;
 
 				.sidebarParent {
 					background-color: #bdc3c7;
 				}
-				
+
 				&.focused .sidebarParent {
 					background-color: darken(#bdc3c7, 10%) !important;
 				}
-				
+
 				&.unseen {
 					background-color: darken(#ecf0f1, 5%);
 
@@ -466,6 +495,7 @@ html.rl-no-preview-pane {
 					}
 				}
 			}
+*/
 
 			&.checked {
 				.sidebarParent {
@@ -489,13 +519,13 @@ html.rl-no-preview-pane {
 					background-color: #398CF2;
 					.opacity(20);
 				}
-				
+
 				+ .messageListItem .delimiter {
 					background-color: #398CF2;
 					opacity: 0.3;
 				}
 			}
-			
+
 			&.hasFlaggedSubMessage {
 				.flagOff, .flagOn {
 					display: none;
diff --git a/dev/Styles/MessageView.less b/dev/Styles/MessageView.less
index 30a2348d7..2b272a3aa 100644
--- a/dev/Styles/MessageView.less
+++ b/dev/Styles/MessageView.less
@@ -17,7 +17,7 @@ html.rl-no-preview-pane {
 		position: absolute;
 		top: 0;
 		right: 0;
-		left: 0;
+		left: -2px;
 		height: 30px;
 		padding: 10px 0;
 		color: #fff;
@@ -30,12 +30,11 @@ html.rl-no-preview-pane {
 		top: 50px + @rlLowMargin;
 		bottom: @rlLowMargin + @rlBottomMargin;
 		right: @rlLowMargin;
-		left: -1px;
-		overflow: hidden;
+		left: -2px;
+		/*overflow: hidden;*/
 		border: @rlLowBorderSize solid @rlMainDarkColor;
-		
-		.border-top-right-radius(@rlLowBorderRadius);
-		.border-bottom-right-radius(@rlLowBorderRadius);
+
+		.border-radius(@rlLowBorderRadius);
 
 		background-color: #fff;
 
@@ -58,8 +57,8 @@ html.rl-no-preview-pane {
 			text-align: center;
 			font-size: 24px;
 			line-height: 30px;
-			padding-top: 120px;
 			color: #999;
+			padding: 120px 10px 0 10px;
 		}
 
 		.b-message-view-desc.error {
@@ -70,6 +69,100 @@ html.rl-no-preview-pane {
 			-webkit-overflow-scrolling: touch;
 		}
 
+		.message-fixed-button-toolbar {
+			z-index: 100;
+			position: absolute;
+			top: 33px;
+			right: 10px;
+		}
+
+		.flagParent {
+
+			cursor: pointer;
+			margin-right: 5px;
+
+			.flagOn {
+				color: orange;
+			}
+
+			.flagOff {
+				opacity: 0.5;
+				&:hover {
+					opacity: 1;
+				}
+			}
+		}
+
+		.messageItemHeader {
+
+			position: absolute;
+			padding: 10px;
+			background-color: #f8f8f8;
+			border-top: 0px;
+			border-bottom: 1px solid #ddd;
+			z-index: 1;
+			top: 0;
+			left: 0;
+			right: 0;
+
+			.date {
+			}
+
+			.fromPic {
+				display: inline-block;
+				width: 50px;
+				height: 50px;
+				float: left;
+				padding: 2px;
+				margin: 0 5px 0 0;
+				background: #fff;
+				border: 1px solid #ccc;
+				border-radius: 6px;
+				box-sizing: border-box;
+			}
+
+			.subjectParent {
+				font-size: 16px;
+				font-weight: bold;
+				text-overflow: ellipsis;
+				overflow: hidden;
+				white-space: nowrap;
+			}
+
+			.senderParent {
+				margin-top: 10px;
+			}
+
+			.messageButtons {
+				margin-top: 5px;
+			}
+
+			.informationShort {
+				margin-left: 22px;
+				a {
+					.g-ui-link;
+				}
+			}
+			.informationFull {
+				margin-top: 10px;
+				border: 1px solid #ddd;
+				background-color: #fff;
+				border-radius: 5px;
+				padding: 10px 15px;
+				overflow: hidden;
+			}
+
+			.emptySubjectText {
+				display: none;
+				font-style: italic;
+				color: #999;
+			}
+
+			&.emptySubject .emptySubjectText {
+				display: inline;
+			}
+		}
+
 		.messageItem {
 
 			position: absolute;
@@ -81,21 +174,11 @@ html.rl-no-preview-pane {
 			-webkit-overflow-scrolling: touch;
 			border-radius: @rlLowBorderRadius;
 
-			.emptySubjectText {
-				display: none;
-				font-style: italic;
-				color: #999;
-			}
-
-			&.emptySubject .emptySubjectText {
-				display: inline;
-			}
-
 			.buttonUp, .buttonUnFull, .buttonFull {
 				display: inline-block;
 				position: fixed;
-				right: 30px;
-				top: 90px;
+				right: 25px;
+				bottom: 25px;
 				height: 30px;
 				width: 30px;
 				text-align: center;
@@ -119,71 +202,14 @@ html.rl-no-preview-pane {
 			}
 
 			.buttonUp {
-				right: 70px;
+				right: 65px;
 				z-index: 0;
 			}
 
-			.buttonUnFull {
+			.buttonUp, .buttonUnFull {
 				display: none;
 			}
 
-			.messageItemHeader {
-
-				position: relative;
-				padding: 10px;
-				background-color: #f8f8f8;
-				border-top: 0px;
-				border-bottom: 1px solid #ddd;
-				z-index: 1;
-
-				.date {
-				}
-
-				.fromPic {
-					display: inline-block;
-					width: 50px;
-					height: 50px;
-					float: left;
-					padding: 2px;
-					margin: 0 5px 0 0;
-					background: #fff;
-					border: 1px solid #ccc;
-					border-radius: 10px;
-					box-sizing: border-box;
-				}
-
-				.subjectParent {
-					font-size: 16px;
-					font-weight: bold;
-					text-overflow: ellipsis;
-					overflow: hidden;
-					white-space: nowrap;
-					margin-left: 3px;
-				}
-
-				.senderParent {
-					margin-top: 10px;
-				}
-
-				.messageButtons {
-					margin-top: 5px;
-				}
-
-				.informationShort {
-					margin-left: 15px;
-					a {
-						.g-ui-link;
-					}
-				}
-				.informationFull {
-					margin-top: 10px;
-					border: 1px solid #ddd;
-					background-color: #fff;
-					border-radius: 5px;
-					padding: 10px 15px;
-				}
-			}
-
 			.loading {
 				text-align: center;
 				font-size: 24px;
@@ -218,45 +244,38 @@ html.rl-no-preview-pane {
 
 			.attachmentsPlace {
 
-				padding: 10px;
+				padding: 10px 10px 6px 10px;
+				background: #eee;
+				border-bottom: 1px solid #ddd;
+				position: relative;
 
 				.attachmentList {
-
 					margin: 0;
+				}
 
-					.attachmentItem {
+				.checkedParent {
+					display: none;
+				}
 
+				&.selection-mode {
+					.checkedParent {
 						display: inline-block;
-						margin: 5px;
-						padding: 5px;
-						max-width: 170px;
-						min-width: 60px;
-						overflow: hidden;
-						cursor: pointer;
-						list-style: none;
-						line-height: 24px;
-						border: 2px solid grey;
-						background-color: #fff;
-						box-shadow: 1px 1px 5px #ccc;
-						box-shadow: 1px 1px 5px rgba(0,0,0,0.1);
-
-						border-radius: 6px;
-
-						.attachmentIcon {
-							font-size: 23px;
-							width: 23px;
-							height: 23px;
-						}
-
-						.attachmentPreview {
-							color: #999;
-							margin: 0px 5px;
-						}
-						.attachmentPreview:hover {
-							color: #333;
-						}
 					}
 				}
+
+				.controls-handle {
+					position: absolute;
+					bottom: 5px;
+					right: 8px;
+					color: #999;
+					cursor: pointer;
+				}
+			}
+
+			.attachmentsControls {
+				padding: 7px 5px 7px 14px;
+				background: #e8e8e8;
+				border-bottom: 1px solid #ddd;
 			}
 
 			.rlBlockquoteSwitcher {
@@ -345,6 +364,12 @@ html.rl-no-preview-pane {
 							font-family: Monaco, Menlo, Consolas, 'Courier New', monospace;
 						}
 
+						pre.b-plain-openpgp {
+							display: inline-block;
+							padding: 6px 10px;
+							border: 1px dashed #666;
+						}
+
 						blockquote {
 							border-left: 2px solid blue;
 							color: blue;
@@ -366,17 +391,54 @@ html.rl-no-preview-pane {
 	}
 
 	&.message-focused .b-content {
-		z-index: 102;
-		box-shadow: 0px 2px 10px rgba(0, 0, 0, 0.3);
+		z-index: 101;
+		box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.2);
 		border-radius: @rlLowBorderRadius;
 		border-color: darken(@rlMainDarkColor, 5%);
 	}
+
+	.thread-controls {
+		.dropdown-toggle {
+			padding-left: 10px;
+			padding-right: 10px;
+		}
+		&.open .dropdown-toggle {
+			padding-left: 10px;
+			padding-right: 10px;
+		}
+	}
+
+	.thread-list {
+
+		.e-link {
+			padding: 4px 8px 6px 10px;
+		}
+
+		&.hide-more .thread-list-message.more-that {
+			display: none;
+		}
+
+		.thread-date {
+			font-size: 13px;
+			color: #999;
+		}
+
+		.more-threads {
+			text-align: center;
+			padding: 8px;
+			background-color: #F5F5F5;
+			color: #555;
+			text-decoration: underline;
+			border-top: 1px dashed #555;
+		}
+	}
 }
 
 html.rl-no-preview-pane .messageView {
 	.toolbar {
 		padding-left: @rlMainBorderSize;
 	}
+
 	.b-content {
 		top: 50px;
 		bottom: @rlBottomMargin;
@@ -384,10 +446,12 @@ html.rl-no-preview-pane .messageView {
 		border: @rlMainBorderSize solid @rlMainDarkColor;
 		box-shadow: @rlMainShadow;
 		border-radius: @rlMainBorderRadius;
+	}
+}
 
-		.buttonUp, .buttonUnFull, .buttonFull {
-			top: 70px;
-		}
+html.rl-bottom-preview-pane .messageView {
+	.b-content {
+		bottom: @rlBottomMargin;
 	}
 }
 
@@ -408,23 +472,26 @@ html.rl-message-fullscreen {
 	}
 
 	.messageView .b-content {
-		position: fixed;
-		margin: 5px;
-		top: 0;
-		left: 0;
-		right: 0;
-		bottom: 0;
-		z-index: 10000;
-		border: @rlLowBorderSize solid @rlMainDarkColor;
-		border-radius: @rlLowBorderRadius;
+		position: fixed !important;
+		margin: 5px !important;
+		top: 0 !important;
+		left: 0 !important;
+		right: 0 !important;
+		bottom: 0 !important;
+		z-index: 10000 !important;
+		border: @rlLowBorderSize solid @rlMainDarkColor  !important;
+		border-radius: @rlLowBorderRadius  !important;
 
-		.buttonUp, .buttonUnFull {
-			display: inline-block;
-			top: 36px;
+		.buttonUnFull {
+			display: inline-block !important;
 		}
 
 		.buttonFull {
-			display: none;
+			display: none  !important;
 		}
 	}
 }
+
+.nano-scrolllimit-top .buttonUp {
+	display: inline-block !important;
+}
diff --git a/dev/Styles/Scroll.less b/dev/Styles/Scroll.less
index e96a0766c..88bb59520 100644
--- a/dev/Styles/Scroll.less
+++ b/dev/Styles/Scroll.less
@@ -9,6 +9,10 @@
 	box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.3);
 }
 
+.fixIndex.nano:before, .fixIndex.nano:after {
+	z-index: 98;
+}
+
 .nano:before {
 	top: -10px;
 }
diff --git a/dev/Styles/Settings.less b/dev/Styles/Settings.less
index 238340f6d..f819129f7 100644
--- a/dev/Styles/Settings.less
+++ b/dev/Styles/Settings.less
@@ -10,6 +10,16 @@
 		padding: 8px 0 0 @rlLowMargin;
 	}
 
+	.b-footer {
+		position: absolute;
+		bottom: 20px;
+		right: 0;
+		left: 0;
+		height: 20px;
+		padding: 0 10px 0 5px;
+		z-index: 101;
+	}
+
 	.b-content {
 		position: absolute;
 		top: 50px + @rlLowMargin + 10px;
@@ -96,5 +106,9 @@
 	.b-settings-content {
 		padding: 20px;
 		padding-left: 30px;
+
+		.legend {
+			width: 600px;
+		}
 	}
 }
diff --git a/dev/Styles/SettingsAccounts.less b/dev/Styles/SettingsAccounts.less
index 806b646fe..7c6cd63f6 100644
--- a/dev/Styles/SettingsAccounts.less
+++ b/dev/Styles/SettingsAccounts.less
@@ -16,7 +16,15 @@
 			line-height: 30px;
 		}
 
-		.account-img {
+		.drag-handle {
+			color: #eee;
+		}
+
+		tr:hover .drag-handle {
+			color: #aaa;
+		}
+
+		.account-img, .identity-img {
 			font-size: 12px;
 			margin-right: 5px;
 		}
@@ -26,12 +34,32 @@
 			word-break: break-all;
 			box-sizing: border-box;
 			line-height: 22px;
-			cursor: default;
+		}
+
+		.identity-default {
+			cursor: pointer;
+			color: #ccc;
+		}
+		
+		.identity-name {
+			display: inline-block;
+			word-break: break-all;
+			box-sizing: border-box;
+			line-height: 22px;
+			cursor: pointer;
 		}
 	}
 
 	.account-item {
 
+		.e-action {
+			cursor: pointer;
+		}
+
+		.drag-handle {
+			cursor: pointer;
+		}
+
 		.button-delete {
 			margin-right: 15px;
 			margin-top: 5px;
@@ -51,5 +79,58 @@
 			cursor: pointer;
 			opacity: 0.5;
 		}
+
+		&.ui-sortable-helper {
+			background: #F5F5F5;
+			border: 1px solid #DDD;
+
+			.button-delete, .delete-account {
+				display: none;
+			}
+
+			td {
+				border-width: 0px;
+			}
+		}
+	}
+
+	.identity-item {
+
+		.e-action {
+			cursor: pointer;
+		}
+
+		.button-delete {
+			margin-right: 15px;
+			margin-top: 5px;
+			visibility: hidden;
+			opacity: 0;
+		}
+
+		.delete-access {
+			&.button-delete {
+				visibility: visible;
+				margin-right: 0;
+				opacity: 1;
+			}
+		}
+
+		.delete-identity {
+			cursor: pointer;
+			opacity: 0.5;
+		}
+
+		&.ui-sortable-helper {
+			background: #F5F5F5;
+			border: 1px solid #DDD;
+
+			.button-delete, .delete-identity {
+				display: none;
+			}
+
+			td {
+				border-width: 0px;
+			}
+		}
 	}
 }
\ No newline at end of file
diff --git a/dev/Styles/SettingsFilters.less b/dev/Styles/SettingsFilters.less
new file mode 100644
index 000000000..ea218b9fe
--- /dev/null
+++ b/dev/Styles/SettingsFilters.less
@@ -0,0 +1,92 @@
+
+.b-settings-filters {
+
+	.process-place {
+		text-align: center;
+		width: 600px;
+		padding: 14px 0;
+	}
+
+	.list-table {
+
+		width: 600px;
+
+		td {
+			padding: 4px 8px;
+			line-height: 30px;
+
+			&.drag-wrapper {
+				padding: 4px 0;
+			}
+		}
+
+		.drag-handle {
+			color: #eee;
+		}
+
+		tr:hover .drag-handle {
+			color: #aaa;
+		}
+
+		.filter-img {
+			font-size: 12px;
+			margin-right: 5px;
+		}
+
+		.filter-name, .filter-sub-name {
+			display: inline-block;
+			word-break: break-all;
+			box-sizing: border-box;
+			line-height: 22px;
+			cursor: pointer;
+		}
+
+		.filter-sub-name {
+			color: #aaa;
+		}
+	}
+
+	.filter-item {
+
+		.e-action {
+			cursor: pointer;
+		}
+
+		.drag-handle {
+			cursor: pointer;
+		}
+
+		.button-delete {
+			margin-right: 15px;
+			margin-top: 5px;
+			visibility: hidden;
+			opacity: 0;
+		}
+
+		.delete-access {
+			&.button-delete {
+				visibility: visible;
+				margin-right: 0;
+				opacity: 1;
+			}
+		}
+
+		.delete-filter {
+			cursor: pointer;
+			opacity: 0.5;
+		}
+
+		&.ui-sortable-helper {
+			background: #F5F5F5;
+			border: 1px solid #DDD;
+
+			.button-delete, .delete-filter {
+				display: none;
+			}
+
+			td {
+				border-width: 0px;
+			}
+		}
+	}
+}
diff --git a/dev/Styles/SettingsFolders.less b/dev/Styles/SettingsFolders.less
index ec9646b19..734f0e026 100644
--- a/dev/Styles/SettingsFolders.less
+++ b/dev/Styles/SettingsFolders.less
@@ -92,7 +92,7 @@
 		}
 
 		.unsubscribe-folder {
-			opacity: 0.25;
+			opacity: 0.2;
 		}
 	}
 
diff --git a/dev/Styles/SettingsGeneral.less b/dev/Styles/SettingsGeneral.less
index 4c82170e3..7d4a236be 100644
--- a/dev/Styles/SettingsGeneral.less
+++ b/dev/Styles/SettingsGeneral.less
@@ -1,25 +1,26 @@
-
-.b-settings-general {
-
-	.notification-desc-denied {
-		color: #999;
-		display: none;
-	}
-
-	.denied-by-browser {
-		.notification-desc-denied {
-			display: inline;
-		}
-		.notification-desc {
-			color: #999;
-		}
-	}
-
-	.flag-selector {
-		padding-top: 5px;
-	}
-
-	.flag-name {
-		border-bottom: 1px dashed #555;
-	}
-}
+
+.b-settings-general {
+
+	.notification-desc-denied {
+		color: #999;
+		display: none;
+	}
+
+	.denied-by-browser {
+		.notification-desc-denied {
+			display: inline;
+		}
+		.notification-desc {
+			color: #999;
+		}
+	}
+
+	.flag-selector {
+		padding-top: 5px;
+	}
+
+	.flag-name {
+		border-bottom: 1px dashed #555;
+		cursor: pointer;
+	}
+}
diff --git a/dev/Styles/SettingsIdentity.less b/dev/Styles/SettingsIdentity.less
deleted file mode 100644
index e88b2ea3c..000000000
--- a/dev/Styles/SettingsIdentity.less
+++ /dev/null
@@ -1,8 +0,0 @@
-
-.b-settings-identity {
-	.e-signature-place {
-		display: inline-block;
-		width: 680px;
-		height: 250px;
-	}
-}
diff --git a/dev/Styles/SettingsIdentities.less b/dev/Styles/SettingsTemplates.less
similarity index 62%
rename from dev/Styles/SettingsIdentities.less
rename to dev/Styles/SettingsTemplates.less
index 98917e230..09ad48c32 100644
--- a/dev/Styles/SettingsIdentities.less
+++ b/dev/Styles/SettingsTemplates.less
@@ -1,5 +1,5 @@
 
-.b-settings-identities {
+.b-settings-templates {
 
 	.process-place {
 		text-align: center;
@@ -7,12 +7,6 @@
 		padding: 14px 0;
 	}
 
-	.e-signature-place {
-		display: inline-block;
-		width: 680px;
-		height: 250px;
-	}
-
 	.list-table {
 
 		width: 600px;
@@ -22,26 +16,37 @@
 			line-height: 30px;
 		}
 
-		.identity-img {
+		.drag-handle {
+			color: #eee;
+		}
+
+		tr:hover .drag-handle {
+			color: #aaa;
+		}
+
+		.template-img {
 			font-size: 12px;
 			margin-right: 5px;
 		}
 
-		.identity-name {
+		.template-name {
 			display: inline-block;
 			word-break: break-all;
 			box-sizing: border-box;
 			line-height: 22px;
-			cursor: pointer;
 		}
 	}
 
-	.identity-item {
+	.template-item {
 
 		.e-action {
 			cursor: pointer;
 		}
 
+		.drag-handle {
+			cursor: pointer;
+		}
+
 		.button-delete {
 			margin-right: 15px;
 			margin-top: 5px;
@@ -57,9 +62,22 @@
 			}
 		}
 
-		.delete-identity {
+		.delete-template {
 			cursor: pointer;
 			opacity: 0.5;
 		}
+
+		&.ui-sortable-helper {
+			background: #F5F5F5;
+			border: 1px solid #DDD;
+
+			.button-delete, .delete-template {
+				display: none;
+			}
+
+			td {
+				border-width: 0px;
+			}
+		}
 	}
-}
+}
\ No newline at end of file
diff --git a/dev/Styles/SettingsThemes.less b/dev/Styles/SettingsThemes.less
index 415059ae6..33310d449 100644
--- a/dev/Styles/SettingsThemes.less
+++ b/dev/Styles/SettingsThemes.less
@@ -1,33 +1,38 @@
-
-.b-settings-themes {
-}
-
-.b-themes-list {
-
-	.e-item {
-
-		display: inline-block;
-		border: 2px solid transparent;
-		cursor: pointer;
-
-		color: #000;
-		background-color: #fff;
-		padding: 16px;
-		margin: 5px;
-
-		&:hover {
-			border: 2px solid grey;
-		}
-
-		&.selected {
-			background-color: #eee;
-			border: 2px solid #000;
-		}
-
-		.e-image {
-			width: 100px;
-			height: 100px;
-			border: 1px solid #ddd;
-		}
-	}
-}
+
+.b-settings-content {
+	.b-settings-themes {
+		.legend {
+			width: 100%;
+		}
+	}
+}
+
+.b-themes-list {
+
+	.e-item {
+
+		display: inline-block;
+		border: 2px solid transparent;
+		cursor: pointer;
+
+		color: #000;
+		background-color: #fff;
+		padding: 16px;
+		margin: 5px;
+
+		&:hover {
+			border: 2px solid grey;
+		}
+
+		&.selected {
+			background-color: #eee;
+			border: 2px solid #000;
+		}
+
+		.e-image {
+			width: 100px;
+			height: 100px;
+			border: 1px solid #ddd;
+		}
+	}
+}
diff --git a/dev/Styles/SystemDropDown.less b/dev/Styles/SystemDropDown.less
index 404be4649..32422839f 100644
--- a/dev/Styles/SystemDropDown.less
+++ b/dev/Styles/SystemDropDown.less
@@ -1,34 +1,188 @@
-
-.b-system-drop-down {
-
-	.b-toolbar {
-		position: absolute;
-		top: 0;
-		right: 0;
-		height: 30px;
-		padding: 10px @rlLowMargin;
-		z-index: 103;
-	}
-
-	.e-facebook-name {
-		display: inline-block;
-		padding-top: 4px;
-	}
-
-	.btn.system-dropdown {
-		padding-left: 10px;
-		padding-right: 10px;
-	}
-
-	.button-fb-logout {
-		margin: 5px;
-	}
-
-	.email-title {
-		display: inline-block;
-		max-width: 200px;
-		text-align: left;
-		text-overflow: ellipsis;
-		overflow: hidden;
-	}
+
+.b-system-drop-down {
+
+	.b-toolbar {
+		position: absolute;
+		top: 0;
+		right: 0;
+		height: 30px;
+		padding: 10px @rlLowMargin;
+		z-index: 103;
+	}
+
+	.e-facebook-name {
+		display: inline-block;
+		padding-top: 4px;
+	}
+
+	.btn.system-dropdown {
+		padding-left: 10px;
+		padding-right: 10px;
+	}
+
+	.button-fb-logout {
+		margin: 5px;
+	}
+
+	.email-title {
+		display: inline-block;
+		max-width: 200px;
+		text-align: left;
+		text-overflow: ellipsis;
+		overflow: hidden;
+		margin-right: 28px;
+		vertical-align: middle;
+	}
+
+	.audioPlace {
+
+		font-size: 25px;
+		line-height: 30px;
+		margin-right: 15px;
+
+		.playIcon, .stopIcon {
+			cursor: pointer;
+			color: orange;
+			text-shadow: 0 1px 0 #555;
+		}
+
+		.playIcon {
+			margin-top: 5px;
+		}
+
+		.stopIcon {
+			font-size: 30px;
+			line-height: 30px;
+			margin-right: 10px;
+		}
+
+		.stopIcon {
+			display: none;
+		}
+
+		&:hover {
+
+			.playIcon {
+				display: none;
+			}
+
+			.stopIcon {
+				display: inline-block;
+			}
+		}
+	}
+
+	.accountPlace {
+		background-color: #000;
+		background-color: rgba(0, 0, 0, 0.5);
+
+		color: #fff;
+		text-shadow: 0 1px 0 #000;
+
+		display: inline-block;
+		height: 29px;
+		max-width: 250px;
+		border-radius: 4px;
+		font-size: 16px;
+		line-height: 30px;
+		padding: 1px 8px;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		border-radius: 4px;
+		font-weight: bold;
+		margin-right: 5px;
+
+		white-space: nowrap;
+	}
+
+	.account-item {
+
+		.icon-ok {
+			display: none;
+		}
+
+		&.current {
+			.icon-ok {
+				display: inline-block;
+			}
+			.icon-user {
+				display: none;
+			}
+		}
+	}
+
+	.counter {
+		display: inline-block;
+	}
+
+	.g-ui-menu .e-link.account-item {
+		padding-right: 5px;
+	}
+}
+
+html.ssm-state-mobile .accountPlace {
+	max-width: 150px !important;
+}
+
+@keyframes firstBar {
+  0%   { height: 30%; }
+  50%  { height: 100%; }
+  100% { height: 30%; }
+}
+
+@keyframes secondBar {
+  0%   { height: 90%; }
+  50%  { height: 30%; }
+  100% { height: 100%; }
+}
+
+@keyframes thirdBar {
+  0%   { height: 20%; }
+  40%  { height: 40%; }
+  60%  { height: 80%; }
+  100% { height: 40%; }
+}
+
+.equaliser {
+
+	width: 20px;
+	height: 20px;
+	position: relative;
+
+	.bar {
+		float:left;
+		width: 5px;
+		height: 5px;
+		background: orange;
+		position: absolute;
+		bottom:0;
+	}
+
+
+	.first {
+		left: 0px;
+		animation: none;
+	}
+	.second {
+		left: 7px;
+		animation: none;
+	}
+	.third {
+		left: 14px;
+		animation: none;
+	}
+
+	&.animated {
+		.first {
+			animation: firstBar 1s infinite;
+		}
+		.second {
+			animation: secondBar 1s infinite;
+		}
+		.third {
+			animation: thirdBar 1s infinite;
+		}
+	}
+
+
 }
\ No newline at end of file
diff --git a/dev/Styles/Template.less b/dev/Styles/Template.less
new file mode 100644
index 000000000..5da4d9f1e
--- /dev/null
+++ b/dev/Styles/Template.less
@@ -0,0 +1,16 @@
+.popups {
+	.b-template-add-content {
+
+		&.modal {
+			width: 750px;
+		}
+
+		.modal-header {
+			background-color: #fff;
+		}
+
+		.e-template-place {
+			height: 300px;
+		}
+	}
+}
diff --git a/dev/Styles/TwoFactor.less b/dev/Styles/TwoFactor.less
index 6b480cc48..4cb3e23bb 100644
--- a/dev/Styles/TwoFactor.less
+++ b/dev/Styles/TwoFactor.less
@@ -1,4 +1,18 @@
 .popups {
+
+	.b-two-factor-content {
+
+		width: 750px;
+
+		.modal-body {
+			min-height: 100px;
+		}
+
+		.modal-header {
+			background-color: #fff;
+		}
+	}
+
 	.b-two-factor-test-content {
 		.modal-header {
 			background-color: #fff;
diff --git a/dev/Styles/Ui.less b/dev/Styles/Ui.less
index b1c4f6843..dbfa7b86b 100644
--- a/dev/Styles/Ui.less
+++ b/dev/Styles/Ui.less
@@ -1,9 +1,9 @@
 
 .g-ui-user-select-none {
-	webkit-touch-callout: none;
-	user-select: none;
-	standard-user-select: none;
-	touch-callout: none;
+//	webkit-touch-callout: none;
+//	user-select: none;
+//	standard-user-select: none;
+//	touch-callout: none;
 }
 
 .g-ui-user-select-allow {
@@ -168,6 +168,28 @@ e-spinner {
 	display: none;
 }
 
+.e-powered {
+	margin-top: 8px;
+	color: #333;
+	a {
+		color: #333;
+		text-decoration: underline;
+		&:hover {
+			color: #333;
+		}
+	}
+}
+
+.e-languages {
+	margin-top: 8px;
+	color: #333;
+	.flag-name {
+		color: #333;
+		/*text-decoration: underline;*/
+		border-bottom: 1px dashed #333;
+	}
+}
+
 html.cssanimations {
 
 	.e-spinner {
@@ -204,4 +226,8 @@ html.cssanimations {
 @keyframes bouncedelay {
   0%, 80%, 100% { transform: scale(0.0); }
   40% { transform: scale(1.0); }
-}
\ No newline at end of file
+}
+
+.command.command-disabled.hide-on-disabled-command {
+	display:none;
+}
diff --git a/dev/Styles/WelcomPage.less b/dev/Styles/WelcomPage.less
new file mode 100644
index 000000000..731faab8b
--- /dev/null
+++ b/dev/Styles/WelcomPage.less
@@ -0,0 +1,9 @@
+.popups {
+	.b-welcom-page-content {
+
+		.modal-header {
+			background-color: #fff;
+		}
+
+	}
+}
diff --git a/dev/Styles/_BootstrapFix.less b/dev/Styles/_BootstrapFix.less
index fc84ecec3..9bb2619b8 100644
--- a/dev/Styles/_BootstrapFix.less
+++ b/dev/Styles/_BootstrapFix.less
@@ -11,6 +11,32 @@ label.inline {
 	display: inline-block;
 }
 
+.close-custom {
+	.close;
+}
+
+button.close-custom {
+	padding: 0;
+	cursor: pointer;
+	background: transparent;
+	border: 0;
+	-webkit-appearance: none;
+}
+
+.minimize-custom {
+	border: 0px solid #333;
+	border-bottom-width: 3px;
+	display: inline-block;
+	float: right;
+	height: 20px;
+	width: 16px;
+	font-size: 20px;
+	font-weight: bold;
+	line-height: 20px;
+	margin-right: 15px;
+	cursor: pointer;
+}
+
 .legend {
 	display: block;
 	width: 100%;
@@ -47,6 +73,10 @@ select {
 	line-height: 11px;
 }
 
+.btn.btn-thin {
+	padding: 4px 9px;
+}
+
 .btn.btn-ellipsis {
 	text-overflow: ellipsis;
 	overflow: hidden;
@@ -62,6 +92,10 @@ select {
 	margin-left: 0px;
 }
 
+.btn.btn-large {
+	font-size: 17px;
+}
+
 .dropdown-menu {
 	border-radius: @btnBorderRadius;
 }
@@ -71,7 +105,7 @@ select {
 }
 
 .btn {
-	border-radiu: @btnBorderRadius;
+	border-radius: @btnBorderRadius;
 	background-image: none;
 	padding-left: 13px;
 	padding-right: 13px;
@@ -127,14 +161,13 @@ html.rgba.textshadow {
 		opacity: 1;
 	}
 
-	.tooltip-class {
-	}
-
 	.tooltip-inner {
 		max-width: 380px;
 		text-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
 		padding: 5px 10px;
 		border-radius: 2px;
+		overflow: hidden;
+		text-overflow: ellipsis;
 	}
 
 	.tooltip-big {
@@ -144,26 +177,22 @@ html.rgba.textshadow {
 	}
 }
 
-// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match
-.btn-group > .btn:first-child {
-	border-top-left-radius: @btnBorderRadius;
-	border-bottom-left-radius: @btnBorderRadius;
+.btn-group > .btn {
+	border-radius: 0 !important;
 }
-// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it
-.btn-group > .btn:last-child,
-.btn-group > .dropdown-toggle {
-	border-top-right-radius: @btnBorderRadius;
-	border-bottom-right-radius: @btnBorderRadius;
+
+.btn-group > .btn.single {
+	border-radius: @btnBorderRadius !important;
 }
-// Reset corners for large buttons
-.btn-group > .btn.large:first-child {
-	border-top-left-radius: @btnBorderRadius;
-	border-bottom-left-radius: @btnBorderRadius;
+
+.btn-group > .btn.first {
+	border-top-left-radius: @btnBorderRadius !important;
+	border-bottom-left-radius: @btnBorderRadius !important;
 }
-.btn-group > .btn.large:last-child,
-.btn-group > .large.dropdown-toggle {
-	border-top-right-radius: @btnBorderRadius;
-	border-bottom-right-radius: @btnBorderRadius;
+
+.btn-group > .btn.last {
+	border-top-right-radius: @btnBorderRadius !important;
+	border-bottom-right-radius: @btnBorderRadius !important;
 }
 
 .dropdown.colored-toggle.open .btn.dropdown-toggle {
@@ -206,6 +235,14 @@ select {
 	margin-left: 0;
 }
 
+.alert a {
+	color: #c09853;
+}
+
+.alert.alert-info a {
+	color: #3a87ad;
+}
+
 .alert.alert-error a {
 	color: #b94a48;
 }
@@ -223,7 +260,8 @@ html.no-rgba .modal {
 }
 
 .modal-backdrop, .modal-backdrop.fade.in {
-	.opacity(10);
+	.opacity(20);
+	transform: none;
 }
 
 .popups {
@@ -262,6 +300,10 @@ html.no-rgba .modal {
 	top: 0;
 }
 
+.modal-backdrop {
+	.g-ui-user-select-none();
+}
+
 .modal.loginContent .modal-body, .modal.loginAdminContent .modal-body {
 	background-color: transparent !important;
 }
diff --git a/dev/Styles/_CkeFix.less b/dev/Styles/_CkeFix.less
index 039b471c5..a7b486efc 100644
--- a/dev/Styles/_CkeFix.less
+++ b/dev/Styles/_CkeFix.less
@@ -2,6 +2,40 @@
 	display: none !important;
 }*/
 
+.cke_chrome {
+	border: 1px solid #ccc !important;
+}
+
+.cke_toolgroup, .cke_combo_button {
+	border: 1px solid #A6A6A6 !important;
+}
+
+.cke_top {
+	padding: 6px 4px 1px 6px !important;
+	-moz-box-shadow: none !important;
+	-webkit-box-shadow: none !important;
+	box-shadow: none !important;
+}
+
+.cke_button {
+	padding: 5px 6px !important;
+}
+
+.cke_combo_text {
+	line-height: 28px !important;
+	padding-left: 8px !important;
+}
+
+.cke_combo_button {
+	background: #FBFBFB !important;
+}
+
+.cke_source {
+	font-family: Monaco, Menlo, Consolas, 'Courier New', monospace !important;
+	padding: 10px !important;
+	padding-right: 0 !important;
+}
+
 .cke_plain {
 
 	background-color: #fff;
@@ -38,7 +72,7 @@
 }
 
 .cke_wysiwyg_div {
-	
+
 	padding: 10px !important;
 	font-family: arial, sans-serif;
 	font-size: 13px;
@@ -74,11 +108,10 @@
 		padding-left: 5px;
 	}
 
-	blockquote * {
-		font-family: arial,sans-serif;
-		font-size: 13px;
-		line-height: 16px;
-		color: #333;
+	blockquote p {
+		margin: 0 0 10px;
+		font-size: 14px;
+		line-height: 20px;
 	}
 
 	img {
@@ -97,3 +130,31 @@
 		}
 	}
 }
+
+.cke_dialog {
+
+	a:hover {
+		text-decoration: none;
+	}
+
+	.cke_dialog_ui_labeled_content {
+		margin-top: 5px;
+		margin-bottom: 5px;
+	}
+
+	.cke_dialog_ui_input_select, .cke_dialog_ui_input_text, .cke_dialog_ui_input_textarea {
+
+		box-shadow: none;
+		border-radius: 2px;
+
+		&:focus {
+			outline: 0;
+			border: 1px solid #999;
+		}
+	}
+
+	.cke_dialog_ui_input_select, .cke_dialog_ui_input_text {
+		height: 25px;
+		line-height: 25px;
+	}
+}
\ No newline at end of file
diff --git a/dev/Styles/_End.less b/dev/Styles/_End.less
index 64abb8c8e..5fcebedea 100644
--- a/dev/Styles/_End.less
+++ b/dev/Styles/_End.less
@@ -1,8 +1,71 @@
-
-.rl-view-model {
-	&.RL-Login, &.RL-LoginNew, &.RL-AdminLogin, &.RL-About {
-		position: relative;
-		height: 100%;
-		z-index: 5;
-	}
-}
\ No newline at end of file
+
+.rl-view-model {
+	&.RL-Login, &.RL-LoginNew, &.RL-AdminLogin, &.RL-About {
+		position: relative;
+		height: 100%;
+		z-index: 5;
+	}
+}
+
+.mfp-container .mfp-figure {
+	min-height: 300px;
+	min-width: 300px;
+}
+
+.ui-resizable-e {
+	right: -1px;
+	width: 5px;
+}
+
+.pswp__error-msg {
+	color: #ccc;
+	a, a:hover {
+		color: #fff;
+	}
+}
+
+.pswp__img--placeholder {
+	opacity: 0;
+}
+
+#rl-content {
+	display: block;
+}
+
+.opentip-container {
+
+	z-index: 2001 !important;
+	.ot-content {
+		font-size: 13px;
+	}
+
+	&.style-rainloopErrorTip .ot-content {
+		color: red;
+	}
+
+	&.ot-show-effect-none, &.ot-hide-effect-none {
+		transition: none !important;
+	}
+
+	&.ot-show-effect-fade {
+
+		transition: none;
+
+		&.ot-hidden {
+			opacity: 0;
+		}
+
+		&.ot-going-to-show {
+			opacity: 0;
+			transition: opacity 0.2s ease-in-out;
+		}
+		&.ot-showing {
+			opacity: 1;
+			transition: opacity 0.2s ease-in-out;
+		}
+		&.ot-visible {
+			opacity: 1;
+			transition: none;
+		}
+	}
+}
diff --git a/dev/Styles/_FontasticToBoot.less b/dev/Styles/_FontasticToBoot.less
index ad6c9df3d..9e61f787a 100644
--- a/dev/Styles/_FontasticToBoot.less
+++ b/dev/Styles/_FontasticToBoot.less
@@ -22,6 +22,11 @@
 	font-size: 24px;
 }
 
+.iconsize24 {
+	line-height: 20px;
+	font-size: 20px;
+}
+
 /* White icons with optional class, or on hover/active states of certain elements */
 .icon-white,
 .nav > .active > a > [class^="icon-"],
@@ -58,6 +63,26 @@
 	}
 }
 
+.iconcolor-display-none {
+	display: none;
+}
+
+.iconcolor-green {
+	color: green;
+}
+
+.iconcolor-red {
+	color: red;
+}
+
+.iconcolor-white {
+	color: white;
+}
+
+.iconcolor-grey {
+	color: #aaa;
+}
+
 .denied-by-browser {
 	cursor: default;
 	.icon-checkbox-checked, icon-checkbox-unchecked {
@@ -70,6 +95,7 @@
 	line-height: 20px;
 }
 
+/*
 @keyframes rotation {
 	to   {transform: rotate(1turn);}
 }
@@ -86,19 +112,76 @@
 		line-height: 100%;
 	}
 }
+*/
+
+@keyframes rotation {
+	to   {transform: rotate(1turn);}
+}
+
+.icon-spinner {
+
+	font-family: Arial;
+
+	&:before {
+		content: "";
+	}
+
+	height: 11px;
+	width: 11px;
+
+	margin-right: -1px;
+	background: none;
+
+	border: 3px solid #aaa;
+	border-top-color: #333;
+	border-radius: 100%;
+
+	animation: none;
+
+	&.animated {
+		border-color: transparent;
+		border-top-color: #999;
+
+		animation: rotation .8s infinite linear;
+	}
+
+	&.big {
+
+		height: 13px;
+		width: 13px;
+
+		margin-top: -2px;
+		margin-left: -2px;
+	}
+
+	&.white, &.icon-white {
+
+		border-color: #fff;
+		border-top-color: #999;
+
+		&.animated {
+			border-color: transparent;
+			border-top-color: #fff;
+		}
+	}
+}
 
 html.no-cssanimations .icon-spinner {
+	background: none;
 	background-image: url('@{rlSyncFileStop}');
 	background-repeat: no-repeat;
+
+	border: none;
+	box-shadow: none;
+	height: 16px;
+	width: 16px;
+
 	font-family: Arial;
 	&:before {
 		content: "";
 	}
 	&.animated {
+		background: none;
 		background-image: url('@{rlSyncFile}');
 	}
 }
-
-html.cssanimations .icon-spinner.animated:before {
-	animation: rotation 1s infinite steps(40);
-}
diff --git a/dev/Styles/_InputosaurusFix.less b/dev/Styles/_InputosaurusFix.less
index d2ea35f00..1978b5bea 100644
--- a/dev/Styles/_InputosaurusFix.less
+++ b/dev/Styles/_InputosaurusFix.less
@@ -55,6 +55,10 @@
 		&.inputosaurus-selected {
 			background-color: #ddd;
 		}
+
+		&.pgp {
+			background-color: #E5F3E2;
+		}
 	}
 
 	.inputosaurus-input {
diff --git a/dev/Styles/_Values.less b/dev/Styles/_Values.less
index a30e71fd4..c81735ba1 100644
--- a/dev/Styles/_Values.less
+++ b/dev/Styles/_Values.less
@@ -10,7 +10,7 @@
 @rlMainBorderRadius: 5px;
 @rlLowBorderRadius: 3px;
 
-@rlMainShadow: 0px 2px 8px rgba(0, 0, 0, 0.3);
+@rlMainShadow: 0px 2px 8px rgba(0, 0, 0, 0.2);
 
 @rlLowMargin: 8px;
 @rlBottomMargin: 5px;
diff --git a/dev/View/Admin/Login.js b/dev/View/Admin/Login.js
index 57357ea55..e6a64c8ec 100644
--- a/dev/View/Admin/Login.js
+++ b/dev/View/Admin/Login.js
@@ -9,8 +9,10 @@
 
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
 
-		Remote = require('Storage/Admin/Remote'),
+		Settings = require('Storage/Settings'),
+		Remote = require('Remote/Admin/Ajax'),
 
 		kn = require('Knoin/Knoin'),
 		AbstractView = require('Knoin/AbstractView')
@@ -24,14 +26,25 @@
 	{
 		AbstractView.call(this, 'Center', 'AdminLogin');
 
+		this.logoPowered = !!Settings.settingsGet('LoginPowered');
+
 		this.login = ko.observable('');
 		this.password = ko.observable('');
 
 		this.loginError = ko.observable(false);
 		this.passwordError = ko.observable(false);
 
+		this.loginErrorAnimation = ko.observable(false).extend({'falseTimeout': 500});
+		this.passwordErrorAnimation = ko.observable(false).extend({'falseTimeout': 500});
+
 		this.loginFocus = ko.observable(false);
 
+		this.formHidden = ko.observable(false);
+
+		this.formError = ko.computed(function () {
+			return this.loginErrorAnimation() || this.passwordErrorAnimation();
+		}, this);
+
 		this.login.subscribe(function () {
 			this.loginError(false);
 		}, this);
@@ -40,6 +53,14 @@
 			this.passwordError(false);
 		}, this);
 
+		this.loginError.subscribe(function (bV) {
+			this.loginErrorAnimation(!!bV);
+		}, this);
+
+		this.passwordError.subscribe(function (bV) {
+			this.passwordErrorAnimation(!!bV);
+		}, this);
+
 		this.submitRequest = ko.observable(false);
 		this.submitError = ko.observable('');
 
@@ -47,6 +68,9 @@
 
 			Utils.triggerAutocompleteInputChange();
 
+			this.loginError(false);
+			this.passwordError(false);
+
 			this.loginError('' === Utils.trim(this.login()));
 			this.passwordError('' === Utils.trim(this.password()));
 
@@ -63,18 +87,19 @@
 				{
 					if (oData.Result)
 					{
-						require('App/Admin').loginAndLogoutReload();
+						this.formHidden(true);
+						require('App/Admin').loginAndLogoutReload(true);
 					}
 					else if (oData.ErrorCode)
 					{
 						this.submitRequest(false);
-						this.submitError(Utils.getNotification(oData.ErrorCode));
+						this.submitError(Translator.getNotification(oData.ErrorCode));
 					}
 				}
 				else
 				{
 					this.submitRequest(false);
-					this.submitError(Utils.getNotification(Enums.Notification.UnknownError));
+					this.submitError(Translator.getNotification(Enums.Notification.UnknownError));
 				}
 
 			}, this), this.login(), this.password());
diff --git a/dev/View/Admin/Settings/Menu.js b/dev/View/Admin/Settings/Menu.js
index a9693e246..5aab0d423 100644
--- a/dev/View/Admin/Settings/Menu.js
+++ b/dev/View/Admin/Settings/Menu.js
@@ -37,6 +37,39 @@
 		return '#/' + sRoute;
 	};
 
+	MenuSettingsAdminView.prototype.onBuild = function (oDom)
+	{
+		key('up, down', _.throttle(function (event, handler) {
+
+			var
+				sH = '',
+				iIndex = -1,
+				bUp = handler && 'up' === handler.shortcut,
+				$items = $('.b-admin-menu .e-item', oDom)
+			;
+
+			if (event && $items.length)
+			{
+				iIndex = $items.index($items.filter('.selected'));
+				if (bUp && iIndex > 0)
+				{
+					iIndex--;
+				}
+				else if (!bUp && iIndex < $items.length - 1)
+				{
+					iIndex++;
+				}
+
+				sH = $items.eq(iIndex).attr('href');
+				if (sH)
+				{
+					kn.setHash(sH, false, true);
+				}
+			}
+
+		}, 200));
+	};
+
 	module.exports = MenuSettingsAdminView;
 
 }());
diff --git a/dev/View/Admin/Settings/Pane.js b/dev/View/Admin/Settings/Pane.js
index 4ef5f32d4..fa6ff52a2 100644
--- a/dev/View/Admin/Settings/Pane.js
+++ b/dev/View/Admin/Settings/Pane.js
@@ -8,8 +8,7 @@
 		ko = require('ko'),
 
 		Settings = require('Storage/Settings'),
-		Data = require('Storage/Admin/Data'),
-		Remote = require('Storage/Admin/Remote'),
+		Remote = require('Remote/Admin/Ajax'),
 
 		kn = require('Knoin/Knoin'),
 		AbstractView = require('Knoin/AbstractView')
@@ -26,7 +25,19 @@
 		this.adminDomain = ko.observable(Settings.settingsGet('AdminDomain'));
 		this.version = ko.observable(Settings.settingsGet('Version'));
 
-		this.adminManLoadingVisibility = Data.adminManLoadingVisibility;
+		this.capa = !!Settings.settingsGet('PremType');
+
+		this.adminManLoading = ko.computed(function () {
+			return '000' !== [
+				require('Stores/Admin/Domain').domains.loading() ? '1' : '0',
+				require('Stores/Admin/Plugin').plugins.loading() ? '1' : '0',
+				require('Stores/Admin/Package').packages.loading() ? '1' : '0'
+			].join('');
+		}, this);
+
+		this.adminManLoadingVisibility = ko.computed(function () {
+			return this.adminManLoading() ? 'visible' : 'hidden';
+		}, this).extend({'rateLimit': 300});
 
 		kn.constructorEnd(this);
 	}
@@ -37,7 +48,7 @@
 	PaneSettingsAdminView.prototype.logoutClick = function ()
 	{
 		Remote.adminLogout(function () {
-			require('App/Admin').loginAndLogoutReload();
+			require('App/Admin').loginAndLogoutReload(true, true);
 		});
 	};
 
diff --git a/dev/View/App/AbstractSystemDropDown.js b/dev/View/App/AbstractSystemDropDown.js
deleted file mode 100644
index 4f8599473..000000000
--- a/dev/View/App/AbstractSystemDropDown.js
+++ /dev/null
@@ -1,120 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		window = require('window'),
-		_ = require('_'),
-		ko = require('ko'),
-		key = require('key'),
-
-		Enums = require('Common/Enums'),
-		Utils = require('Common/Utils'),
-		LinkBuilder = require('Common/LinkBuilder'),
-
-		Settings = require('Storage/Settings'),
-		Data = require('Storage/App/Data'),
-		Remote = require('Storage/App/Remote'),
-
-		AbstractView = require('Knoin/AbstractView')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractView
-	 */
-	function AbstractSystemDropDownAppView()
-	{
-		AbstractView.call(this, 'Right', 'SystemDropDown');
-
-		this.accounts = Data.accounts;
-		this.accountEmail = Data.accountEmail;
-		this.accountsLoading = Data.accountsLoading;
-
-		this.accountMenuDropdownTrigger = ko.observable(false);
-
-		this.capaAdditionalAccounts = Settings.capa(Enums.Capa.AdditionalAccounts);
-
-		this.loading = ko.computed(function () {
-			return this.accountsLoading();
-		}, this);
-
-		this.accountClick = _.bind(this.accountClick, this);
-	}
-
-	_.extend(AbstractSystemDropDownAppView.prototype, AbstractView.prototype);
-
-	AbstractSystemDropDownAppView.prototype.accountClick = function (oAccount, oEvent)
-	{
-		if (oAccount && oEvent && !Utils.isUnd(oEvent.which) && 1 === oEvent.which)
-		{
-			var self = this;
-			this.accountsLoading(true);
-			_.delay(function () {
-				self.accountsLoading(false);
-			}, 1000);
-		}
-
-		return true;
-	};
-
-	AbstractSystemDropDownAppView.prototype.emailTitle = function ()
-	{
-		return Data.accountEmail();
-	};
-
-	AbstractSystemDropDownAppView.prototype.settingsClick = function ()
-	{
-		require('Knoin/Knoin').setHash(LinkBuilder.settings());
-	};
-
-	AbstractSystemDropDownAppView.prototype.settingsHelp = function ()
-	{
-		require('Knoin/Knoin').showScreenPopup(require('View/Popup/KeyboardShortcutsHelp'));
-	};
-
-	AbstractSystemDropDownAppView.prototype.addAccountClick = function ()
-	{
-		if (this.capaAdditionalAccounts)
-		{
-			require('Knoin/Knoin').showScreenPopup(require('View/Popup/AddAccount'));
-		}
-	};
-
-	AbstractSystemDropDownAppView.prototype.logoutClick = function ()
-	{
-		Remote.logout(function () {
-			if (window.__rlah_clear)
-			{
-				window.__rlah_clear();
-			}
-
-			require('App/App').loginAndLogoutReload(true,
-				Settings.settingsGet('ParentEmail') && 0 < Settings.settingsGet('ParentEmail').length);
-		});
-	};
-
-	AbstractSystemDropDownAppView.prototype.onBuild = function ()
-	{
-		var self = this;
-		key('`', [Enums.KeyState.MessageList, Enums.KeyState.MessageView, Enums.KeyState.Settings], function () {
-			if (self.viewModelVisibility())
-			{
-				self.accountMenuDropdownTrigger(true);
-			}
-		});
-
-		// shortcuts help
-		key('shift+/', [Enums.KeyState.MessageList, Enums.KeyState.MessageView, Enums.KeyState.Settings], function () {
-			if (self.viewModelVisibility())
-			{
-				require('Knoin/Knoin').showScreenPopup(require('View/Popup/KeyboardShortcutsHelp'));
-				return false;
-			}
-		});
-	};
-
-	module.exports = AbstractSystemDropDownAppView;
-
-}());
\ No newline at end of file
diff --git a/dev/View/App/MailBox/MessageView.js b/dev/View/App/MailBox/MessageView.js
deleted file mode 100644
index 4330ca888..000000000
--- a/dev/View/App/MailBox/MessageView.js
+++ /dev/null
@@ -1,722 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-		$ = require('$'),
-		ko = require('ko'),
-		key = require('key'),
-
-		Consts = require('Common/Consts'),
-		Enums = require('Common/Enums'),
-		Globals = require('Common/Globals'),
-		Utils = require('Common/Utils'),
-		Events = require('Common/Events'),
-
-		Cache = require('Storage/App/Cache'),
-		Data = require('Storage/App/Data'),
-		Remote = require('Storage/App/Remote'),
-
-		kn = require('Knoin/Knoin'),
-		AbstractView = require('Knoin/AbstractView')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractView
-	 */
-	function MessageViewMailBoxAppView()
-	{
-		AbstractView.call(this, 'Right', 'MailMessageView');
-
-		var
-			self = this,
-			sLastEmail = '',
-			createCommandHelper = function (sType) {
-				return Utils.createCommand(self, function () {
-					this.replyOrforward(sType);
-				}, self.canBeRepliedOrForwarded);
-			}
-		;
-
-		this.oMessageScrollerDom = null;
-
-		this.message = Data.message;
-		this.currentMessage = Data.currentMessage;
-		this.messageListChecked = Data.messageListChecked;
-		this.hasCheckedMessages = Data.hasCheckedMessages;
-		this.messageListCheckedOrSelectedUidsWithSubMails = Data.messageListCheckedOrSelectedUidsWithSubMails;
-		this.messageLoading = Data.messageLoading;
-		this.messageLoadingThrottle = Data.messageLoadingThrottle;
-		this.messagesBodiesDom = Data.messagesBodiesDom;
-		this.useThreads = Data.useThreads;
-		this.replySameFolder = Data.replySameFolder;
-		this.layout = Data.layout;
-		this.usePreviewPane = Data.usePreviewPane;
-		this.isMessageSelected = Data.isMessageSelected;
-		this.messageActiveDom = Data.messageActiveDom;
-		this.messageError = Data.messageError;
-
-		this.fullScreenMode = Data.messageFullScreenMode;
-
-		this.showFullInfo = ko.observable(false);
-		this.moreDropdownTrigger = ko.observable(false);
-		this.messageDomFocused = ko.observable(false).extend({'rateLimit': 0});
-
-		this.messageVisibility = ko.computed(function () {
-			return !this.messageLoadingThrottle() && !!this.message();
-		}, this);
-
-		this.message.subscribe(function (oMessage) {
-			if (!oMessage)
-			{
-				this.currentMessage(null);
-			}
-		}, this);
-
-		this.canBeRepliedOrForwarded = this.messageVisibility;
-
-		// commands
-		this.closeMessage = Utils.createCommand(this, function () {
-			Data.message(null);
-		});
-
-		this.replyCommand = createCommandHelper(Enums.ComposeType.Reply);
-		this.replyAllCommand = createCommandHelper(Enums.ComposeType.ReplyAll);
-		this.forwardCommand = createCommandHelper(Enums.ComposeType.Forward);
-		this.forwardAsAttachmentCommand = createCommandHelper(Enums.ComposeType.ForwardAsAttachment);
-		this.editAsNewCommand = createCommandHelper(Enums.ComposeType.EditAsNew);
-
-		this.messageVisibilityCommand = Utils.createCommand(this, Utils.emptyFunction, this.messageVisibility);
-
-		this.messageEditCommand = Utils.createCommand(this, function () {
-			this.editMessage();
-		}, this.messageVisibility);
-
-		this.deleteCommand = Utils.createCommand(this, function () {
-			if (this.message())
-			{
-				require('App/App').deleteMessagesFromFolder(Enums.FolderType.Trash,
-					this.message().folderFullNameRaw,
-					[this.message().uid], true);
-			}
-		}, this.messageVisibility);
-
-		this.deleteWithoutMoveCommand = Utils.createCommand(this, function () {
-			if (this.message())
-			{
-				require('App/App').deleteMessagesFromFolder(Enums.FolderType.Trash,
-					Data.currentFolderFullNameRaw(),
-					[this.message().uid], false);
-			}
-		}, this.messageVisibility);
-
-		this.archiveCommand = Utils.createCommand(this, function () {
-			if (this.message())
-			{
-				require('App/App').deleteMessagesFromFolder(Enums.FolderType.Archive,
-					this.message().folderFullNameRaw,
-					[this.message().uid], true);
-			}
-		}, this.messageVisibility);
-
-		this.spamCommand = Utils.createCommand(this, function () {
-			if (this.message())
-			{
-				require('App/App').deleteMessagesFromFolder(Enums.FolderType.Spam,
-					this.message().folderFullNameRaw,
-					[this.message().uid], true);
-			}
-		}, this.messageVisibility);
-
-		this.notSpamCommand = Utils.createCommand(this, function () {
-			if (this.message())
-			{
-				require('App/App').deleteMessagesFromFolder(Enums.FolderType.NotSpam,
-					this.message().folderFullNameRaw,
-					[this.message().uid], true);
-			}
-		}, this.messageVisibility);
-
-		// viewer
-		this.viewHash = '';
-		this.viewSubject = ko.observable('');
-		this.viewFromShort = ko.observable('');
-		this.viewToShort = ko.observable('');
-		this.viewFrom = ko.observable('');
-		this.viewTo = ko.observable('');
-		this.viewCc = ko.observable('');
-		this.viewBcc = ko.observable('');
-		this.viewDate = ko.observable('');
-		this.viewSize = ko.observable('');
-		this.viewMoment = ko.observable('');
-		this.viewLineAsCcc = ko.observable('');
-		this.viewViewLink = ko.observable('');
-		this.viewDownloadLink = ko.observable('');
-		this.viewUserPic = ko.observable(Consts.DataImages.UserDotPic);
-		this.viewUserPicVisible = ko.observable(false);
-
-		this.viewPgpPassword = ko.observable('');
-		this.viewPgpSignedVerifyStatus = ko.computed(function () {
-			return this.message() ? this.message().pgpSignedVerifyStatus() : Enums.SignedVerifyStatus.None;
-		}, this);
-
-		this.viewPgpSignedVerifyUser = ko.computed(function () {
-			return this.message() ? this.message().pgpSignedVerifyUser() : '';
-		}, this);
-
-		this.message.subscribe(function (oMessage) {
-
-			this.messageActiveDom(null);
-
-			this.viewPgpPassword('');
-
-			if (oMessage)
-			{
-				if (this.viewHash !== oMessage.hash)
-				{
-					this.scrollMessageToTop();
-				}
-
-				this.viewHash = oMessage.hash;
-				this.viewSubject(oMessage.subject());
-				this.viewFromShort(oMessage.fromToLine(true, true));
-				this.viewToShort(oMessage.toToLine(true, true));
-				this.viewFrom(oMessage.fromToLine(false));
-				this.viewTo(oMessage.toToLine(false));
-				this.viewCc(oMessage.ccToLine(false));
-				this.viewBcc(oMessage.bccToLine(false));
-				this.viewDate(oMessage.fullFormatDateValue());
-				this.viewSize(oMessage.friendlySize());
-				this.viewMoment(oMessage.momentDate());
-				this.viewLineAsCcc(oMessage.lineAsCcc());
-				this.viewViewLink(oMessage.viewLink());
-				this.viewDownloadLink(oMessage.downloadLink());
-
-				sLastEmail = oMessage.fromAsSingleEmail();
-				Cache.getUserPic(sLastEmail, function (sPic, $sEmail) {
-					if (sPic !== self.viewUserPic() && sLastEmail === $sEmail)
-					{
-						self.viewUserPicVisible(false);
-						self.viewUserPic(Consts.DataImages.UserDotPic);
-						if ('' !== sPic)
-						{
-							self.viewUserPicVisible(true);
-							self.viewUserPic(sPic);
-						}
-					}
-				});
-			}
-			else
-			{
-				this.viewHash = '';
-				this.scrollMessageToTop();
-			}
-
-		}, this);
-
-		this.fullScreenMode.subscribe(function (bValue) {
-			if (bValue)
-			{
-				Globals.$html.addClass('rl-message-fullscreen');
-			}
-			else
-			{
-				Globals.$html.removeClass('rl-message-fullscreen');
-			}
-
-			Utils.windowResize();
-		});
-
-		this.messageLoadingThrottle.subscribe(function (bV) {
-			if (bV)
-			{
-				Utils.windowResize();
-			}
-		});
-
-		this.goUpCommand = Utils.createCommand(this, function () {
-			Events.pub('mailbox.message-list.selector.go-up');
-		});
-
-		this.goDownCommand = Utils.createCommand(this, function () {
-			Events.pub('mailbox.message-list.selector.go-down');
-		});
-
-		kn.constructorEnd(this);
-	}
-
-	kn.extendAsViewModel(['View/App/MailBox/MessageView', 'MailBoxMessageViewViewModel'], MessageViewMailBoxAppView);
-	_.extend(MessageViewMailBoxAppView.prototype, AbstractView.prototype);
-
-	MessageViewMailBoxAppView.prototype.isPgpActionVisible = function ()
-	{
-		return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus();
-	};
-
-	MessageViewMailBoxAppView.prototype.isPgpStatusVerifyVisible = function ()
-	{
-		return Enums.SignedVerifyStatus.None !== this.viewPgpSignedVerifyStatus();
-	};
-
-	MessageViewMailBoxAppView.prototype.isPgpStatusVerifySuccess = function ()
-	{
-		return Enums.SignedVerifyStatus.Success === this.viewPgpSignedVerifyStatus();
-	};
-
-	MessageViewMailBoxAppView.prototype.pgpStatusVerifyMessage = function ()
-	{
-		var sResult = '';
-		switch (this.viewPgpSignedVerifyStatus())
-		{
-			case Enums.SignedVerifyStatus.UnknownPublicKeys:
-				sResult = Utils.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND');
-				break;
-			case Enums.SignedVerifyStatus.UnknownPrivateKey:
-				sResult = Utils.i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND');
-				break;
-			case Enums.SignedVerifyStatus.Unverified:
-				sResult = Utils.i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE');
-				break;
-			case Enums.SignedVerifyStatus.Error:
-				sResult = Utils.i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR');
-				break;
-			case Enums.SignedVerifyStatus.Success:
-				sResult = Utils.i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', {
-					'USER': this.viewPgpSignedVerifyUser()
-				});
-				break;
-		}
-
-		return sResult;
-	};
-
-	MessageViewMailBoxAppView.prototype.fullScreen = function ()
-	{
-		this.fullScreenMode(true);
-		Utils.windowResize();
-	};
-
-	MessageViewMailBoxAppView.prototype.unFullScreen = function ()
-	{
-		this.fullScreenMode(false);
-		Utils.windowResize();
-	};
-
-	MessageViewMailBoxAppView.prototype.toggleFullScreen = function ()
-	{
-		Utils.removeSelection();
-
-		this.fullScreenMode(!this.fullScreenMode());
-		Utils.windowResize();
-	};
-
-	/**
-	 * @param {string} sType
-	 */
-	MessageViewMailBoxAppView.prototype.replyOrforward = function (sType)
-	{
-		kn.showScreenPopup(require('View/Popup/Compose'), [sType, Data.message()]);
-	};
-
-	MessageViewMailBoxAppView.prototype.onBuild = function (oDom)
-	{
-		var self = this;
-		this.fullScreenMode.subscribe(function (bValue) {
-			if (bValue)
-			{
-				self.message.focused(true);
-			}
-		}, this);
-
-		$('.attachmentsPlace', oDom).magnificPopup({
-			'delegate': '.magnificPopupImage:visible',
-			'type': 'image',
-			'gallery': {
-				'enabled': true,
-				'preload': [1, 1],
-				'navigateByImgClick': true
-			},
-			'callbacks': {
-				'open': function() {
-					Globals.useKeyboardShortcuts(false);
-				},
-				'close': function() {
-					Globals.useKeyboardShortcuts(true);
-				}
-			},
-			'mainClass': 'mfp-fade',
-			'removalDelay': 400
-		});
-
-		oDom
-			.on('click', 'a', function (oEvent) {
-				// setup maito protocol
-				return !(!!oEvent && 3 !== oEvent['which'] && Utils.mailToHelper($(this).attr('href'), require('View/Popup/Compose')));
-			})
-			.on('click', '.attachmentsPlace .attachmentPreview', function (oEvent) {
-				if (oEvent && oEvent.stopPropagation)
-				{
-					oEvent.stopPropagation();
-				}
-			})
-			.on('click', '.attachmentsPlace .attachmentItem', function () {
-
-				var
-					oAttachment = ko.dataFor(this)
-				;
-
-				if (oAttachment && oAttachment.download)
-				{
-					require('App/App').download(oAttachment.linkDownload());
-				}
-			})
-		;
-
-		this.message.focused.subscribe(function (bValue) {
-			if (bValue && !Utils.inFocus()) {
-				this.messageDomFocused(true);
-			} else {
-				this.messageDomFocused(false);
-				this.scrollMessageToTop();
-				this.scrollMessageToLeft();
-			}
-		}, this);
-
-		this.messageDomFocused.subscribe(function (bValue) {
-			if (!bValue && Enums.KeyState.MessageView === Globals.keyScope())
-			{
-				this.message.focused(false);
-			}
-		}, this);
-
-		Globals.keyScope.subscribe(function (sValue) {
-			if (Enums.KeyState.MessageView === sValue && this.message.focused())
-			{
-				this.messageDomFocused(true);
-			}
-		}, this);
-
-		this.oMessageScrollerDom = oDom.find('.messageItem .content');
-		this.oMessageScrollerDom = this.oMessageScrollerDom && this.oMessageScrollerDom[0] ? this.oMessageScrollerDom : null;
-
-		this.initShortcuts();
-	};
-
-	/**
-	 * @return {boolean}
-	 */
-	MessageViewMailBoxAppView.prototype.escShortcuts = function ()
-	{
-		if (this.viewModelVisibility() && this.message())
-		{
-			if (this.fullScreenMode())
-			{
-				this.fullScreenMode(false);
-			}
-			else if (Enums.Layout.NoPreview === Data.layout())
-			{
-				this.message(null);
-			}
-			else
-			{
-				this.message.focused(false);
-			}
-
-			return false;
-		}
-	};
-
-	MessageViewMailBoxAppView.prototype.initShortcuts = function ()
-	{
-		var
-			self = this
-		;
-
-		// exit fullscreen, back
-		key('esc', Enums.KeyState.MessageView, _.bind(this.escShortcuts, this));
-
-		// fullscreen
-		key('enter', Enums.KeyState.MessageView, function () {
-			self.toggleFullScreen();
-			return false;
-		});
-
-		key('enter', Enums.KeyState.MessageList, function () {
-			if (Enums.Layout.NoPreview !== Data.layout() && self.message())
-			{
-				self.toggleFullScreen();
-				return false;
-			}
-		});
-
-		// TODO // more toggle
-	//	key('', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
-	//		self.moreDropdownTrigger(true);
-	//		return false;
-	//	});
-
-		// reply
-		key('r', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
-			if (Data.message())
-			{
-				self.replyCommand();
-				return false;
-			}
-		});
-
-		// replaAll
-		key('a', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
-			if (Data.message())
-			{
-				self.replyAllCommand();
-				return false;
-			}
-		});
-
-		// forward
-		key('f', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
-			if (Data.message())
-			{
-				self.forwardCommand();
-				return false;
-			}
-		});
-
-		// message information
-	//	key('i', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
-	//		if (oData.message())
-	//		{
-	//			self.showFullInfo(!self.showFullInfo());
-	//			return false;
-	//		}
-	//	});
-
-		// toggle message blockquotes
-		key('b', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
-			if (Data.message() && Data.message().body)
-			{
-				Data.message().body.find('.rlBlockquoteSwitcher').click();
-				return false;
-			}
-		});
-
-		key('ctrl+left, command+left, ctrl+up, command+up', Enums.KeyState.MessageView, function () {
-			self.goUpCommand();
-			return false;
-		});
-
-		key('ctrl+right, command+right, ctrl+down, command+down', Enums.KeyState.MessageView, function () {
-			self.goDownCommand();
-			return false;
-		});
-
-		// print
-		key('ctrl+p, command+p', Enums.KeyState.MessageView, function () {
-			if (self.message())
-			{
-				self.message().printMessage();
-			}
-
-			return false;
-		});
-
-		// delete
-		key('delete, shift+delete', Enums.KeyState.MessageView, function (event, handler) {
-			if (event)
-			{
-				if (handler && 'shift+delete' === handler.shortcut)
-				{
-					self.deleteWithoutMoveCommand();
-				}
-				else
-				{
-					self.deleteCommand();
-				}
-
-				return false;
-			}
-		});
-
-		// change focused state
-		key('tab, shift+tab, left', Enums.KeyState.MessageView, function (event, handler) {
-			if (!self.fullScreenMode() && self.message() && Enums.Layout.NoPreview !== Data.layout())
-			{
-				if (event && handler && 'left' === handler.shortcut)
-				{
-					if (self.oMessageScrollerDom && 0 < self.oMessageScrollerDom.scrollLeft())
-					{
-						return true;
-					}
-
-					self.message.focused(false);
-				}
-				else
-				{
-					self.message.focused(false);
-				}
-			}
-			else if (self.message() && Enums.Layout.NoPreview === Data.layout() && event && handler && 'left' === handler.shortcut)
-			{
-				return true;
-			}
-
-			return false;
-		});
-	};
-
-	/**
-	 * @return {boolean}
-	 */
-	MessageViewMailBoxAppView.prototype.isDraftFolder = function ()
-	{
-		return Data.message() && Data.draftFolder() === Data.message().folderFullNameRaw;
-	};
-
-	/**
-	 * @return {boolean}
-	 */
-	MessageViewMailBoxAppView.prototype.isSentFolder = function ()
-	{
-		return Data.message() && Data.sentFolder() === Data.message().folderFullNameRaw;
-	};
-
-	/**
-	 * @return {boolean}
-	 */
-	MessageViewMailBoxAppView.prototype.isSpamFolder = function ()
-	{
-		return Data.message() && Data.spamFolder() === Data.message().folderFullNameRaw;
-	};
-
-	/**
-	 * @return {boolean}
-	 */
-	MessageViewMailBoxAppView.prototype.isSpamDisabled = function ()
-	{
-		return Data.message() && Data.spamFolder() === Consts.Values.UnuseOptionValue;
-	};
-
-	/**
-	 * @return {boolean}
-	 */
-	MessageViewMailBoxAppView.prototype.isArchiveFolder = function ()
-	{
-		return Data.message() && Data.archiveFolder() === Data.message().folderFullNameRaw;
-	};
-
-	/**
-	 * @return {boolean}
-	 */
-	MessageViewMailBoxAppView.prototype.isArchiveDisabled = function ()
-	{
-		return Data.message() && Data.archiveFolder() === Consts.Values.UnuseOptionValue;
-	};
-
-	/**
-	 * @return {boolean}
-	 */
-	MessageViewMailBoxAppView.prototype.isDraftOrSentFolder = function ()
-	{
-		return this.isDraftFolder() || this.isSentFolder();
-	};
-
-	MessageViewMailBoxAppView.prototype.composeClick = function ()
-	{
-		kn.showScreenPopup(require('View/Popup/Compose'));
-	};
-
-	MessageViewMailBoxAppView.prototype.editMessage = function ()
-	{
-		if (Data.message())
-		{
-			kn.showScreenPopup(require('View/Popup/Compose'), [Enums.ComposeType.Draft, Data.message()]);
-		}
-	};
-
-	MessageViewMailBoxAppView.prototype.scrollMessageToTop = function ()
-	{
-		if (this.oMessageScrollerDom)
-		{
-			this.oMessageScrollerDom.scrollTop(0);
-			Utils.windowResize();
-		}
-	};
-
-	MessageViewMailBoxAppView.prototype.scrollMessageToLeft = function ()
-	{
-		if (this.oMessageScrollerDom)
-		{
-			this.oMessageScrollerDom.scrollLeft(0);
-			Utils.windowResize();
-		}
-	};
-
-	/**
-	 * @param {MessageModel} oMessage
-	 */
-	MessageViewMailBoxAppView.prototype.showImages = function (oMessage)
-	{
-		if (oMessage && oMessage.showExternalImages)
-		{
-			oMessage.showExternalImages(true);
-		}
-	};
-
-	/**
-	 * @returns {string}
-	 */
-	MessageViewMailBoxAppView.prototype.printableCheckedMessageCount = function ()
-	{
-		var iCnt = this.messageListCheckedOrSelectedUidsWithSubMails().length;
-		return 0 < iCnt ? (100 > iCnt ? iCnt : '99+') : '';
-	};
-
-
-	/**
-	 * @param {MessageModel} oMessage
-	 */
-	MessageViewMailBoxAppView.prototype.verifyPgpSignedClearMessage = function (oMessage)
-	{
-		if (oMessage)
-		{
-			oMessage.verifyPgpSignedClearMessage();
-		}
-	};
-
-	/**
-	 * @param {MessageModel} oMessage
-	 */
-	MessageViewMailBoxAppView.prototype.decryptPgpEncryptedMessage = function (oMessage)
-	{
-		if (oMessage)
-		{
-			oMessage.decryptPgpEncryptedMessage(this.viewPgpPassword());
-		}
-	};
-
-	/**
-	 * @param {MessageModel} oMessage
-	 */
-	MessageViewMailBoxAppView.prototype.readReceipt = function (oMessage)
-	{
-		if (oMessage && '' !== oMessage.readReceipt())
-		{
-			Remote.sendReadReceiptMessage(Utils.emptyFunction, oMessage.folderFullNameRaw, oMessage.uid,
-				oMessage.readReceipt(),
-				Utils.i18n('READ_RECEIPT/SUBJECT', {'SUBJECT': oMessage.subject()}),
-				Utils.i18n('READ_RECEIPT/BODY', {'READ-RECEIPT': Data.accountEmail()}));
-
-			oMessage.isReadReceipt(true);
-
-			Cache.storeMessageFlagsToCache(oMessage);
-
-			require('App/App').reloadFlagsCurrentMessageListAndMessageFromCache();
-		}
-	};
-
-	module.exports = MessageViewMailBoxAppView;
-
-}());
\ No newline at end of file
diff --git a/dev/View/App/MailBox/SystemDropDown.js b/dev/View/App/MailBox/SystemDropDown.js
deleted file mode 100644
index c8d6e92c4..000000000
--- a/dev/View/App/MailBox/SystemDropDown.js
+++ /dev/null
@@ -1,28 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-
-		kn = require('Knoin/Knoin'),
-		AbstractSystemDropDownViewModel = require('View/App/AbstractSystemDropDown')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractSystemDropDownViewModel
-	 */
-	function SystemDropDownMailBoxAppView()
-	{
-		AbstractSystemDropDownViewModel.call(this);
-		kn.constructorEnd(this);
-	}
-
-	kn.extendAsViewModel(['View/App/MailBox/SystemDropDown', 'MailBoxSystemDropDownViewModel'], SystemDropDownMailBoxAppView);
-	_.extend(SystemDropDownMailBoxAppView.prototype, AbstractSystemDropDownViewModel.prototype);
-
-	module.exports = SystemDropDownMailBoxAppView;
-
-}());
diff --git a/dev/View/App/Settings/Menu.js b/dev/View/App/Settings/Menu.js
deleted file mode 100644
index c50d1933f..000000000
--- a/dev/View/App/Settings/Menu.js
+++ /dev/null
@@ -1,48 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-
-		Globals = require('Common/Globals'),
-		LinkBuilder = require('Common/LinkBuilder'),
-
-		kn = require('Knoin/Knoin'),
-		AbstractView = require('Knoin/AbstractView')
-	;
-
-	/**
-	 * @param {?} oScreen
-	 *
-	 * @constructor
-	 * @extends AbstractView
-	 */
-	function MenuSettingsAppView(oScreen)
-	{
-		AbstractView.call(this, 'Left', 'SettingsMenu');
-
-		this.leftPanelDisabled = Globals.leftPanelDisabled;
-
-		this.menu = oScreen.menu;
-
-		kn.constructorEnd(this);
-	}
-
-	kn.extendAsViewModel(['View/App/Settings/Menu', 'SettingsMenuViewModel'], MenuSettingsAppView);
-	_.extend(MenuSettingsAppView.prototype, AbstractView.prototype);
-
-	MenuSettingsAppView.prototype.link = function (sRoute)
-	{
-		return LinkBuilder.settings(sRoute);
-	};
-
-	MenuSettingsAppView.prototype.backToMailBoxClick = function ()
-	{
-		kn.setHash(LinkBuilder.inbox());
-	};
-
-	module.exports = MenuSettingsAppView;
-
-}());
\ No newline at end of file
diff --git a/dev/View/App/Settings/Pane.js b/dev/View/App/Settings/Pane.js
deleted file mode 100644
index 9be8f80cb..000000000
--- a/dev/View/App/Settings/Pane.js
+++ /dev/null
@@ -1,53 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-		key = require('key'),
-
-		Enums = require('Common/Enums'),
-		LinkBuilder = require('Common/LinkBuilder'),
-
-		Data = require('Storage/App/Data'),
-
-		kn = require('Knoin/Knoin'),
-		AbstractView = require('Knoin/AbstractView')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractView
-	 */
-	function PaneSettingsAppView()
-	{
-		AbstractView.call(this, 'Right', 'SettingsPane');
-
-		kn.constructorEnd(this);
-	}
-
-	kn.extendAsViewModel(['View/App/Settings/Pane', 'SettingsPaneViewModel'], PaneSettingsAppView);
-	_.extend(PaneSettingsAppView.prototype, AbstractView.prototype);
-
-	PaneSettingsAppView.prototype.onBuild = function ()
-	{
-		var self = this;
-		key('esc', Enums.KeyState.Settings, function () {
-			self.backToMailBoxClick();
-		});
-	};
-
-	PaneSettingsAppView.prototype.onShow = function ()
-	{
-		Data.message(null);
-	};
-
-	PaneSettingsAppView.prototype.backToMailBoxClick = function ()
-	{
-		kn.setHash(LinkBuilder.inbox());
-	};
-
-	module.exports = PaneSettingsAppView;
-
-}());
\ No newline at end of file
diff --git a/dev/View/App/Settings/SystemDropDown.js b/dev/View/App/Settings/SystemDropDown.js
deleted file mode 100644
index 025f9fbe7..000000000
--- a/dev/View/App/Settings/SystemDropDown.js
+++ /dev/null
@@ -1,28 +0,0 @@
-
-(function () {
-
-	'use strict';
-
-	var
-		_ = require('_'),
-
-		kn = require('Knoin/Knoin'),
-		AbstractSystemDropDownAppView = require('View/App/AbstractSystemDropDown')
-	;
-
-	/**
-	 * @constructor
-	 * @extends AbstractSystemDropDownAppView
-	 */
-	function SystemDropDownSettingsAppView()
-	{
-		AbstractSystemDropDownAppView.call(this);
-		kn.constructorEnd(this);
-	}
-
-	kn.extendAsViewModel(['View/App/Settings/SystemDropDown', 'SettingsSystemDropDownViewModel'], SystemDropDownSettingsAppView);
-	_.extend(SystemDropDownSettingsAppView.prototype, AbstractSystemDropDownAppView.prototype);
-
-	module.exports = SystemDropDownSettingsAppView;
-
-}());
\ No newline at end of file
diff --git a/dev/View/Popup/AddAccount.js b/dev/View/Popup/Account.js
similarity index 51%
rename from dev/View/Popup/AddAccount.js
rename to dev/View/Popup/Account.js
index fb4ef136b..58af7fc5a 100644
--- a/dev/View/Popup/AddAccount.js
+++ b/dev/View/Popup/Account.js
@@ -9,8 +9,9 @@
 
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
 
-		Remote = require('Storage/App/Remote'),
+		Remote = require('Remote/User/Ajax'),
 
 		kn = require('Knoin/Knoin'),
 		AbstractView = require('Knoin/AbstractView')
@@ -20,9 +21,11 @@
 	 * @constructor
 	 * @extends AbstractView
 	 */
-	function AddAccountPopupView()
+	function AccountPopupView()
 	{
-		AbstractView.call(this, 'Popups', 'PopupsAddAccount');
+		AbstractView.call(this, 'Popups', 'PopupsAccount');
+
+		this.isNew = ko.observable(true);
 
 		this.email = ko.observable('');
 		this.password = ko.observable('');
@@ -40,6 +43,7 @@
 
 		this.submitRequest = ko.observable(false);
 		this.submitError = ko.observable('');
+		this.submitErrorAdditional = ko.observable('');
 
 		this.emailFocus = ko.observable(false);
 
@@ -55,27 +59,34 @@
 
 			this.submitRequest(true);
 
-			Remote.accountAdd(_.bind(function (sResult, oData) {
+			Remote.accountSetup(_.bind(function (sResult, oData) {
 
 				this.submitRequest(false);
-				if (Enums.StorageResultType.Success === sResult && oData && 'AccountAdd' === oData.Action)
+				if (Enums.StorageResultType.Success === sResult && oData)
 				{
 					if (oData.Result)
 					{
-						require('App/App').accountsAndIdentities();
+						require('App/User').accountsAndIdentities();
 						this.cancelCommand();
 					}
-					else if (oData.ErrorCode)
+					else
 					{
-						this.submitError(Utils.getNotification(oData.ErrorCode));
+						this.submitError(oData.ErrorCode ? Translator.getNotification(oData.ErrorCode) :
+							Translator.getNotification(Enums.Notification.UnknownError));
+
+						if (oData.ErrorMessageAdditional)
+						{
+							this.submitErrorAdditional(oData.ErrorMessageAdditional);
+						}
 					}
 				}
 				else
 				{
-					this.submitError(Utils.getNotification(Enums.Notification.UnknownError));
+					this.submitError(Translator.getNotification(Enums.Notification.UnknownError));
+					this.submitErrorAdditional('');
 				}
 
-			}, this), this.email(), '', this.password());
+			}, this), this.email(), this.password(), this.isNew());
 
 			return true;
 
@@ -86,11 +97,13 @@
 		kn.constructorEnd(this);
 	}
 
-	kn.extendAsViewModel(['View/Popup/AddAccount', 'PopupsAddAccountViewModel'], AddAccountPopupView);
-	_.extend(AddAccountPopupView.prototype, AbstractView.prototype);
+	kn.extendAsViewModel(['View/Popup/Account', 'View/Popup/AddAccount', 'PopupsAddAccountViewModel'], AccountPopupView);
+	_.extend(AccountPopupView.prototype, AbstractView.prototype);
 
-	AddAccountPopupView.prototype.clearPopup = function ()
+	AccountPopupView.prototype.clearPopup = function ()
 	{
+		this.isNew(true);
+
 		this.email('');
 		this.password('');
 
@@ -99,18 +112,24 @@
 
 		this.submitRequest(false);
 		this.submitError('');
+		this.submitErrorAdditional('');
 	};
 
-	AddAccountPopupView.prototype.onShow = function ()
+	AccountPopupView.prototype.onShow = function (oAccount)
 	{
 		this.clearPopup();
+		if (oAccount && oAccount.canBeEdit())
+		{
+			this.isNew(false);
+			this.email(oAccount.email);
+		}
 	};
 
-	AddAccountPopupView.prototype.onFocus = function ()
+	AccountPopupView.prototype.onShowWithDelay = function ()
 	{
 		this.emailFocus(true);
 	};
 
-	module.exports = AddAccountPopupView;
+	module.exports = AccountPopupView;
 
 }());
\ No newline at end of file
diff --git a/dev/View/Popup/Activate.js b/dev/View/Popup/Activate.js
index f075ad4e0..b48f3073a 100644
--- a/dev/View/Popup/Activate.js
+++ b/dev/View/Popup/Activate.js
@@ -9,10 +9,13 @@
 
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
+		Consts = require('Common/Consts'),
+		Translator = require('Common/Translator'),
 
 		Settings = require('Storage/Settings'),
-		Data = require('Storage/Admin/Data'),
-		Remote = require('Storage/Admin/Remote'),
+		Remote = require('Remote/Admin/Ajax'),
+
+		LicenseStore = require('Stores/Admin/License'),
 
 		kn = require('Knoin/Knoin'),
 		AbstractView = require('Knoin/AbstractView')
@@ -33,12 +36,16 @@
 		this.key.focus = ko.observable(false);
 		this.activationSuccessed = ko.observable(false);
 
-		this.licenseTrigger = Data.licenseTrigger;
+		this.licenseTrigger = LicenseStore.licenseTrigger;
 
 		this.activateProcess = ko.observable(false);
 		this.activateText = ko.observable('');
 		this.activateText.isError = ko.observable(false);
 
+		this.htmlDescription = ko.computed(function () {
+			return Translator.i18n('POPUPS_ACTIVATE/HTML_DESC', {'DOMAIN': this.domain()});
+		}, this);
+
 		this.key.subscribe(function () {
 			this.activateText('');
 			this.activateText.isError(false);
@@ -64,7 +71,7 @@
 						if (true === oData.Result)
 						{
 							self.activationSuccessed(true);
-							self.activateText('Subscription Key Activated Successfully');
+							self.activateText(Translator.i18n('POPUPS_ACTIVATE/SUBS_KEY_ACTIVATED'));
 							self.activateText.isError(false);
 						}
 						else
@@ -76,13 +83,13 @@
 					}
 					else if (oData.ErrorCode)
 					{
-						self.activateText(Utils.getNotification(oData.ErrorCode));
+						self.activateText(Translator.getNotification(oData.ErrorCode));
 						self.activateText.isError(true);
 						self.key.focus(true);
 					}
 					else
 					{
-						self.activateText(Utils.getNotification(Enums.Notification.UnknownError));
+						self.activateText(Translator.getNotification(Enums.Notification.UnknownError));
 						self.activateText.isError(true);
 						self.key.focus(true);
 					}
@@ -92,7 +99,7 @@
 			else
 			{
 				this.activateProcess(false);
-				this.activateText('Invalid Subscription Key');
+				this.activateText(Translator.i18n('POPUPS_ACTIVATE/ERROR_INVALID_SUBS_KEY'));
 				this.activateText.isError(true);
 				this.key.focus(true);
 			}
@@ -107,19 +114,21 @@
 	kn.extendAsViewModel(['View/Popup/Activate', 'PopupsActivateViewModel'], ActivatePopupView);
 	_.extend(ActivatePopupView.prototype, AbstractView.prototype);
 
-	ActivatePopupView.prototype.onShow = function ()
+	ActivatePopupView.prototype.onShow = function (bTrial)
 	{
 		this.domain(Settings.settingsGet('AdminDomain'));
 		if (!this.activateProcess())
 		{
-			this.key('');
+			bTrial = Utils.isUnd(bTrial) ? false : !!bTrial;
+
+			this.key(bTrial ? Consts.Values.RainLoopTrialKey : '');
 			this.activateText('');
 			this.activateText.isError(false);
 			this.activationSuccessed(false);
 		}
 	};
 
-	ActivatePopupView.prototype.onFocus = function ()
+	ActivatePopupView.prototype.onShowWithDelay = function ()
 	{
 		if (!this.activateProcess())
 		{
@@ -128,12 +137,13 @@
 	};
 
 	/**
-	 * @returns {boolean}
+	 * @return {boolean}
 	 */
 	ActivatePopupView.prototype.validateSubscriptionKey = function ()
 	{
 		var sValue = this.key();
-		return '' === sValue || !!/^RL[\d]+-[A-Z0-9\-]+Z$/.test(Utils.trim(sValue));
+		return '' === sValue || Consts.Values.RainLoopTrialKey === sValue ||
+			!!/^RL[\d]+-[A-Z0-9\-]+Z$/.test(Utils.trim(sValue));
 	};
 
 	module.exports = ActivatePopupView;
diff --git a/dev/View/Popup/AddOpenPgpKey.js b/dev/View/Popup/AddOpenPgpKey.js
index 7ab73a0da..7ac467ea9 100644
--- a/dev/View/Popup/AddOpenPgpKey.js
+++ b/dev/View/Popup/AddOpenPgpKey.js
@@ -9,7 +9,7 @@
 
 		Utils = require('Common/Utils'),
 
-		Data = require('Storage/App/Data'),
+		PgpStore = require('Stores/User/Pgp'),
 
 		kn = require('Knoin/Knoin'),
 		AbstractView = require('Knoin/AbstractView')
@@ -38,11 +38,14 @@
 				aMatch = null,
 				sKey = Utils.trim(this.key()),
 				oReg = /[\-]{3,6}BEGIN[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[\-]{3,6}[\s\S]+?[\-]{3,6}END[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[\-]{3,6}/gi,
-				oOpenpgpKeyring = Data.openpgpKeyring
+				oOpenpgpKeyring = PgpStore.openpgpKeyring
 			;
 
-			sKey = sKey.replace(/[\r\n]([a-zA-Z0-9]{2,}:[^\r\n]+)[\r\n]+([a-zA-Z0-9\/\\+=]{10,})/g, '\n$1!-!N!-!$2')
-				.replace(/[\n\r]+/g, '\n').replace(/!-!N!-!/g, '\n\n');
+			if (/[\n]/.test(sKey))
+			{
+				sKey = sKey.replace(/[\r]+/g, '')
+					.replace(/[\n]{2,}/g, '\n\n');
+			}
 
 			this.key.error('' === sKey);
 
@@ -77,7 +80,7 @@
 
 			oOpenpgpKeyring.store();
 
-			require('App/App').reloadOpenPgpKeys();
+			require('App/User').reloadOpenPgpKeys();
 			Utils.delegateRun(this, 'cancelCommand');
 
 			return true;
@@ -100,7 +103,7 @@
 		this.clearPopup();
 	};
 
-	AddOpenPgpKeyPopupView.prototype.onFocus = function ()
+	AddOpenPgpKeyPopupView.prototype.onShowWithDelay = function ()
 	{
 		this.key.focus(true);
 	};
diff --git a/dev/View/Popup/AdvancedSearch.js b/dev/View/Popup/AdvancedSearch.js
index 70e65cc0e..ad4eef02a 100644
--- a/dev/View/Popup/AdvancedSearch.js
+++ b/dev/View/Popup/AdvancedSearch.js
@@ -6,11 +6,10 @@
 	var
 		_ = require('_'),
 		ko = require('ko'),
-		moment = require('moment'),
 
 		Utils = require('Common/Utils'),
 
-		Data = require('Storage/App/Data'),
+		MessageStore = require('Stores/User/Message'),
 
 		kn = require('Knoin/Knoin'),
 		AbstractView = require('Knoin/AbstractView')
@@ -41,7 +40,7 @@
 			var sSearch = this.buildSearchString();
 			if ('' !== sSearch)
 			{
-				Data.mainMessageListSearch(sSearch);
+				MessageStore.mainMessageListSearch(sSearch);
 			}
 
 			this.cancelCommand();
@@ -117,7 +116,7 @@
 
 		if (-1 < this.selectedDateValue())
 		{
-			aResult.push('date:' + moment().subtract('days', this.selectedDateValue()).format('YYYY.MM.DD') + '/');
+			aResult.push('date:' + require('Common/Momentor').searchSubtractFormatDateHelper(this.selectedDateValue()) + '/');
 		}
 
 		if (sText && '' !== sText)
@@ -148,7 +147,7 @@
 		this.clearPopup();
 	};
 
-	AdvancedSearchPopupView.prototype.onFocus = function ()
+	AdvancedSearchPopupView.prototype.onShowWithDelay = function ()
 	{
 		this.fromFocus(true);
 	};
diff --git a/dev/View/Popup/Ask.js b/dev/View/Popup/Ask.js
index 01466ce5b..3d1cb4754 100644
--- a/dev/View/Popup/Ask.js
+++ b/dev/View/Popup/Ask.js
@@ -10,6 +10,7 @@
 
 		Enums = require('Common/Enums'),
 		Utils = require('Common/Utils'),
+		Translator = require('Common/Translator'),
 
 		kn = require('Knoin/Knoin'),
 		AbstractView = require('Knoin/AbstractView')
@@ -33,6 +34,7 @@
 		this.fYesAction = null;
 		this.fNoAction = null;
 
+		this.bFocusYesOnShow = true;
 		this.bDisabeCloseOnEsc = true;
 		this.sDefaultKeyScope = Enums.KeyState.PopupAsk;
 
@@ -45,8 +47,8 @@
 	AskPopupView.prototype.clearPopup = function ()
 	{
 		this.askDesc('');
-		this.yesButton(Utils.i18n('POPUPS_ASK/BUTTON_YES'));
-		this.noButton(Utils.i18n('POPUPS_ASK/BUTTON_NO'));
+		this.yesButton(Translator.i18n('POPUPS_ASK/BUTTON_YES'));
+		this.noButton(Translator.i18n('POPUPS_ASK/BUTTON_NO'));
 
 		this.yesFocus(false);
 		this.noFocus(false);
@@ -81,8 +83,9 @@
 	 * @param {Function=} fNoFunc
 	 * @param {string=} sYesButton
 	 * @param {string=} sNoButton
+	 * @param {boolean=} bFocusYesOnShow
 	 */
-	AskPopupView.prototype.onShow = function (sAskDesc, fYesFunc, fNoFunc, sYesButton, sNoButton)
+	AskPopupView.prototype.onShow = function (sAskDesc, fYesFunc, fNoFunc, sYesButton, sNoButton, bFocusYesOnShow)
 	{
 		this.clearPopup();
 
@@ -99,11 +102,16 @@
 		{
 			this.yesButton(sNoButton);
 		}
+
+		this.bFocusYesOnShow = Utils.isUnd(bFocusYesOnShow) ? true : !!bFocusYesOnShow;
 	};
 
-	AskPopupView.prototype.onFocus = function ()
+	AskPopupView.prototype.onShowWithDelay = function ()
 	{
-		this.yesFocus(true);
+		if (this.bFocusYesOnShow)
+		{
+			this.yesFocus(true);
+		}
 	};
 
 	AskPopupView.prototype.onBuild = function ()
diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js
index 6233641a5..42b8bf4ab 100644
--- a/dev/View/Popup/Compose.js
+++ b/dev/View/Popup/Compose.js
@@ -8,7 +8,6 @@
 		_ = require('_'),
 		$ = require('$'),
 		ko = require('ko'),
-		moment = require('moment'),
 		JSON = require('JSON'),
 		Jua = require('Jua'),
 
@@ -17,13 +16,25 @@
 		Utils = require('Common/Utils'),
 		Globals = require('Common/Globals'),
 		Events = require('Common/Events'),
-		LinkBuilder = require('Common/LinkBuilder'),
+		Links = require('Common/Links'),
 		HtmlEditor = require('Common/HtmlEditor'),
 
+		Translator = require('Common/Translator'),
+		Momentor = require('Common/Momentor'),
+
+		Cache = require('Common/Cache'),
+
+		AppStore = require('Stores/User/App'),
+		SettingsStore = require('Stores/User/Settings'),
+		IdentityStore = require('Stores/User/Identity'),
+		AccountStore = require('Stores/User/Account'),
+		FolderStore = require('Stores/User/Folder'),
+		PgpStore = require('Stores/User/Pgp'),
+		MessageStore = require('Stores/User/Message'),
+		SocialStore = require('Stores/Social'),
+
 		Settings = require('Storage/Settings'),
-		Data = require('Storage/App/Data'),
-		Cache = require('Storage/App/Cache'),
-		Remote = require('Storage/App/Remote'),
+		Remote = require('Remote/User/Ajax'),
 
 		ComposeAttachmentModel = require('Model/ComposeAttachment'),
 
@@ -39,55 +50,167 @@
 	{
 		AbstractView.call(this, 'Popups', 'PopupsCompose');
 
-		this.oEditor = null;
-		this.aDraftInfo = null;
-		this.sInReplyTo = '';
-		this.bFromDraft = false;
-		this.bSkipNext = false;
-		this.sReferences = '';
-
-		this.bCapaAdditionalIdentities = Settings.capa(Enums.Capa.AdditionalIdentities);
-
 		var
 			self = this,
-			fCcAndBccCheckHelper = function (aValue) {
-				if (false === self.showCcAndBcc() && 0 < aValue.length)
+			fEmailOutInHelper = function (self, oIdentity, sName, bIn) {
+				if (oIdentity && self && oIdentity[sName]() && (bIn ? true : self[sName]()))
 				{
-					self.showCcAndBcc(true);
+					var
+						sIdentityEmail = oIdentity[sName](),
+						aList = Utils.trim(self[sName]()).split(/[,]/)
+					;
+
+					aList = _.filter(aList, function (sEmail) {
+						sEmail = Utils.trim(sEmail);
+						return sEmail && Utils.trim(sIdentityEmail) !== sEmail;
+					});
+
+					if (bIn)
+					{
+						aList.push(sIdentityEmail);
+					}
+
+					self[sName](aList.join(','));
 				}
 			}
 		;
 
-		this.capaOpenPGP = Data.capaOpenPGP;
+		this.oLastMessage = null;
+		this.oEditor = null;
+		this.aDraftInfo = null;
+		this.sInReplyTo = '';
+		this.bFromDraft = false;
+		this.sReferences = '';
 
-		this.resizer = ko.observable(false).extend({'throttle': 50});
+		this.sLastFocusedField = 'to';
+
+		this.resizerTrigger = _.bind(this.resizerTrigger, this);
+
+		this.allowContacts = !!AppStore.contactsIsAllowed();
+
+		this.bSkipNextHide = false;
+		this.composeInEdit = AppStore.composeInEdit;
+		this.editorDefaultType = SettingsStore.editorDefaultType;
+
+		this.capaOpenPGP = PgpStore.capaOpenPGP;
 
 		this.identitiesDropdownTrigger = ko.observable(false);
 
 		this.to = ko.observable('');
-		this.to.focusTrigger = ko.observable(false);
+		this.to.focused = ko.observable(false);
 		this.cc = ko.observable('');
+		this.cc.focused = ko.observable(false);
 		this.bcc = ko.observable('');
-
+		this.bcc.focused = ko.observable(false);
 		this.replyTo = ko.observable('');
+		this.replyTo.focused = ko.observable(false);
+
+		ko.computed(function () {
+			switch (true)
+			{
+				case this.to.focused():
+					this.sLastFocusedField = 'to';
+					break;
+				case this.cc.focused():
+					this.sLastFocusedField = 'cc';
+					break;
+				case this.bcc.focused():
+					this.sLastFocusedField = 'bcc';
+					break;
+			}
+		}, this).extend({'notify': 'always'});
+
 		this.subject = ko.observable('');
 		this.isHtml = ko.observable(false);
 
+		this.requestDsn = ko.observable(false);
 		this.requestReadReceipt = ko.observable(false);
+		this.markAsImportant = ko.observable(false);
 
 		this.sendError = ko.observable(false);
 		this.sendSuccessButSaveError = ko.observable(false);
 		this.savedError = ko.observable(false);
 
+		this.sendErrorDesc = ko.observable('');
+		this.savedErrorDesc = ko.observable('');
+
+		this.sendError.subscribe(function (bValue) {
+			if (!bValue)
+			{
+				this.sendErrorDesc('');
+			}
+		}, this);
+
+		this.savedError.subscribe(function (bValue) {
+			if (!bValue)
+			{
+				this.savedErrorDesc('');
+			}
+		}, this);
+
+		this.sendSuccessButSaveError.subscribe(function (bValue) {
+			if (!bValue)
+			{
+				this.savedErrorDesc('');
+			}
+		}, this);
+
 		this.savedTime = ko.observable(0);
-		this.savedOrSendingText = ko.observable('');
+		this.savedTimeText = ko.computed(function () {
+			return 0 < this.savedTime() ? Translator.i18n('COMPOSE/SAVED_TIME', {
+				'TIME': Momentor.format(this.savedTime() - 1, 'LT')
+			}) : '';
+		}, this);
 
 		this.emptyToError = ko.observable(false);
-		this.attachmentsInProcessError = ko.observable(false);
-		this.showCcAndBcc = ko.observable(false);
+		this.emptyToErrorTooltip = ko.computed(function () {
+			return this.emptyToError() ? Translator.i18n('COMPOSE/EMPTY_TO_ERROR_DESC') : '';
+		}, this);
 
-		this.cc.subscribe(fCcAndBccCheckHelper, this);
-		this.bcc.subscribe(fCcAndBccCheckHelper, this);
+		this.attachmentsInProcessError = ko.observable(false);
+		this.attachmentsInErrorError = ko.observable(false);
+
+		this.attachmentsErrorTooltip = ko.computed(function () {
+
+			var sResult = '';
+			switch (true)
+			{
+				case this.attachmentsInProcessError():
+					sResult = Translator.i18n('COMPOSE/ATTACHMENTS_UPLOAD_ERROR_DESC');
+					break;
+				case this.attachmentsInErrorError():
+					sResult = Translator.i18n('COMPOSE/ATTACHMENTS_ERROR_DESC');
+					break;
+			}
+
+			return sResult;
+
+		}, this);
+
+		this.showCc = ko.observable(false);
+		this.showBcc = ko.observable(false);
+		this.showReplyTo = ko.observable(false);
+
+		this.cc.subscribe(function (aValue) {
+			if (false === self.showCc() && 0 < aValue.length)
+			{
+				self.showCc(true);
+			}
+		}, this);
+
+		this.bcc.subscribe(function (aValue) {
+			if (false === self.showBcc() && 0 < aValue.length)
+			{
+				self.showBcc(true);
+			}
+		}, this);
+
+		this.replyTo.subscribe(function (aValue) {
+			if (false === self.showReplyTo() && 0 < aValue.length)
+			{
+				self.showReplyTo(true);
+			}
+		}, this);
 
 		this.draftFolder = ko.observable('');
 		this.draftUid = ko.observable('');
@@ -96,21 +219,45 @@
 		this.attachments = ko.observableArray([]);
 
 		this.attachmentsInProcess = this.attachments.filter(function (oItem) {
-			return oItem && '' === oItem.tempName();
+			return oItem && !oItem.complete();
 		});
 
 		this.attachmentsInReady = this.attachments.filter(function (oItem) {
-			return oItem && '' !== oItem.tempName();
+			return oItem && oItem.complete();
 		});
 
-		this.attachments.subscribe(function () {
-			this.triggerForResize();
+		this.attachmentsInError = this.attachments.filter(function (oItem) {
+			return oItem && '' !== oItem.error();
+		});
+
+		this.attachmentsCount = ko.computed(function () {
+			return this.attachments().length;
+		}, this);
+
+		this.attachmentsInErrorCount = ko.computed(function () {
+			return this.attachmentsInError().length;
+		}, this);
+
+		this.attachmentsInProcessCount = ko.computed(function () {
+			return this.attachmentsInProcess().length;
 		}, this);
 
 		this.isDraftFolderMessage = ko.computed(function () {
 			return '' !== this.draftFolder() && '' !== this.draftUid();
 		}, this);
 
+		this.attachmentsPlace = ko.observable(false);
+
+		this.attachments.subscribe(this.resizerTrigger);
+		this.attachmentsPlace.subscribe(this.resizerTrigger);
+
+		this.attachmentsInErrorCount.subscribe(function (iN) {
+			if (0 === iN)
+			{
+				this.attachmentsInErrorError(false);
+			}
+		}, this);
+
 		this.composeUploaderButton = ko.observable(null);
 		this.composeUploaderDropPlace = ko.observable(null);
 		this.dragAndDropEnabled = ko.observable(false);
@@ -121,68 +268,33 @@
 
 		this.composeEditorArea = ko.observable(null);
 
-		this.identities = Data.identities;
-		this.defaultIdentityID = Data.defaultIdentityID;
-		this.currentIdentityID = ko.observable('');
-
-		this.currentIdentityString = ko.observable('');
-		this.currentIdentityResultEmail = ko.observable('');
-
+		this.identities = IdentityStore.identities;
 		this.identitiesOptions = ko.computed(function () {
-
-			var aList = [{
-				'optValue': Data.accountEmail(),
-				'optText': this.formattedFrom(false)
-			}];
-
-			_.each(Data.identities(), function (oItem) {
-				aList.push({
-					'optValue': oItem.id,
-					'optText': oItem.formattedNameForCompose()
-				});
+			return _.map(IdentityStore.identities(), function (oItem) {
+				return {
+					'item': oItem,
+					'optValue': oItem.id(),
+					'optText': oItem.formattedName()
+				};
 			});
-
-			return aList;
-
 		}, this);
 
-		ko.computed(function () {
+		this.currentIdentity = ko.observable(
+			this.identities()[0] ? this.identities()[0] : null);
 
-			var
-				sResult = '',
-				sResultEmail = '',
-				oItem = null,
-				aList = this.identities(),
-				sID = this.currentIdentityID()
-			;
-
-			if (this.bCapaAdditionalIdentities && sID && sID !== Data.accountEmail())
-			{
-				oItem = _.find(aList, function (oItem) {
-					return oItem && sID === oItem['id'];
-				});
-
-				sResult = oItem ? oItem.formattedNameForCompose() : '';
-				sResultEmail = oItem ? oItem.formattedNameForEmail() : '';
-
-				if ('' === sResult && aList[0])
-				{
-					this.currentIdentityID(aList[0]['id']);
-					return '';
-				}
+		this.currentIdentity.extend({'toggleSubscribe': [this,
+			function (oIdentity) {
+				fEmailOutInHelper(this, oIdentity, 'bcc');
+				fEmailOutInHelper(this, oIdentity, 'replyTo');
+			}, function (oIdentity) {
+				fEmailOutInHelper(this, oIdentity, 'bcc', true);
+				fEmailOutInHelper(this, oIdentity, 'replyTo', true);
 			}
+		]});
 
-			if ('' === sResult)
-			{
-				sResult = this.formattedFrom(false);
-				sResultEmail = this.formattedFrom(true);
-			}
-
-			this.currentIdentityString(sResult);
-			this.currentIdentityResultEmail(sResultEmail);
-
-			return sResult;
-
+		this.currentIdentityView = ko.computed(function () {
+			var oItem = this.currentIdentity();
+			return oItem ? oItem.formattedName() : 'unknown';
 		}, this);
 
 		this.to.subscribe(function (sValue) {
@@ -199,11 +311,13 @@
 			}
 		}, this);
 
-		this.editorResizeThrottle = _.throttle(_.bind(this.editorResize, this), 100);
+		this.resizer = ko.observable(false).extend({'throttle': 50});
 
-		this.resizer.subscribe(function () {
-			this.editorResizeThrottle();
-		}, this);
+		this.resizer.subscribe(_.bind(function () {
+			if (this.oEditor){
+				this.oEditor.resize();
+			}
+		}, this));
 
 		this.canBeSendedOrSaved = ko.computed(function () {
 			return !this.sending() && !this.saving();
@@ -211,7 +325,7 @@
 
 		this.deleteCommand = Utils.createCommand(this, function () {
 
-			require('App/App').deleteMessagesFromFolderWithoutCheck(this.draftFolder(), [this.draftUid()]);
+			require('App/User').deleteMessagesFromFolderWithoutCheck(this.draftFolder(), [this.draftUid()]);
 			kn.hideScreenPopup(ComposePopupView);
 
 		}, function () {
@@ -222,23 +336,36 @@
 		this.saveMessageResponse = _.bind(this.saveMessageResponse, this);
 
 		this.sendCommand = Utils.createCommand(this, function () {
+
 			var
 				sTo = Utils.trim(this.to()),
-				sSentFolder = Data.sentFolder(),
+				sSentFolder = FolderStore.sentFolder(),
 				aFlagsCache = []
 			;
 
+			this.attachmentsInProcessError(false);
+			this.attachmentsInErrorError(false);
+			this.emptyToError(false);
+
 			if (0 < this.attachmentsInProcess().length)
 			{
 				this.attachmentsInProcessError(true);
+				this.attachmentsPlace(true);
 			}
-			else if (0 === sTo.length)
+			else if (0 < this.attachmentsInError().length)
+			{
+				this.attachmentsInErrorError(true);
+				this.attachmentsPlace(true);
+			}
+
+			if (0 === sTo.length)
 			{
 				this.emptyToError(true);
 			}
-			else
+
+			if (!this.emptyToError() && !this.attachmentsInErrorError() && !this.attachmentsInProcessError())
 			{
-				if (Data.replySameFolder())
+				if (SettingsStore.replySameFolder())
 				{
 					if (Utils.isArray(this.aDraftInfo) && 3 === this.aDraftInfo.length && Utils.isNormal(this.aDraftInfo[2]) && 0 < this.aDraftInfo[2].length)
 					{
@@ -270,7 +397,7 @@
 							}
 
 							Cache.setMessageFlagsToCache(this.aDraftInfo[2], this.aDraftInfo[1], aFlagsCache);
-							require('App/App').reloadFlagsCurrentMessageListAndMessageFromCache();
+							require('App/User').reloadFlagsCurrentMessageListAndMessageFromCache();
 							Cache.setFolderHash(this.aDraftInfo[2], '');
 						}
 					}
@@ -282,21 +409,24 @@
 
 					Remote.sendMessage(
 						this.sendMessageResponse,
+						this.currentIdentity() ? this.currentIdentity().id() : '',
 						this.draftFolder(),
 						this.draftUid(),
 						sSentFolder,
-						this.currentIdentityResultEmail(),
 						sTo,
 						this.cc(),
 						this.bcc(),
+						this.replyTo(),
 						this.subject(),
 						this.oEditor ? this.oEditor.isHtml() : false,
-						this.oEditor ? this.oEditor.getData(true) : '',
+						this.oEditor ? this.oEditor.getData(true, true) : '',
 						this.prepearAttachmentsForSendOrSave(),
 						this.aDraftInfo,
 						this.sInReplyTo,
 						this.sReferences,
-						this.requestReadReceipt()
+						this.requestDsn(),
+						this.requestReadReceipt(),
+						this.markAsImportant()
 					);
 				}
 			}
@@ -304,7 +434,7 @@
 
 		this.saveCommand = Utils.createCommand(this, function () {
 
-			if (Data.draftFolderNotEnabled())
+			if (FolderStore.draftFolderNotEnabled())
 			{
 				kn.showScreenPopup(require('View/Popup/FolderSystem'), [Enums.SetSystemFoldersNotification.Draft]);
 			}
@@ -313,45 +443,80 @@
 				this.savedError(false);
 				this.saving(true);
 
-				this.bSkipNext = true;
+				this.autosaveStart();
 
-				Cache.setFolderHash(Data.draftFolder(), '');
+				Cache.setFolderHash(FolderStore.draftFolder(), '');
 
 				Remote.saveMessage(
 					this.saveMessageResponse,
+					this.currentIdentity() ? this.currentIdentity().id() : '',
 					this.draftFolder(),
 					this.draftUid(),
-					Data.draftFolder(),
-					this.currentIdentityResultEmail(),
+					FolderStore.draftFolder(),
 					this.to(),
 					this.cc(),
 					this.bcc(),
+					this.replyTo(),
 					this.subject(),
 					this.oEditor ? this.oEditor.isHtml() : false,
 					this.oEditor ? this.oEditor.getData(true) : '',
 					this.prepearAttachmentsForSendOrSave(),
 					this.aDraftInfo,
 					this.sInReplyTo,
-					this.sReferences
+					this.sReferences,
+					this.markAsImportant()
 				);
 			}
 
 		}, this.canBeSendedOrSaved);
 
-		Events.sub('interval.1m', function () {
-			if (this.modalVisibility() && !Data.draftFolderNotEnabled() && !this.isEmptyForm(false) &&
-				!this.bSkipNext && !this.saving() && !this.sending() && !this.savedError())
+		this.skipCommand = Utils.createCommand(this, function () {
+
+			this.bSkipNextHide = true;
+
+			if (this.modalVisibility() && !this.saving() && !this.sending() &&
+				!FolderStore.draftFolderNotEnabled())
+			{
+				this.saveCommand();
+			}
+
+			this.tryToClosePopup();
+
+		}, this.canBeSendedOrSaved);
+
+		this.contactsCommand = Utils.createCommand(this, function () {
+
+			if (this.allowContacts)
+			{
+				this.skipCommand();
+
+				var self = this;
+
+				_.delay(function () {
+					kn.showScreenPopup(require('View/Popup/Contacts'),
+						[true, self.sLastFocusedField]);
+				}, 200);
+			}
+
+		}, function () {
+			return this.allowContacts;
+		});
+
+		Events.sub('interval.2m', function () {
+
+			if (this.modalVisibility() && !FolderStore.draftFolderNotEnabled() && !this.isEmptyForm(false) &&
+				!this.saving() && !this.sending() && !this.savedError())
 			{
-				this.bSkipNext = false;
 				this.saveCommand();
 			}
 		}, this);
 
-		this.showCcAndBcc.subscribe(function () {
-			this.triggerForResize();
-		}, this);
+		this.showCc.subscribe(this.resizerTrigger);
+		this.showBcc.subscribe(this.resizerTrigger);
+		this.showReplyTo.subscribe(this.resizerTrigger);
 
-		this.dropboxEnabled = ko.observable(!!Settings.settingsGet('DropboxApiKey'));
+		this.dropboxEnabled = SocialStore.dropbox.enabled;
+		this.dropboxApiKey = SocialStore.dropbox.apiKey;
 
 		this.dropboxCommand = Utils.createCommand(this, function () {
 
@@ -366,7 +531,7 @@
 							self.addDropboxAttachment(aFiles[0]);
 						}
 					},
-					'linkType': "direct",
+					'linkType': 'direct',
 					'multiselect': false
 				});
 			}
@@ -378,6 +543,7 @@
 		});
 
 		this.driveEnabled = ko.observable(Globals.bXMLHttpRequestSupported &&
+			!!Settings.settingsGet('AllowGoogleSocial') && !!Settings.settingsGet('AllowGoogleSocialDrive') &&
 			!!Settings.settingsGet('GoogleClientID') && !!Settings.settingsGet('GoogleApiKey'));
 
 		this.driveVisible = ko.observable(false);
@@ -393,12 +559,17 @@
 
 		this.driveCallback = _.bind(this.driveCallback, this);
 
+		this.onMessageUploadAttachments = _.bind(this.onMessageUploadAttachments, this);
+
 		this.bDisabeCloseOnEsc = true;
 		this.sDefaultKeyScope = Enums.KeyState.Compose;
 
 		this.tryToClosePopup = _.debounce(_.bind(this.tryToClosePopup, this), 200);
 
 		this.emailsSource = _.bind(this.emailsSource, this);
+		this.autosaveFunction = _.bind(this.autosaveFunction, this);
+
+		this.iTimer = 0;
 
 		kn.constructorEnd(this);
 	}
@@ -406,9 +577,31 @@
 	kn.extendAsViewModel(['View/Popup/Compose', 'PopupsComposeViewModel'], ComposePopupView);
 	_.extend(ComposePopupView.prototype, AbstractView.prototype);
 
+	ComposePopupView.prototype.autosaveFunction = function ()
+	{
+		if (this.modalVisibility() && !FolderStore.draftFolderNotEnabled() && !this.isEmptyForm(false) &&
+			!this.saving() && !this.sending() && !this.savedError())
+		{
+			this.saveCommand();
+		}
+
+		this.autosaveStart();
+	};
+
+	ComposePopupView.prototype.autosaveStart = function ()
+	{
+		window.clearTimeout(this.iTimer);
+		this.iTimer = window.setTimeout(this.autosaveFunction, 1000 * 60 * 1);
+	};
+
+	ComposePopupView.prototype.autosaveStop = function ()
+	{
+		window.clearTimeout(this.iTimer);
+	};
+
 	ComposePopupView.prototype.emailsSource = function (oData, fResponse)
 	{
-		require('App/App').getAutocomplete(oData.term, function (aData) {
+		require('App/User').getAutocomplete(oData.term, function (aData) {
 			fResponse(_.map(aData, function (oEmailItem) {
 				return oEmailItem.toLine(false);
 			}));
@@ -417,7 +610,7 @@
 
 	ComposePopupView.prototype.openOpenPgpPopup = function ()
 	{
-		if (this.capaOpenPGP() && this.oEditor && !this.oEditor.isHtml())
+		if (PgpStore.capaOpenPGP() && this.oEditor && !this.oEditor.isHtml())
 		{
 			var self = this;
 			kn.showScreenPopup(require('View/Popup/ComposeOpenPgp'), [
@@ -427,7 +620,7 @@
 					});
 				},
 				this.oEditor.getData(),
-				this.currentIdentityResultEmail(),
+				this.currentIdentity(),
 				this.to(),
 				this.cc(),
 				this.bcc()
@@ -438,47 +631,47 @@
 	ComposePopupView.prototype.reloadDraftFolder = function ()
 	{
 		var
-			sDraftFolder = Data.draftFolder()
+			sDraftFolder = FolderStore.draftFolder()
 		;
 
 		if ('' !== sDraftFolder)
 		{
 			Cache.setFolderHash(sDraftFolder, '');
-			if (Data.currentFolderFullNameRaw() === sDraftFolder)
+			if (FolderStore.currentFolderFullNameRaw() === sDraftFolder)
 			{
-				require('App/App').reloadMessageList(true);
+				require('App/User').reloadMessageList(true);
 			}
 			else
 			{
-				require('App/App').folderInformation(sDraftFolder);
+				require('App/User').folderInformation(sDraftFolder);
 			}
 		}
 	};
 
-	ComposePopupView.prototype.findIdentityIdByMessage = function (sComposeType, oMessage)
+	ComposePopupView.prototype.findIdentityByMessage = function (sComposeType, oMessage)
 	{
 		var
-			oIDs = {},
-			sResult = '',
-			fFindHelper = function (oItem) {
-				if (oItem && oItem.email && oIDs[oItem.email])
-				{
-					sResult = oIDs[oItem.email];
-					return true;
-				}
+			aIdentities = IdentityStore.identities(),
+			iResultIndex = 1000,
+			oResultIdentity = null,
+			oIdentitiesCache = {},
 
-				return false;
+			fEachHelper = function (oItem) {
+
+				if (oItem && oItem.email && oIdentitiesCache[oItem.email])
+				{
+					if (!oResultIdentity || iResultIndex > oIdentitiesCache[oItem.email][1])
+					{
+						oResultIdentity = oIdentitiesCache[oItem.email][0];
+						iResultIndex = oIdentitiesCache[oItem.email][1];
+					}
+				}
 			}
 		;
 
-		if (this.bCapaAdditionalIdentities)
-		{
-			_.each(this.identities(), function (oItem) {
-				oIDs[oItem.email()] = oItem['id'];
-			});
-		}
-
-		oIDs[Data.accountEmail()] = Data.accountEmail();
+		_.each(aIdentities, function (oItem, iIndex) {
+			oIdentitiesCache[oItem.email()] = [oItem, iIndex];
+		});
 
 		if (oMessage)
 		{
@@ -490,54 +683,26 @@
 				case Enums.ComposeType.ReplyAll:
 				case Enums.ComposeType.Forward:
 				case Enums.ComposeType.ForwardAsAttachment:
-					_.find(_.union(oMessage.to, oMessage.cc, oMessage.bcc, oMessage.deliveredTo), fFindHelper);
+					_.each(_.union(oMessage.to, oMessage.cc, oMessage.bcc, oMessage.deliveredTo), fEachHelper);
 					break;
 				case Enums.ComposeType.Draft:
-					_.find(_.union(oMessage.from, oMessage.replyTo), fFindHelper);
+					_.each(_.union(oMessage.from, oMessage.replyTo), fEachHelper);
 					break;
 			}
 		}
 
-		if ('' === sResult)
-		{
-			sResult = this.defaultIdentityID();
-		}
-
-		if ('' === sResult)
-		{
-			sResult = Data.accountEmail();
-		}
-
-		return sResult;
+		return oResultIdentity || aIdentities[0] || null;
 	};
 
 	ComposePopupView.prototype.selectIdentity = function (oIdentity)
 	{
-		if (oIdentity)
+		if (oIdentity && oIdentity.item)
 		{
-			this.currentIdentityID(oIdentity.optValue);
+			this.currentIdentity(oIdentity.item);
+			this.setSignatureFromIdentity(oIdentity.item);
 		}
 	};
 
-	/**
-	 *
-	 * @param {boolean=} bHeaderResult = false
-	 * @returns {string}
-	 */
-	ComposePopupView.prototype.formattedFrom = function (bHeaderResult)
-	{
-		var
-			sDisplayName = Data.displayName(),
-			sEmail = Data.accountEmail()
-		;
-
-		return '' === sDisplayName ? sEmail :
-			((Utils.isUnd(bHeaderResult) ? false : !!bHeaderResult) ?
-				'"' + Utils.quoteName(sDisplayName) + '" <' + sEmail + '>' :
-				sDisplayName + ' (' + sEmail + ')')
-		;
-	};
-
 	ComposePopupView.prototype.sendMessageResponse = function (sResult, oData)
 	{
 		var
@@ -561,15 +726,15 @@
 			if (oData && Enums.Notification.CantSaveMessage === oData.ErrorCode)
 			{
 				this.sendSuccessButSaveError(true);
-				window.alert(Utils.trim(Utils.i18n('COMPOSE/SAVED_ERROR_ON_SEND')));
+				this.savedErrorDesc(Utils.trim(Translator.i18n('COMPOSE/SAVED_ERROR_ON_SEND')));
 			}
 			else
 			{
-				sMessage = Utils.getNotification(oData && oData.ErrorCode ? oData.ErrorCode : Enums.Notification.CantSendMessage,
+				sMessage = Translator.getNotification(oData && oData.ErrorCode ? oData.ErrorCode : Enums.Notification.CantSendMessage,
 					oData && oData.ErrorMessage ? oData.ErrorMessage : '');
 
 				this.sendError(true);
-				window.alert(sMessage || Utils.getNotification(Enums.Notification.CantSendMessage));
+				this.sendErrorDesc(sMessage || Translator.getNotification(Enums.Notification.CantSendMessage));
 			}
 		}
 
@@ -589,42 +754,33 @@
 		{
 			if (oData.Result.NewFolder && oData.Result.NewUid)
 			{
+				bResult = true;
+
 				if (this.bFromDraft)
 				{
-					oMessage = Data.message();
+					oMessage = MessageStore.message();
 					if (oMessage && this.draftFolder() === oMessage.folderFullNameRaw && this.draftUid() === oMessage.uid)
 					{
-						Data.message(null);
+						MessageStore.message(null);
 					}
 				}
 
 				this.draftFolder(oData.Result.NewFolder);
 				this.draftUid(oData.Result.NewUid);
 
-				if (this.modalVisibility())
+				this.savedTime(window.Math.round((new window.Date()).getTime() / 1000));
+
+				if (this.bFromDraft)
 				{
-					this.savedTime(window.Math.round((new window.Date()).getTime() / 1000));
-
-					this.savedOrSendingText(
-						0 < this.savedTime() ? Utils.i18n('COMPOSE/SAVED_TIME', {
-							'TIME': moment.unix(this.savedTime() - 1).format('LT')
-						}) : ''
-					);
-
-					bResult = true;
-
-					if (this.bFromDraft)
-					{
-						Cache.setFolderHash(this.draftFolder(), '');
-					}
+					Cache.setFolderHash(this.draftFolder(), '');
 				}
 			}
 		}
 
-		if (!this.modalVisibility() && !bResult)
+		if (!bResult)
 		{
 			this.savedError(true);
-			this.savedOrSendingText(Utils.getNotification(Enums.Notification.CantSaveMessage));
+			this.savedErrorDesc(Translator.getNotification(Enums.Notification.CantSaveMessage));
 		}
 
 		this.reloadDraftFolder();
@@ -632,72 +788,21 @@
 
 	ComposePopupView.prototype.onHide = function ()
 	{
-		this.reset();
+		this.autosaveStop();
+
+		if (!this.bSkipNextHide)
+		{
+			AppStore.composeInEdit(false);
+			this.reset();
+		}
+
+		this.bSkipNextHide = false;
+
+		this.to.focused(false);
+
 		kn.routeOn();
 	};
 
-	/**
-	 * @param {string} sSignature
-	 * @param {string=} sFrom
-	 * @param {string=} sData
-	 * @param {string=} sComposeType
-	 * @return {string}
-	 */
-	ComposePopupView.prototype.convertSignature = function (sSignature, sFrom, sData, sComposeType)
-	{
-		var bHtml = false, bData = false;
-		if ('' !== sSignature)
-		{
-			if (':HTML:' === sSignature.substr(0, 6))
-			{
-				bHtml = true;
-				sSignature = sSignature.substr(6);
-			}
-
-			sSignature = sSignature.replace(/[\r]/g, '');
-
-			sFrom = Utils.pString(sFrom);
-			if ('' !== sFrom)
-			{
-				sSignature = sSignature.replace(/{{FROM}}/g, sFrom);
-			}
-
-			sSignature = sSignature.replace(/[\s]{1,2}{{FROM}}/g, '{{FROM}}');
-
-			sSignature = sSignature.replace(/{{FROM}}/g, '');
-			sSignature = sSignature.replace(/{{DATE}}/g, moment().format('llll'));
-
-			if (sData && Enums.ComposeType.Empty === sComposeType &&
-				-1 < sSignature.indexOf('{{DATA}}'))
-			{
-				bData = true;
-				sSignature = sSignature.replace('{{DATA}}', sData);
-			}
-
-			sSignature = sSignature.replace(/{{DATA}}/g, '');
-
-			if (!bHtml)
-			{
-				sSignature = Utils.convertPlainTextToHtml(sSignature);
-			}
-		}
-
-		if (sData && !bData)
-		{
-			switch (sComposeType)
-			{
-				case Enums.ComposeType.Empty:
-					sSignature = sData + '
' + sSignature; - break; - default: - sSignature = sSignature + '
' + sData; - break; - } - } - - return sSignature; - }; - ComposePopupView.prototype.editor = function (fOnInit) { if (fOnInit) @@ -705,32 +810,223 @@ var self = this; if (!this.oEditor && this.composeEditorArea()) { - _.delay(function () { - self.oEditor = new HtmlEditor(self.composeEditorArea(), null, function () { - fOnInit(self.oEditor); - }, function (bHtml) { - self.isHtml(!!bHtml); - }); - }, 300); +//_.delay(function () { + self.oEditor = new HtmlEditor(self.composeEditorArea(), null, function () { + fOnInit(self.oEditor); + self.resizerTrigger(); + }, function (bHtml) { + self.isHtml(!!bHtml); + }); +//}, 1000); } else if (this.oEditor) { fOnInit(this.oEditor); + this.resizerTrigger(); } } }; + ComposePopupView.prototype.converSignature = function (sSignature) + { + var + iLimit = 10, + oMatch = null, + aMoments = [], + oMomentRegx = /{{MOMENT:([^}]+)}}/g, + sFrom = '' + ; + + sSignature = sSignature.replace(/[\r]/g, ''); + + sFrom = this.oLastMessage ? this.emailArrayToStringLineHelper(this.oLastMessage.from, true) : ''; + if ('' !== sFrom) + { + sSignature = sSignature.replace(/{{FROM-FULL}}/g, sFrom); + + if (-1 === sFrom.indexOf(' ') && 0 < sFrom.indexOf('@')) + { + sFrom = sFrom.replace(/@[\S]+/, ''); + } + + sSignature = sSignature.replace(/{{FROM}}/g, sFrom); + } + + sSignature = sSignature.replace(/[\s]{1,2}{{FROM}}/g, '{{FROM}}'); + sSignature = sSignature.replace(/[\s]{1,2}{{FROM-FULL}}/g, '{{FROM-FULL}}'); + + sSignature = sSignature.replace(/{{FROM}}/g, ''); + sSignature = sSignature.replace(/{{FROM-FULL}}/g, ''); + + if (-1 < sSignature.indexOf('{{DATE}}')) + { + sSignature = sSignature.replace(/{{DATE}}/g, Momentor.format(0, 'llll')); + } + + if (-1 < sSignature.indexOf('{{TIME}}')) + { + sSignature = sSignature.replace(/{{TIME}}/g, Momentor.format(0, 'LT')); + } + if (-1 < sSignature.indexOf('{{MOMENT:')) + { + try + { + while ((oMatch = oMomentRegx.exec(sSignature)) !== null) + { + if (oMatch && oMatch[0] && oMatch[1]) + { + aMoments.push([oMatch[0], oMatch[1]]); + } + + iLimit--; + if (0 === iLimit) + { + break; + } + } + + if (aMoments && 0 < aMoments.length) + { + _.each(aMoments, function (aData) { + sSignature = sSignature.replace( + aData[0], Momentor.format(0, aData[1])); + }); + } + + sSignature = sSignature.replace(/{{MOMENT:[^}]+}}/g, ''); + } + catch(e) {} + } + + return sSignature; + }; + + ComposePopupView.prototype.setSignatureFromIdentity = function (oIdentity) + { + if (oIdentity) + { + var self = this; + this.editor(function (oEditor) { + var bHtml = false, sSignature = oIdentity.signature(); + if ('' !== sSignature) + { + if (':HTML:' === sSignature.substr(0, 6)) + { + bHtml = true; + sSignature = sSignature.substr(6); + } + } + + oEditor.setSignature(self.converSignature(sSignature), + bHtml, !!oIdentity.signatureInsertBefore()); + }); + } + }; + /** * @param {string=} sType = Enums.ComposeType.Empty * @param {?MessageModel|Array=} oMessageOrArray = null * @param {Array=} aToEmails = null + * @param {Array=} aCcEmails = null + * @param {Array=} aBccEmails = null * @param {string=} sCustomSubject = null * @param {string=} sCustomPlainText = null */ - ComposePopupView.prototype.onShow = function (sType, oMessageOrArray, aToEmails, sCustomSubject, sCustomPlainText) + ComposePopupView.prototype.onShow = function (sType, oMessageOrArray, + aToEmails, aCcEmails, aBccEmails, sCustomSubject, sCustomPlainText) { kn.routeOff(); + this.autosaveStart(); + + if (AppStore.composeInEdit()) + { + sType = sType || Enums.ComposeType.Empty; + + var self = this; + + if (Enums.ComposeType.Empty !== sType) + { + kn.showScreenPopup(require('View/Popup/Ask'), [Translator.i18n('COMPOSE/DISCARD_UNSAVED_DATA'), function () { + self.initOnShow(sType, oMessageOrArray, aToEmails, aCcEmails, aBccEmails, sCustomSubject, sCustomPlainText); + }, null, null, null, false]); + } + else + { + this.addEmailsTo(this.to, aToEmails); + this.addEmailsTo(this.cc, aCcEmails); + this.addEmailsTo(this.bcc, aBccEmails); + + if (Utils.isNormal(sCustomSubject) && '' !== sCustomSubject && + '' === this.subject()) + { + this.subject(sCustomSubject); + } + } + } + else + { + this.initOnShow(sType, oMessageOrArray, aToEmails, aCcEmails, aBccEmails, sCustomSubject, sCustomPlainText); + } + }; + + /** + * @param {Function} fKoValue + * @param {Array} aEmails + */ + ComposePopupView.prototype.addEmailsTo = function (fKoValue, aEmails) + { + var + sValue = Utils.trim(fKoValue()), + aValue = [] + ; + + if (Utils.isNonEmptyArray(aEmails)) + { + aValue = _.uniq(_.compact(_.map(aEmails, function (oItem) { + return oItem ? oItem.toLine(false) : null; + }))); + + fKoValue(sValue + ('' === sValue ? '' : ', ') + Utils.trim(aValue.join(', '))); + } + }; + + /** + * + * @param {Array} aList + * @param {boolean} bFriendly + * @return {string} + */ + ComposePopupView.prototype.emailArrayToStringLineHelper = function (aList, bFriendly) + { + var + iIndex = 0, + iLen = aList.length, + aResult = [] + ; + + for (; iIndex < iLen; iIndex++) + { + aResult.push(aList[iIndex].toLine(!!bFriendly)); + } + + return aResult.join(', '); + }; + + /** + * @param {string=} sType = Enums.ComposeType.Empty + * @param {?MessageModel|Array=} oMessageOrArray = null + * @param {Array=} aToEmails = null + * @param {Array=} aCcEmails = null + * @param {Array=} aBccEmails = null + * @param {string=} sCustomSubject = null + * @param {string=} sCustomPlainText = null + */ + ComposePopupView.prototype.initOnShow = function (sType, oMessageOrArray, + aToEmails, aCcEmails, aBccEmails, sCustomSubject, sCustomPlainText) + { + AppStore.composeInEdit(true); + var self = this, sFrom = '', @@ -743,28 +1039,12 @@ sReplyTitle = '', aResplyAllParts = [], oExcludeEmail = {}, - mEmail = Data.accountEmail(), - sSignature = Data.signature(), - bSignatureToAll = Data.signatureToAll(), + oIdentity = null, + mEmail = AccountStore.email(), aDownloads = [], aDraftInfo = null, oMessage = null, - sComposeType = sType || Enums.ComposeType.Empty, - fEmailArrayToStringLineHelper = function (aList, bFriendly) { - - var - iIndex = 0, - iLen = aList.length, - aResult = [] - ; - - for (; iIndex < iLen; iIndex++) - { - aResult.push(aList[iIndex].toLine(!!bFriendly)); - } - - return aResult.join(', '); - } + sComposeType = sType || Enums.ComposeType.Empty ; oMessageOrArray = oMessageOrArray || null; @@ -774,38 +1054,51 @@ (!Utils.isArray(oMessageOrArray) ? oMessageOrArray : null); } + this.oLastMessage = oMessage; + if (null !== mEmail) { oExcludeEmail[mEmail] = true; } - this.currentIdentityID(this.findIdentityIdByMessage(sComposeType, oMessage)); this.reset(); + oIdentity = this.findIdentityByMessage(sComposeType, oMessage); + if (oIdentity) + { + oExcludeEmail[oIdentity.email()] = true; + } + if (Utils.isNonEmptyArray(aToEmails)) { - this.to(fEmailArrayToStringLineHelper(aToEmails)); + this.to(this.emailArrayToStringLineHelper(aToEmails)); + } + + if (Utils.isNonEmptyArray(aCcEmails)) + { + this.cc(this.emailArrayToStringLineHelper(aCcEmails)); + } + + if (Utils.isNonEmptyArray(aBccEmails)) + { + this.bcc(this.emailArrayToStringLineHelper(aBccEmails)); } if ('' !== sComposeType && oMessage) { - sDate = oMessage.fullFormatDateValue(); + sDate = Momentor.format(oMessage.dateTimeStampInUTC(), 'FULL'); sSubject = oMessage.subject(); aDraftInfo = oMessage.aDraftInfo; oText = $(oMessage.body).clone(); if (oText) { - oText.find('blockquote.rl-bq-switcher').each(function () { - $(this).removeClass('rl-bq-switcher hidden-bq'); - }); - oText.find('.rlBlockquoteSwitcher').each(function () { - $(this).remove(); - }); - } + oText.find('blockquote.rl-bq-switcher').removeClass('rl-bq-switcher hidden-bq'); + oText.find('.rlBlockquoteSwitcher').off('.rlBlockquoteSwitcher').remove(); + oText.find('[data-html-editor-font-wrapper]').removeAttr('data-html-editor-font-wrapper'); - oText.find('[data-html-editor-font-wrapper]').removeAttr('data-html-editor-font-wrapper'); - sText = oText.html(); + sText = oText.html(); + } switch (sComposeType) { @@ -813,7 +1106,7 @@ break; case Enums.ComposeType.Reply: - this.to(fEmailArrayToStringLineHelper(oMessage.replyEmails(oExcludeEmail))); + this.to(this.emailArrayToStringLineHelper(oMessage.replyEmails(oExcludeEmail))); this.subject(Utils.replySubjectAdd('Re', sSubject)); this.prepearMessageAttachments(oMessage, sComposeType); this.aDraftInfo = ['reply', oMessage.uid, oMessage.folderFullNameRaw]; @@ -823,8 +1116,8 @@ case Enums.ComposeType.ReplyAll: aResplyAllParts = oMessage.replyAllEmails(oExcludeEmail); - this.to(fEmailArrayToStringLineHelper(aResplyAllParts[0])); - this.cc(fEmailArrayToStringLineHelper(aResplyAllParts[1])); + this.to(this.emailArrayToStringLineHelper(aResplyAllParts[0])); + this.cc(this.emailArrayToStringLineHelper(aResplyAllParts[1])); this.subject(Utils.replySubjectAdd('Re', sSubject)); this.prepearMessageAttachments(oMessage, sComposeType); this.aDraftInfo = ['reply', oMessage.uid, oMessage.folderFullNameRaw]; @@ -849,9 +1142,10 @@ break; case Enums.ComposeType.Draft: - this.to(fEmailArrayToStringLineHelper(oMessage.to)); - this.cc(fEmailArrayToStringLineHelper(oMessage.cc)); - this.bcc(fEmailArrayToStringLineHelper(oMessage.bcc)); + this.to(this.emailArrayToStringLineHelper(oMessage.to)); + this.cc(this.emailArrayToStringLineHelper(oMessage.cc)); + this.bcc(this.emailArrayToStringLineHelper(oMessage.bcc)); + this.replyTo(this.emailArrayToStringLineHelper(oMessage.replyTo)); this.bFromDraft = true; @@ -867,9 +1161,10 @@ break; case Enums.ComposeType.EditAsNew: - this.to(fEmailArrayToStringLineHelper(oMessage.to)); - this.cc(fEmailArrayToStringLineHelper(oMessage.cc)); - this.bcc(fEmailArrayToStringLineHelper(oMessage.bcc)); + this.to(this.emailArrayToStringLineHelper(oMessage.to)); + this.cc(this.emailArrayToStringLineHelper(oMessage.cc)); + this.bcc(this.emailArrayToStringLineHelper(oMessage.bcc)); + this.replyTo(this.emailArrayToStringLineHelper(oMessage.replyTo)); this.subject(sSubject); this.prepearMessageAttachments(oMessage, sComposeType); @@ -885,13 +1180,13 @@ case Enums.ComposeType.Reply: case Enums.ComposeType.ReplyAll: sFrom = oMessage.fromToLine(false, true); - sReplyTitle = Utils.i18n('COMPOSE/REPLY_MESSAGE_TITLE', { + sReplyTitle = Translator.i18n('COMPOSE/REPLY_MESSAGE_TITLE', { 'DATETIME': sDate, 'EMAIL': sFrom }); sText = '

' + sReplyTitle + ':' + - '

' + sText + '

'; + '

' + Utils.trim(sText) + '

'; break; @@ -899,31 +1194,35 @@ 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; + sText = '

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

' + Utils.trim(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, sComposeType); - } - this.editor(function (oEditor) { + oEditor.setHtml(sText, false); - if (!oMessage.isHtml()) + + if (Enums.EditorDefaultType.PlainForced === self.editorDefaultType() || + (!oMessage.isHtml() && Enums.EditorDefaultType.HtmlForced !== self.editorDefaultType())) { oEditor.modeToggle(false); } + + if (oIdentity && Enums.ComposeType.Draft !== sComposeType && Enums.ComposeType.EditAsNew !== sComposeType) + { + self.setSignatureFromIdentity(oIdentity); + } + + self.setFocusInPopup(); }); } else if (Enums.ComposeType.Empty === sComposeType) @@ -931,18 +1230,23 @@ this.subject(Utils.isNormal(sCustomSubject) ? '' + sCustomSubject : ''); sText = Utils.isNormal(sCustomPlainText) ? '' + sCustomPlainText : ''; - if (bSignatureToAll && '' !== sSignature) - { - sText = this.convertSignature(sSignature, '', - Utils.convertPlainTextToHtml(sText), sComposeType); - } this.editor(function (oEditor) { + oEditor.setHtml(sText, false); - if (Enums.EditorDefaultType.Html !== Data.editorDefaultType()) + + if (Enums.EditorDefaultType.Html !== self.editorDefaultType() && + Enums.EditorDefaultType.HtmlForced !== self.editorDefaultType()) { oEditor.modeToggle(false); } + + if (oIdentity) + { + self.setSignatureFromIdentity(oIdentity); + } + + self.setFocusInPopup(); }); } else if (Utils.isNonEmptyArray(oMessageOrArray)) @@ -950,66 +1254,101 @@ _.each(oMessageOrArray, function (oMessage) { self.addMessageAsAttachment(oMessage); }); + + this.editor(function (oEditor) { + + oEditor.setHtml('', false); + + if (Enums.EditorDefaultType.Html !== self.editorDefaultType() && + Enums.EditorDefaultType.HtmlForced !== self.editorDefaultType()) + { + oEditor.modeToggle(false); + } + + if (oIdentity && Enums.ComposeType.Draft !== sComposeType && Enums.ComposeType.EditAsNew !== sComposeType) + { + self.setSignatureFromIdentity(oIdentity); + } + + self.setFocusInPopup(); + }); + } + else + { + this.setFocusInPopup(); } aDownloads = this.getAttachmentsDownloadsForUpload(); if (Utils.isNonEmptyArray(aDownloads)) { - Remote.messageUploadAttachments(function (sResult, oData) { + Remote.messageUploadAttachments(this.onMessageUploadAttachments, aDownloads); + } - if (Enums.StorageResultType.Success === sResult && oData && oData.Result) + if (oIdentity) + { + this.currentIdentity(oIdentity); + } + + this.resizerTrigger(); + }; + + ComposePopupView.prototype.onMessageUploadAttachments = function (sResult, oData) + { + if (Enums.StorageResultType.Success === sResult && oData && oData.Result) + { + var + oAttachment = null, + sTempName = '' + ; + + if (!this.viewModelVisibility()) + { + for (sTempName in oData.Result) { - var - oAttachment = null, - sTempName = '' - ; - - if (!self.viewModelVisibility()) + if (oData.Result.hasOwnProperty(sTempName)) { - for (sTempName in oData.Result) + oAttachment = this.getAttachmentById(oData.Result[sTempName]); + if (oAttachment) { - if (oData.Result.hasOwnProperty(sTempName)) - { - oAttachment = self.getAttachmentById(oData.Result[sTempName]); - if (oAttachment) - { - oAttachment.tempName(sTempName); - } - } + oAttachment.tempName(sTempName); + oAttachment.waiting(false).uploading(false).complete(true); } } } - else + } + } + else + { + this.setMessageAttachmentFailedDownloadText(); + } + }; + + ComposePopupView.prototype.setFocusInPopup = function () + { + if (!Globals.bMobileDevice) + { + var self = this; + _.delay(function () { + + if ('' === self.to()) { - self.setMessageAttachmentFailedDowbloadText(); + self.to.focused(true); + } + else if (self.oEditor) + { + if (!self.to.focused()) + { + self.oEditor.focus(); + } } - }, aDownloads); + }, 100); } - - this.triggerForResize(); }; - ComposePopupView.prototype.onFocus = function () + ComposePopupView.prototype.onShowWithDelay = function () { - if ('' === this.to()) - { - this.to.focusTrigger(!this.to.focusTrigger()); - } - else if (this.oEditor) - { - this.oEditor.focus(); - } - - this.triggerForResize(); - }; - - ComposePopupView.prototype.editorResize = function () - { - if (this.oEditor) - { - this.oEditor.resize(); - } + this.resizerTrigger(); }; ComposePopupView.prototype.tryToClosePopup = function () @@ -1019,14 +1358,21 @@ PopupsAskViewModel = require('View/Popup/Ask') ; - if (!kn.isPopupVisible(PopupsAskViewModel)) + if (!kn.isPopupVisible(PopupsAskViewModel) && this.modalVisibility()) { - kn.showScreenPopup(PopupsAskViewModel, [Utils.i18n('POPUPS_ASK/DESC_WANT_CLOSE_THIS_WINDOW'), function () { - if (self.modalVisibility()) - { - Utils.delegateRun(self, 'closeCommand'); - } - }]); + if (this.bSkipNextHide || (this.isEmptyForm() && !this.draftUid())) + { + Utils.delegateRun(self, 'closeCommand'); + } + else + { + kn.showScreenPopup(PopupsAskViewModel, [Translator.i18n('POPUPS_ASK/DESC_WANT_CLOSE_THIS_WINDOW'), function () { + if (self.modalVisibility()) + { + Utils.delegateRun(self, 'closeCommand'); + } + }]); + } } }; @@ -1049,10 +1395,13 @@ return false; }); - key('ctrl+enter, command+enter', Enums.KeyState.Compose, function () { - self.sendCommand(); - return false; - }); + if (!!Settings.settingsGet('AllowCtrlEnterOnCompose')) + { + key('ctrl+enter, command+enter', Enums.KeyState.Compose, function () { + self.sendCommand(); + return false; + }); + } key('esc', Enums.KeyState.Compose, function () { if (self.modalVisibility()) @@ -1062,16 +1411,15 @@ return false; }); - Globals.$win.on('resize', function () { - self.triggerForResize(); - }); + Events.sub('window.resize.real', this.resizerTrigger); + Events.sub('window.resize.real', _.debounce(this.resizerTrigger, 50)); if (this.dropboxEnabled()) { oScript = window.document.createElement('script'); oScript.type = 'text/javascript'; oScript.src = 'https://www.dropbox.com/static/api/1/dropins.js'; - $(oScript).attr('id', 'dropboxjs').attr('data-app-key', Settings.settingsGet('DropboxApiKey')); + $(oScript).attr('id', 'dropboxjs').attr('data-app-key', self.dropboxApiKey()); window.document.body.appendChild(oScript); } @@ -1085,6 +1433,13 @@ } }); } + + window.setInterval(function () { + if (self.modalVisibility() && self.oEditor) + { + self.oEditor.resize(); + } + }, 5000); }; ComposePopupView.prototype.driveCallback = function (sAccessToken, oData) @@ -1166,14 +1521,13 @@ if (window.google && window.google.picker) { var drivePicker = new window.google.picker.PickerBuilder() - .addView( - new window.google.picker.DocsView() - .setIncludeFolders(true) - ) + // .addView(window.google.picker.ViewId.FOLDERS) + .addView(window.google.picker.ViewId.DOCS) .setAppId(Settings.settingsGet('GoogleClientID')) .setOAuthToken(oOauthToken.access_token) .setCallback(_.bind(self.driveCallback, self, oOauthToken.access_token)) .enableFeature(window.google.picker.Feature.NAV_HIDDEN) + // .setOrigin(window.location.protocol + '//' + window.location.host) .build() ; @@ -1191,14 +1545,9 @@ window.gapi.load('auth', {'callback': function () { - var oAuthToken = window.gapi.auth.getToken(); - if (!oAuthToken) - { - window.gapi.auth.authorize({ - 'client_id': Settings.settingsGet('GoogleClientID'), - 'scope': 'https://www.googleapis.com/auth/drive.readonly', - 'immediate': true - }, function (oAuthResult) { + var + oAuthToken = window.gapi.auth.getToken(), + fResult = function (oAuthResult) { if (oAuthResult && !oAuthResult.error) { var oAuthToken = window.gapi.auth.getToken(); @@ -1206,23 +1555,29 @@ { self.driveCreatePiker(oAuthToken); } + + return true; } - else + + return false; + } + ; + + if (!oAuthToken) + { + window.gapi.auth.authorize({ + 'client_id': Settings.settingsGet('GoogleClientID'), + 'scope': 'https://www.googleapis.com/auth/drive.readonly', + 'immediate': true + }, function (oAuthResult) { + + if (!fResult(oAuthResult)) { window.gapi.auth.authorize({ 'client_id': Settings.settingsGet('GoogleClientID'), 'scope': 'https://www.googleapis.com/auth/drive.readonly', 'immediate': false - }, function (oAuthResult) { - if (oAuthResult && !oAuthResult.error) - { - var oAuthToken = window.gapi.auth.getToken(); - if (oAuthToken) - { - self.driveCreatePiker(oAuthToken); - } - } - }); + }, fResult); } }); } @@ -1257,6 +1612,29 @@ return null; }; + ComposePopupView.prototype.cancelAttachmentHelper = function (sId, oJua) { + + var self = this; + return function () { + + var oItem = _.find(self.attachments(), function (oItem) { + return oItem && oItem.id === sId; + }); + + if (oItem) + { + self.attachments.remove(oItem); + Utils.delegateRunOnDestroy(oItem); + + if (oJua) + { + oJua.cancel(sId); + } + } + }; + + }; + ComposePopupView.prototype.initUploader = function () { if (this.composeUploaderButton()) @@ -1265,11 +1643,10 @@ oUploadCache = {}, iAttachmentSizeLimit = Utils.pInt(Settings.settingsGet('AttachmentLimit')), oJua = new Jua({ - 'action': LinkBuilder.upload(), + 'action': Links.upload(), 'name': 'uploader', 'queueSize': 2, 'multipleSizeLimit': 50, - 'disableFolderDragAndDrop': false, 'clickElement': this.composeUploaderButton(), 'dragAndDropElement': this.composeUploaderDropPlace() }) @@ -1288,6 +1665,7 @@ this.dragAndDropOver(false); }, this)) .on('onBodyDragEnter', _.bind(function () { + this.attachmentsPlace(true); this.dragAndDropVisible(true); }, this)) .on('onBodyDragLeave', _.bind(function () { @@ -1310,7 +1688,7 @@ if (oItem) { - oItem.progress(' - ' + window.Math.floor(iLoaded / iTotal * 100) + '%'); + oItem.progress(window.Math.floor(iLoaded / iTotal * 100)); } }, this)) @@ -1325,26 +1703,18 @@ oAttachment = new ComposeAttachmentModel(sId, sFileName, mSize) ; - oAttachment.cancel = (function (sId) { - - return function () { - that.attachments.remove(function (oItem) { - return oItem && oItem.id === sId; - }); - - if (oJua) - { - oJua.cancel(sId); - } - }; - - }(sId)); + oAttachment.cancel = that.cancelAttachmentHelper(sId, oJua); this.attachments.push(oAttachment); + this.attachmentsPlace(true); + if (0 < mSize && 0 < iAttachmentSizeLimit && iAttachmentSizeLimit < mSize) { - oAttachment.error(Utils.i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG')); + oAttachment + .waiting(false).uploading(true).complete(true) + .error(Translator.i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG')); + return false; } @@ -1372,8 +1742,7 @@ if (oItem) { - oItem.waiting(false); - oItem.uploading(true); + oItem.waiting(false).uploading(true).complete(false); } }, this)) @@ -1391,11 +1760,11 @@ if (null !== mErrorCode) { - sError = Utils.getUploadErrorDescByCode(mErrorCode); + sError = Translator.getUploadErrorDescByCode(mErrorCode); } else if (!oAttachmentJson) { - sError = Utils.i18n('UPLOAD/ERROR_UNKNOWN'); + sError = Translator.i18n('UPLOAD/ERROR_UNKNOWN'); } if (oAttachment) @@ -1405,6 +1774,7 @@ oAttachment .waiting(false) .uploading(false) + .complete(true) .error(sError) ; } @@ -1413,6 +1783,7 @@ oAttachment .waiting(false) .uploading(false) + .complete(true) ; oAttachment.initByUploadJson(oAttachmentJson); @@ -1471,16 +1842,8 @@ if (oMessage) { var - self = this, oAttachment = null, - sTemp = oMessage.subject(), - fCancelFunc = function (sId) { - return function () { - self.attachments.remove(function (oItem) { - return oItem && oItem.id === sId; - }); - }; - } + sTemp = oMessage.subject() ; sTemp = '.eml' === sTemp.substr(-4).toLowerCase() ? sTemp : sTemp + '.eml'; @@ -1489,8 +1852,8 @@ ); oAttachment.fromMessage = true; - oAttachment.cancel = fCancelFunc(oMessage.requestHash); - oAttachment.waiting(false).uploading(true); + oAttachment.cancel = this.cancelAttachmentHelper(oMessage.requestHash); + oAttachment.waiting(false).uploading(true).complete(true); this.attachments.push(oAttachment); } @@ -1503,15 +1866,7 @@ ComposePopupView.prototype.addDropboxAttachment = function (oDropboxFile) { var - self = this, oAttachment = null, - fCancelFunc = function (sId) { - return function () { - self.attachments.remove(function (oItem) { - return oItem && oItem.id === sId; - }); - }; - }, iAttachmentSizeLimit = Utils.pInt(Settings.settingsGet('AttachmentLimit')), mSize = oDropboxFile['bytes'] ; @@ -1521,22 +1876,24 @@ ); oAttachment.fromMessage = false; - oAttachment.cancel = fCancelFunc(oDropboxFile['link']); - oAttachment.waiting(false).uploading(true); + oAttachment.cancel = this.cancelAttachmentHelper(oDropboxFile['link']); + oAttachment.waiting(false).uploading(true).complete(false); this.attachments.push(oAttachment); + this.attachmentsPlace(true); + if (0 < mSize && 0 < iAttachmentSizeLimit && iAttachmentSizeLimit < mSize) { - oAttachment.uploading(false); - oAttachment.error(Utils.i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG')); + oAttachment.uploading(false).complete(true); + oAttachment.error(Translator.i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG')); return false; } Remote.composeUploadExternals(function (sResult, oData) { var bResult = false; - oAttachment.uploading(false); + oAttachment.uploading(false).complete(true); if (Enums.StorageResultType.Success === sResult && oData && oData.Result) { @@ -1549,7 +1906,7 @@ if (!bResult) { - oAttachment.error(Utils.getUploadErrorDescByCode(Enums.UploadErrorCode.FileNoUploaded)); + oAttachment.error(Translator.getUploadErrorDescByCode(Enums.UploadErrorCode.FileNoUploaded)); } }, [oDropboxFile['link']]); @@ -1565,14 +1922,6 @@ ComposePopupView.prototype.addDriveAttachment = function (oDriveFile, sAccessToken) { var - self = this, - fCancelFunc = function (sId) { - return function () { - self.attachments.remove(function (oItem) { - return oItem && oItem.id === sId; - }); - }; - }, iAttachmentSizeLimit = Utils.pInt(Settings.settingsGet('AttachmentLimit')), oAttachment = null, mSize = oDriveFile['fileSize'] ? Utils.pInt(oDriveFile['fileSize']) : 0 @@ -1583,22 +1932,24 @@ ); oAttachment.fromMessage = false; - oAttachment.cancel = fCancelFunc(oDriveFile['downloadUrl']); - oAttachment.waiting(false).uploading(true); + oAttachment.cancel = this.cancelAttachmentHelper(oDriveFile['downloadUrl']); + oAttachment.waiting(false).uploading(true).complete(false); this.attachments.push(oAttachment); + this.attachmentsPlace(true); + if (0 < mSize && 0 < iAttachmentSizeLimit && iAttachmentSizeLimit < mSize) { - oAttachment.uploading(false); - oAttachment.error(Utils.i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG')); + oAttachment.uploading(false).complete(true); + oAttachment.error(Translator.i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG')); return false; } Remote.composeUploadDrive(function (sResult, oData) { var bResult = false; - oAttachment.uploading(false); + oAttachment.uploading(false).complete(true); if (Enums.StorageResultType.Success === sResult && oData && oData.Result) { @@ -1612,7 +1963,7 @@ if (!bResult) { - oAttachment.error(Utils.getUploadErrorDescByCode(Enums.UploadErrorCode.FileNoUploaded)); + oAttachment.error(Translator.getUploadErrorDescByCode(Enums.UploadErrorCode.FileNoUploaded)); } }, oDriveFile['downloadUrl'], sAccessToken); @@ -1629,20 +1980,12 @@ if (oMessage) { var - self = this, aAttachments = Utils.isNonEmptyArray(oMessage.attachments()) ? oMessage.attachments() : [], iIndex = 0, iLen = aAttachments.length, oAttachment = null, oItem = null, - bAdd = false, - fCancelFunc = function (sId) { - return function () { - self.attachments.remove(function (oItem) { - return oItem && oItem.id === sId; - }); - }; - } + bAdd = false ; if (Enums.ComposeType.ForwardAsAttachment === sType) @@ -1677,8 +2020,8 @@ ); oAttachment.fromMessage = true; - oAttachment.cancel = fCancelFunc(oItem.download); - oAttachment.waiting(false).uploading(true); + oAttachment.cancel = this.cancelAttachmentHelper(oItem.download); + oAttachment.waiting(false).uploading(true).complete(false); this.attachments.push(oAttachment); } @@ -1689,12 +2032,18 @@ ComposePopupView.prototype.removeLinkedAttachments = function () { - this.attachments.remove(function (oItem) { + var oItem = _.find(this.attachments(), function (oItem) { return oItem && oItem.isLinked; }); + + if (oItem) + { + this.attachments.remove(oItem); + Utils.delegateRunOnDestroy(oItem); + } }; - ComposePopupView.prototype.setMessageAttachmentFailedDowbloadText = function () + ComposePopupView.prototype.setMessageAttachmentFailedDownloadText = function () { _.each(this.attachments(), function(oAttachment) { if (oAttachment && oAttachment.fromMessage) @@ -1702,7 +2051,8 @@ oAttachment .waiting(false) .uploading(false) - .error(Utils.getUploadErrorDescByCode(Enums.UploadErrorCode.FileNoUploaded)) + .complete(true) + .error(Translator.getUploadErrorDescByCode(Enums.UploadErrorCode.FileNoUploaded)) ; } }, this); @@ -1715,14 +2065,15 @@ ComposePopupView.prototype.isEmptyForm = function (bIncludeAttachmentInProgress) { bIncludeAttachmentInProgress = Utils.isUnd(bIncludeAttachmentInProgress) ? true : !!bIncludeAttachmentInProgress; - var bAttach = bIncludeAttachmentInProgress ? + var bWithoutAttach = bIncludeAttachmentInProgress ? 0 === this.attachments().length : 0 === this.attachmentsInReady().length; return 0 === this.to().length && 0 === this.cc().length && 0 === this.bcc().length && + 0 === this.replyTo().length && 0 === this.subject().length && - bAttach && + bWithoutAttach && (!this.oEditor || '' === this.oEditor.getData()) ; }; @@ -1735,7 +2086,11 @@ this.replyTo(''); this.subject(''); + this.requestDsn(false); this.requestReadReceipt(false); + this.markAsImportant(false); + + this.attachmentsPlace(false); this.aDraftInfo = null; this.sInReplyTo = ''; @@ -1746,12 +2101,16 @@ this.sendSuccessButSaveError(false); this.savedError(false); this.savedTime(0); - this.savedOrSendingText(''); this.emptyToError(false); this.attachmentsInProcessError(false); - this.showCcAndBcc(false); + this.showCc(false); + this.showBcc(false); + this.showReplyTo(false); + + Utils.delegateRunOnDestroy(this.attachments()); this.attachments([]); + this.dragAndDropOver(false); this.dragAndDropVisible(false); @@ -1779,10 +2138,9 @@ }); }; - ComposePopupView.prototype.triggerForResize = function () + ComposePopupView.prototype.resizerTrigger = function () { this.resizer(!this.resizer()); - this.editorResizeThrottle(); }; module.exports = ComposePopupView; diff --git a/dev/View/Popup/ComposeOpenPgp.js b/dev/View/Popup/ComposeOpenPgp.js index 2fbadc588..e77a82f18 100644 --- a/dev/View/Popup/ComposeOpenPgp.js +++ b/dev/View/Popup/ComposeOpenPgp.js @@ -10,8 +10,9 @@ Utils = require('Common/Utils'), Enums = require('Common/Enums'), + Translator = require('Common/Translator'), - Data = require('Storage/App/Data'), + PgpStore = require('Stores/User/Pgp'), EmailModel = require('Model/Email'), @@ -58,16 +59,16 @@ if (bResult && this.sign() && '' === this.from()) { - this.notification(Utils.i18n('PGP_NOTIFICATIONS/SPECIFY_FROM_EMAIL')); + this.notification(Translator.i18n('PGP_NOTIFICATIONS/SPECIFY_FROM_EMAIL')); bResult = false; } if (bResult && this.sign()) { - oPrivateKey = Data.findPrivateKeyByEmail(this.from(), this.password()); + oPrivateKey = PgpStore.findPrivateKeyByEmail(this.from(), this.password()); if (!oPrivateKey) { - this.notification(Utils.i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND_FOR', { + this.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND_FOR', { 'EMAIL': this.from() })); @@ -77,7 +78,7 @@ if (bResult && this.encrypt() && 0 === this.to().length) { - this.notification(Utils.i18n('PGP_NOTIFICATIONS/SPECIFY_AT_LEAST_ONE_RECIPIENT')); + this.notification(Translator.i18n('PGP_NOTIFICATIONS/SPECIFY_AT_LEAST_ONE_RECIPIENT')); bResult = false; } @@ -85,10 +86,10 @@ { aPublicKeys = []; _.each(this.to(), function (sEmail) { - var aKeys = Data.findPublicKeysByEmail(sEmail); + var aKeys = PgpStore.findPublicKeysByEmail(sEmail); if (0 === aKeys.length && bResult) { - self.notification(Utils.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND_FOR', { + self.notification(Translator.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND_FOR', { 'EMAIL': sEmail })); @@ -113,25 +114,25 @@ if (oPrivateKey && 0 === aPublicKeys.length) { self.resultCallback( - Data.openpgp.signClearMessage([oPrivateKey], self.text()) + PgpStore.openpgp.signClearMessage([oPrivateKey], self.text()) ); } else if (oPrivateKey && 0 < aPublicKeys.length) { self.resultCallback( - Data.openpgp.signAndEncryptMessage(aPublicKeys, oPrivateKey, self.text()) + PgpStore.openpgp.signAndEncryptMessage(aPublicKeys, oPrivateKey, self.text()) ); } else if (!oPrivateKey && 0 < aPublicKeys.length) { self.resultCallback( - Data.openpgp.encryptMessage(aPublicKeys, self.text()) + PgpStore.openpgp.encryptMessage(aPublicKeys, self.text()) ); } } catch (e) { - self.notification(Utils.i18n('PGP_NOTIFICATIONS/PGP_ERROR', { + self.notification(Translator.i18n('PGP_NOTIFICATIONS/PGP_ERROR', { 'ERROR': '' + e })); @@ -196,12 +197,12 @@ }, this)); }; - ComposeOpenPgpPopupView.prototype.onHide = function () + ComposeOpenPgpPopupView.prototype.onHideWithDelay = function () { this.clearPopup(); }; - ComposeOpenPgpPopupView.prototype.onFocus = function () + ComposeOpenPgpPopupView.prototype.onShowWithDelay = function () { if (this.sign()) { @@ -213,25 +214,17 @@ } }; - ComposeOpenPgpPopupView.prototype.onShow = function (fCallback, sText, sFromEmail, sTo, sCc, sBcc) + ComposeOpenPgpPopupView.prototype.onShow = function (fCallback, sText, oIdentity, sTo, sCc, sBcc) { this.clearPopup(); var - oEmail = new EmailModel(), - sResultFromEmail = '', - aRec = [] + aRec = [], + oEmail = new EmailModel() ; this.resultCallback = fCallback; - oEmail.clear(); - oEmail.mailsoParse(sFromEmail); - if ('' !== oEmail.email) - { - sResultFromEmail = oEmail.email; - } - if ('' !== sTo) { aRec.push(sTo); @@ -254,7 +247,7 @@ return '' === oEmail.email ? false : oEmail.email; })); - this.from(sResultFromEmail); + this.from(oIdentity ? oIdentity.email() : ''); this.to(aRec); this.text(sText); }; diff --git a/dev/View/Popup/Contacts.js b/dev/View/Popup/Contacts.js index 7d26e03ce..0cf7c8b17 100644 --- a/dev/View/Popup/Contacts.js +++ b/dev/View/Popup/Contacts.js @@ -15,14 +15,16 @@ Globals = require('Common/Globals'), Utils = require('Common/Utils'), Selector = require('Common/Selector'), - LinkBuilder = require('Common/LinkBuilder'), + Links = require('Common/Links'), + Translator = require('Common/Translator'), - Data = require('Storage/App/Data'), - Remote = require('Storage/App/Remote'), + SettingsStore = require('Stores/User/Settings'), + ContactStore = require('Stores/User/Contact'), + + Remote = require('Remote/User/Ajax'), EmailModel = require('Model/Email'), ContactModel = require('Model/Contact'), - ContactTagModel = require('Model/ContactTag'), ContactPropertyModel = require('Model/ContactProperty'), kn = require('Knoin/Knoin'), @@ -42,18 +44,21 @@ fFastClearEmptyListHelper = function (aList) { if (aList && 0 < aList.length) { self.viewProperties.removeAll(aList); + Utils.delegateRunOnDestroy(aList); } } ; - this.allowContactsSync = Data.allowContactsSync; - this.enableContactsSync = Data.enableContactsSync; + this.bBackToCompose = false; + this.sLastComposeFocusedField = ''; + + this.allowContactsSync = ContactStore.allowContactsSync; + this.enableContactsSync = ContactStore.enableContactsSync; this.allowExport = !Globals.bMobileDevice; this.search = ko.observable(''); this.contactsCount = ko.observable(0); - this.contacts = Data.contacts; - this.contactTags = Data.contactTags; + this.contacts = ContactStore.contacts; this.currentContact = ko.observable(null); @@ -74,21 +79,6 @@ this.viewReadOnly = ko.observable(false); this.viewProperties = ko.observableArray([]); - this.viewTags = ko.observable(''); - this.viewTags.visibility = ko.observable(false); - this.viewTags.focusTrigger = ko.observable(false); - - this.viewTags.focusTrigger.subscribe(function (bValue) { - if (!bValue && '' === this.viewTags()) - { - this.viewTags.visibility(false); - } - else if (bValue) - { - this.viewTags.visibility(true); - } - }, this); - this.viewSaveTrigger = ko.observable(Enums.SaveSettingsStep.Idle); this.viewPropertiesNames = this.viewProperties.filter(function(oProperty) { @@ -186,19 +176,14 @@ this.viewSaving = ko.observable(false); - this.useCheckboxesInList = Data.useCheckboxesInList; + this.useCheckboxesInList = SettingsStore.useCheckboxesInList; this.search.subscribe(function () { this.reloadContactList(); }, this); - this.contacts.subscribe(function () { - Utils.windowResize(); - }, this); - - this.viewProperties.subscribe(function () { - Utils.windowResize(); - }, this); + this.contacts.subscribe(Utils.windowResizeCallback); + this.viewProperties.subscribe(Utils.windowResizeCallback); this.contactsChecked = ko.computed(function () { return _.filter(this.contacts(), function (oItem) { @@ -223,7 +208,7 @@ }); }, this); - this.selector = new Selector(this.contacts, this.currentContact, + this.selector = new Selector(this.contacts, this.currentContact, null, '.e-contact-item .actionHandle', '.e-contact-item.selected', '.e-contact-item .checkboxItem', '.e-contact-item.focused'); @@ -252,7 +237,14 @@ }); this.newMessageCommand = Utils.createCommand(this, function () { - var aC = this.contactsCheckedOrSelected(), aE = []; + var + aE = [], + aC = this.contactsCheckedOrSelected(), + aToEmails = null, + aCcEmails = null, + aBccEmails = null + ; + if (Utils.isNonEmptyArray(aC)) { aE = _.map(aC, function (oItem) { @@ -277,8 +269,30 @@ if (Utils.isNonEmptyArray(aE)) { + self.bBackToCompose = false; + kn.hideScreenPopup(require('View/Popup/Contacts')); - kn.showScreenPopup(require('View/Popup/Compose'), [Enums.ComposeType.Empty, null, aE]); + + switch (self.sLastComposeFocusedField) + { + default: + case 'to': + aToEmails = aE; + break; + case 'cc': + aCcEmails = aE; + break; + case 'bcc': + aBccEmails = aE; + break; + } + + self.sLastComposeFocusedField = ''; + + _.delay(function () { + kn.showScreenPopup(require('View/Popup/Compose'), + [Enums.ComposeType.Empty, null, aToEmails, aCcEmails, aBccEmails]); + }, 200); } }, function () { @@ -336,7 +350,7 @@ }, 1000); } - }, sRequestUid, this.viewID(), this.viewTags(), aProperties); + }, sRequestUid, this.viewID(), aProperties); }, function () { var @@ -349,10 +363,10 @@ this.syncCommand = Utils.createCommand(this, function () { var self = this; - require('App/App').contactsSync(function (sResult, oData) { + require('App/User').contactsSync(function (sResult, oData) { if (Enums.StorageResultType.Success !== sResult || !oData || !oData.Result) { - window.alert(Utils.getNotification( + window.alert(Translator.getNotification( oData && oData.ErrorCode ? oData.ErrorCode : Enums.Notification.ContactsSyncError)); } @@ -369,7 +383,7 @@ this.watchHash = ko.observable(false); this.viewHash = ko.computed(function () { - return '' + self.viewTags() + '|' + _.map(self.viewProperties(), function (oItem) { + return '' + _.map(self.viewProperties(), function (oItem) { return oItem.value(); }).join(''); }); @@ -385,24 +399,13 @@ this.sDefaultKeyScope = Enums.KeyState.ContactList; - this.contactTagsSource = _.bind(this.contactTagsSource, this); - kn.constructorEnd(this); } kn.extendAsViewModel(['View/Popup/Contacts', 'PopupsContactsViewModel'], ContactsPopupView); _.extend(ContactsPopupView.prototype, AbstractView.prototype); - ContactsPopupView.prototype.contactTagsSource = function (oData, fResponse) - { - require('App/App').getContactTagsAutocomplete(oData.term, function (aData) { - fResponse(_.map(aData, function (oTagItem) { - return oTagItem.toLine(false); - })); - }); - }; - - ContactsPopupView.prototype.getPropertyPlceholder = function (sType) + ContactsPopupView.prototype.getPropertyPlaceholder = function (sType) { var sResult = ''; switch (sType) @@ -423,7 +426,7 @@ ContactsPopupView.prototype.addNewProperty = function (sType, sTypeStr) { - this.viewProperties.push(new ContactPropertyModel(sType, sTypeStr || '', '', true, this.getPropertyPlceholder(sType))); + this.viewProperties.push(new ContactPropertyModel(sType, sTypeStr || '', '', true, this.getPropertyPlaceholder(sType))); }; ContactsPopupView.prototype.addNewOrFocusProperty = function (sType, sTypeStr) @@ -442,12 +445,6 @@ } }; - ContactsPopupView.prototype.addNewTag = function () - { - this.viewTags.visibility(true); - this.viewTags.focusTrigger(true); - }; - ContactsPopupView.prototype.addNewEmail = function () { this.addNewProperty(Enums.ContactPropertyType.Email, 'Home'); @@ -480,12 +477,12 @@ ContactsPopupView.prototype.exportVcf = function () { - require('App/App').download(LinkBuilder.exportContactsVcf()); + require('App/User').download(Links.exportContactsVcf()); }; ContactsPopupView.prototype.exportCsv = function () { - require('App/App').download(LinkBuilder.exportContactsCsv()); + require('App/User').download(Links.exportContactsCsv()); }; ContactsPopupView.prototype.initUploader = function () @@ -494,11 +491,10 @@ { var oJua = new Jua({ - 'action': LinkBuilder.uploadContacts(), + 'action': Links.uploadContacts(), 'name': 'uploader', 'queueSize': 1, 'multipleSizeLimit': 1, - 'disableFolderDragAndDrop': true, 'disableDragAndDrop': true, 'disableMultiple': true, 'disableDocumentDropPrevent': true, @@ -519,7 +515,7 @@ if (!sId || !bResult || !oData || !oData.Result) { - window.alert(Utils.i18n('CONTACTS/ERROR_IMPORT_FILE')); + window.alert(Translator.i18n('CONTACTS/ERROR_IMPORT_FILE')); } }, this)) @@ -561,6 +557,7 @@ _.each(aContacts, function (oContact) { oKoContacts.remove(oContact); + Utils.delegateRunOnDestroy(oContact); }); }, 500); @@ -603,6 +600,7 @@ ContactsPopupView.prototype.removeProperty = function (oProp) { this.viewProperties.remove(oProp); + Utils.delegateRunOnDestroy(oProp); }; /** @@ -621,7 +619,6 @@ this.emptySelection(false); this.viewReadOnly(false); - this.viewTags(''); if (oContact) { @@ -647,21 +644,19 @@ }); } - this.viewTags(oContact.tags); - this.viewReadOnly(!!oContact.readOnly); } - this.viewTags.focusTrigger.valueHasMutated(); - this.viewTags.visibility('' !== this.viewTags()); - aList.unshift(new ContactPropertyModel(Enums.ContactPropertyType.LastName, '', sLastName, false, - this.getPropertyPlceholder(Enums.ContactPropertyType.LastName))); + this.getPropertyPlaceholder(Enums.ContactPropertyType.LastName))); aList.unshift(new ContactPropertyModel(Enums.ContactPropertyType.FirstName, '', sFirstName, !oContact, - this.getPropertyPlceholder(Enums.ContactPropertyType.FirstName))); + this.getPropertyPlaceholder(Enums.ContactPropertyType.FirstName))); this.viewID(sId); + + Utils.delegateRunOnDestroy(this.viewProperties()); + this.viewProperties([]); this.viewProperties(aList); @@ -689,10 +684,10 @@ this.contacts.loading(true); Remote.contacts(function (sResult, oData) { + var iCount = 0, - aList = [], - aTagsList = [] + aList = [] ; if (Enums.StorageResultType.Success === sResult && oData && oData.Result && oData.Result.List) @@ -709,24 +704,14 @@ iCount = Utils.pInt(oData.Result.Count); iCount = 0 < iCount ? iCount : 0; } - - if (Utils.isNonEmptyArray(oData.Result.Tags)) - { - aTagsList = _.map(oData.Result.Tags, function (oItem) { - var oContactTag = new ContactTagModel(); - return oContactTag.parse(oItem) ? oContactTag : null; - }); - - aTagsList = _.compact(aTagsList); - } } self.contactsCount(iCount); + Utils.delegateRunOnDestroy(self.contacts()); self.contacts(aList); - self.contacts.loading(false); - self.contactTags(aTagsList); + self.contacts.loading(false); self.viewClearSearch('' !== self.search()); }, iOffset, Consts.Defaults.ContactsPerPage, this.search()); @@ -760,8 +745,11 @@ this.initUploader(); }; - ContactsPopupView.prototype.onShow = function () + ContactsPopupView.prototype.onShow = function (bBackToCompose, sLastComposeFocusedField) { + this.bBackToCompose = Utils.isUnd(bBackToCompose) ? false : !!bBackToCompose; + this.sLastComposeFocusedField = Utils.isUnd(sLastComposeFocusedField) ? '' : sLastComposeFocusedField; + kn.routeOff(); this.reloadContactList(true); }; @@ -769,11 +757,23 @@ ContactsPopupView.prototype.onHide = function () { kn.routeOn(); + this.currentContact(null); this.emptySelection(true); this.search(''); this.contactsCount(0); + + Utils.delegateRunOnDestroy(this.contacts()); this.contacts([]); + + this.sLastComposeFocusedField = ''; + + if (this.bBackToCompose) + { + this.bBackToCompose = false; + + kn.showScreenPopup(require('View/Popup/Compose')); + } }; module.exports = ContactsPopupView; diff --git a/dev/View/Popup/Domain.js b/dev/View/Popup/Domain.js index e9bb4448c..b981f7949 100644 --- a/dev/View/Popup/Domain.js +++ b/dev/View/Popup/Domain.js @@ -9,9 +9,14 @@ Enums = require('Common/Enums'), Consts = require('Common/Consts'), + Globals = require('Common/Globals'), Utils = require('Common/Utils'), - Remote = require('Storage/Admin/Remote'), + Translator = require('Common/Translator'), + + CapaAdminStore = require('Stores/Admin/Capa'), + + Remote = require('Remote/Admin/Ajax'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -28,13 +33,16 @@ this.edit = ko.observable(false); this.saving = ko.observable(false); this.savingError = ko.observable(''); - this.whiteListPage = ko.observable(false); + this.page = ko.observable('main'); + this.sieveSettings = ko.observable(false); this.testing = ko.observable(false); this.testingDone = ko.observable(false); this.testingImapError = ko.observable(false); + this.testingSieveError = ko.observable(false); this.testingSmtpError = ko.observable(false); this.testingImapErrorDesc = ko.observable(''); + this.testingSieveErrorDesc = ko.observable(''); this.testingSmtpErrorDesc = ko.observable(''); this.testingImapError.subscribe(function (bValue) { @@ -44,6 +52,13 @@ } }, this); + this.testingSieveError.subscribe(function (bValue) { + if (!bValue) + { + this.testingSieveErrorDesc(''); + } + }, this); + this.testingSmtpError.subscribe(function (bValue) { if (!bValue) { @@ -51,10 +66,8 @@ } }, this); - this.testingImapErrorDesc = ko.observable(''); - this.testingSmtpErrorDesc = ko.observable(''); - this.imapServerFocus = ko.observable(false); + this.sieveServerFocus = ko.observable(false); this.smtpServerFocus = ko.observable(false); this.name = ko.observable(''); @@ -64,25 +77,46 @@ this.imapPort = ko.observable('' + Consts.Values.ImapDefaulPort); this.imapSecure = ko.observable(Enums.ServerSecure.None); this.imapShortLogin = ko.observable(false); + this.useSieve = ko.observable(false); + this.sieveAllowRaw = ko.observable(false); + this.sieveServer = ko.observable(''); + this.sievePort = ko.observable('' + Consts.Values.SieveDefaulPort); + this.sieveSecure = ko.observable(Enums.ServerSecure.None); this.smtpServer = ko.observable(''); this.smtpPort = ko.observable('' + Consts.Values.SmtpDefaulPort); this.smtpSecure = ko.observable(Enums.ServerSecure.None); this.smtpShortLogin = ko.observable(false); this.smtpAuth = ko.observable(true); + this.smtpPhpMail = ko.observable(false); this.whiteList = ko.observable(''); + this.enableSmartPorts = ko.observable(false); + + this.allowSieve = ko.computed(function () { + return CapaAdminStore.filters() && CapaAdminStore.sieve(); + }, this); + this.headerText = ko.computed(function () { var sName = this.name(); - return this.edit() ? 'Edit Domain "' + sName + '"' : - 'Add Domain' + ('' === sName ? '' : ' "' + sName + '"'); + return this.edit() ? Translator.i18n('POPUPS_DOMAIN/TITLE_EDIT_DOMAIN', {'NAME': sName}) : + ('' === sName ? Translator.i18n('POPUPS_DOMAIN/TITLE_ADD_DOMAIN') : + Translator.i18n('POPUPS_DOMAIN/TITLE_ADD_DOMAIN_WITH_NAME', {'NAME': sName})); }, this); this.domainIsComputed = ko.computed(function () { + + var + bPhpMail = this.smtpPhpMail(), + bAllowSieve = this.allowSieve(), + bUseSieve = this.useSieve() + ; + return '' !== this.name() && '' !== this.imapServer() && '' !== this.imapPort() && - '' !== this.smtpServer() && - '' !== this.smtpPort(); + (bAllowSieve && bUseSieve ? ('' !== this.sieveServer() && '' !== this.sievePort()) : true) && + (('' !== this.smtpServer() && '' !== this.smtpPort()) || bPhpMail); + }, this); this.canBeTested = ko.computed(function () { @@ -99,42 +133,77 @@ _.bind(this.onDomainCreateOrSaveResponse, this), !this.edit(), this.name(), + this.imapServer(), Utils.pInt(this.imapPort()), this.imapSecure(), this.imapShortLogin(), + + this.useSieve(), + this.sieveAllowRaw(), + this.sieveServer(), + Utils.pInt(this.sievePort()), + this.sieveSecure(), + this.smtpServer(), Utils.pInt(this.smtpPort()), this.smtpSecure(), this.smtpShortLogin(), this.smtpAuth(), + this.smtpPhpMail(), + this.whiteList() ); }, this.canBeSaved); this.testConnectionCommand = Utils.createCommand(this, function () { - this.whiteListPage(false); + + this.page('main'); + this.testingDone(false); this.testingImapError(false); + this.testingSieveError(false); this.testingSmtpError(false); this.testing(true); + Remote.testConnectionForDomain( _.bind(this.onTestConnectionResponse, this), this.name(), + this.imapServer(), Utils.pInt(this.imapPort()), this.imapSecure(), + + this.useSieve(), + this.sieveServer(), + Utils.pInt(this.sievePort()), + this.sieveSecure(), + this.smtpServer(), Utils.pInt(this.smtpPort()), this.smtpSecure(), - this.smtpAuth() + this.smtpAuth(), + this.smtpPhpMail() ); }, this.canBeTested); this.whiteListCommand = Utils.createCommand(this, function () { - this.whiteListPage(!this.whiteListPage()); + this.page('white-list'); }); + this.backCommand = Utils.createCommand(this, function () { + this.page('main'); + }); + + this.sieveCommand = Utils.createCommand(this, function () { + this.sieveSettings(!this.sieveSettings()); + this.clearTesting(); + }); + + this.page.subscribe(function () { + this.sieveSettings(false); + }, this); + // smart form improvements this.imapServerFocus.subscribe(function (bValue) { if (bValue && '' !== this.name() && '' === this.imapServer()) @@ -143,6 +212,13 @@ } }, this); + this.sieveServerFocus.subscribe(function (bValue) { + if (bValue && '' !== this.imapServer() && '' === this.sieveServer()) + { + this.sieveServer(this.imapServer()); + } + }, this); + this.smtpServerFocus.subscribe(function (bValue) { if (bValue && '' !== this.imapServer() && '' === this.smtpServer()) { @@ -151,48 +227,54 @@ }, this); this.imapSecure.subscribe(function (sValue) { - var iPort = Utils.pInt(this.imapPort()); - sValue = Utils.pString(sValue); - switch (sValue) + if (this.enableSmartPorts()) { - case '0': - if (993 === iPort) - { - this.imapPort('143'); - } - break; - case '1': - if (143 === iPort) - { - this.imapPort('993'); - } - break; + var iPort = Utils.pInt(this.imapPort()); + sValue = Utils.pString(sValue); + switch (sValue) + { + case '0': + if (993 === iPort) + { + this.imapPort('143'); + } + break; + case '1': + if (143 === iPort) + { + this.imapPort('993'); + } + break; + } } }, this); this.smtpSecure.subscribe(function (sValue) { - var iPort = Utils.pInt(this.smtpPort()); - sValue = Utils.pString(sValue); - switch (sValue) + if (this.enableSmartPorts()) { - case '0': - if (465 === iPort || 587 === iPort) - { - this.smtpPort('25'); - } - break; - case '1': - if (25 === iPort || 587 === iPort) - { - this.smtpPort('465'); - } - break; - case '2': - if (25 === iPort || 465 === iPort) - { - this.smtpPort('587'); - } - break; + var iPort = Utils.pInt(this.smtpPort()); + sValue = Utils.pString(sValue); + switch (sValue) + { + case '0': + if (465 === iPort || 587 === iPort) + { + this.smtpPort('25'); + } + break; + case '1': + if (25 === iPort || 587 === iPort) + { + this.smtpPort('465'); + } + break; + case '2': + if (25 === iPort || 465 === iPort) + { + this.smtpPort('587'); + } + break; + } } }, this); @@ -207,24 +289,57 @@ this.testing(false); if (Enums.StorageResultType.Success === sResult && oData.Result) { + var + bImap = false, + bSieve = false + ; + this.testingDone(true); this.testingImapError(true !== oData.Result.Imap); + this.testingSieveError(true !== oData.Result.Sieve); this.testingSmtpError(true !== oData.Result.Smtp); if (this.testingImapError() && oData.Result.Imap) { + bImap = true; + this.testingImapErrorDesc(''); this.testingImapErrorDesc(oData.Result.Imap); } + if (this.testingSieveError() && oData.Result.Sieve) + { + bSieve = true; + this.testingSieveErrorDesc(''); + this.testingSieveErrorDesc(oData.Result.Sieve); + } + if (this.testingSmtpError() && oData.Result.Smtp) { + this.testingSmtpErrorDesc(''); this.testingSmtpErrorDesc(oData.Result.Smtp); } + + if (this.sieveSettings()) + { + if (!bSieve && bImap) + { + this.sieveSettings(false); + } + } + else + { + if (bSieve && !bImap) + { + this.sieveSettings(true); + } + } } else { this.testingImapError(true); + this.testingSieveError(true); this.testingSmtpError(true); + this.sieveSettings(false); } }; @@ -240,33 +355,45 @@ } else if (Enums.Notification.DomainAlreadyExists === oData.ErrorCode) { - this.savingError('Domain already exists'); + this.savingError(Translator.i18n('ERRORS/DOMAIN_ALREADY_EXISTS')); } } else { - this.savingError('Unknown error'); + this.savingError(Translator.i18n('ERRORS/UNKNOWN_ERROR')); } }; + DomainPopupView.prototype.clearTesting = function () + { + this.testing(false); + this.testingDone(false); + this.testingImapError(false); + this.testingSieveError(false); + this.testingSmtpError(false); + }; + DomainPopupView.prototype.onHide = function () { - this.whiteListPage(false); + this.page('main'); + this.sieveSettings(false); }; + DomainPopupView.prototype.onShow = function (oDomain) { this.saving(false); - this.whiteListPage(false); - this.testing(false); - this.testingDone(false); - this.testingImapError(false); - this.testingSmtpError(false); + this.page('main'); + this.sieveSettings(false); + + this.clearTesting(); this.clearForm(); if (oDomain) { + this.enableSmartPorts(false); + this.edit(true); this.name(Utils.trim(oDomain.Name)); @@ -274,18 +401,26 @@ this.imapPort('' + Utils.pInt(oDomain.IncPort)); this.imapSecure(Utils.trim(oDomain.IncSecure)); this.imapShortLogin(!!oDomain.IncShortLogin); + this.useSieve(!!oDomain.UseSieve); + this.sieveAllowRaw(!!oDomain.SieveAllowRaw); + this.sieveServer(Utils.trim(oDomain.SieveHost)); + this.sievePort('' + Utils.pInt(oDomain.SievePort)); + this.sieveSecure(Utils.trim(oDomain.SieveSecure)); this.smtpServer(Utils.trim(oDomain.OutHost)); this.smtpPort('' + Utils.pInt(oDomain.OutPort)); this.smtpSecure(Utils.trim(oDomain.OutSecure)); this.smtpShortLogin(!!oDomain.OutShortLogin); this.smtpAuth(!!oDomain.OutAuth); + this.smtpPhpMail(!!oDomain.OutUsePhpMail); this.whiteList(Utils.trim(oDomain.WhiteList)); + + this.enableSmartPorts(true); } }; - DomainPopupView.prototype.onFocus = function () + DomainPopupView.prototype.onShowWithDelay = function () { - if ('' === this.name()) + if ('' === this.name() && !Globals.bMobile) { this.name.focused(true); } @@ -294,7 +429,11 @@ DomainPopupView.prototype.clearForm = function () { this.edit(false); - this.whiteListPage(false); + + this.page('main'); + this.sieveSettings(false); + + this.enableSmartPorts(false); this.savingError(''); @@ -305,12 +444,22 @@ this.imapPort('' + Consts.Values.ImapDefaulPort); this.imapSecure(Enums.ServerSecure.None); this.imapShortLogin(false); + + this.useSieve(false); + this.sieveAllowRaw(false); + this.sieveServer(''); + this.sievePort('' + Consts.Values.SieveDefaulPort); + this.sieveSecure(Enums.ServerSecure.None); + this.smtpServer(''); this.smtpPort('' + Consts.Values.SmtpDefaulPort); this.smtpSecure(Enums.ServerSecure.None); this.smtpShortLogin(false); this.smtpAuth(true); + this.smtpPhpMail(false); + this.whiteList(''); + this.enableSmartPorts(true); }; module.exports = DomainPopupView; diff --git a/dev/View/Popup/Filter.js b/dev/View/Popup/Filter.js index 444c94ad3..c5f5ad428 100644 --- a/dev/View/Popup/Filter.js +++ b/dev/View/Popup/Filter.js @@ -7,10 +7,13 @@ _ = require('_'), ko = require('ko'), - Consts = require('Common/Consts'), + Enums = require('Common/Enums'), + Globals = require('Common/Globals'), Utils = require('Common/Utils'), + Translator = require('Common/Translator'), - Data = require('Storage/App/Data'), + FilterStore = require('Stores/User/Filter'), + FolderStore = require('Stores/User/Folder'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -24,11 +27,61 @@ { AbstractView.call(this, 'Popups', 'PopupsFilter'); + this.isNew = ko.observable(true); + + this.modules = FilterStore.modules; + + this.fTrueCallback = null; this.filter = ko.observable(null); - this.selectedFolderValue = ko.observable(Consts.Values.UnuseOptionValue); - this.folderSelectList = Data.folderMenuForMove; + this.allowMarkAsRead = ko.observable(false); + this.defautOptionsAfterRender = Utils.defautOptionsAfterRender; + this.folderSelectList = FolderStore.folderMenuForFilters; + this.selectedFolderValue = ko.observable(''); + + this.selectedFolderValue.subscribe(function() { + if (this.filter()) + { + this.filter().actionValue.error(false); + } + }, this); + + this.saveFilter = Utils.createCommand(this, function () { + + if (this.filter()) + { + if (Enums.FiltersAction.MoveTo === this.filter().actionType()) + { + this.filter().actionValue(this.selectedFolderValue()); + } + + if (!this.filter().verify()) + { + return false; + } + + if (this.fTrueCallback) + { + this.fTrueCallback(this.filter()); + } + + if (this.modalVisibility()) + { + Utils.delegateRun(this, 'closeCommand'); + } + } + + return true; + }); + + this.actionTypeOptions = ko.observableArray([]); + this.fieldOptions = ko.observableArray([]); + this.typeOptions = ko.observableArray([]); + + Translator.initOnStartOrLangChange(this.populateOptions, this); + + this.modules.subscribe(this.populateOptions, this); kn.constructorEnd(this); } @@ -36,16 +89,107 @@ kn.extendAsViewModel(['View/Popup/Filter', 'PopupsFilterViewModel'], FilterPopupView); _.extend(FilterPopupView.prototype, AbstractView.prototype); - FilterPopupView.prototype.clearPopup = function () + FilterPopupView.prototype.populateOptions = function () { - // TODO + this.actionTypeOptions([]); + +// this.actionTypeOptions.push({'id': Enums.FiltersAction.None, +// 'name': Translator.i18n('POPUPS_FILTER/SELECT_ACTION_NONE')}); + + var oModules = this.modules(); + if (oModules) + { + if (oModules.markasread) + { + this.allowMarkAsRead(true); + } + + if (oModules.moveto) + { + this.actionTypeOptions.push({'id': Enums.FiltersAction.MoveTo, + 'name': Translator.i18n('POPUPS_FILTER/SELECT_ACTION_MOVE_TO')}); + } + + if (oModules.redirect) + { + this.actionTypeOptions.push({'id': Enums.FiltersAction.Forward, + 'name': Translator.i18n('POPUPS_FILTER/SELECT_ACTION_FORWARD_TO')}); + } + + if (oModules.reject) + { + this.actionTypeOptions.push({'id': Enums.FiltersAction.Reject, + 'name': Translator.i18n('POPUPS_FILTER/SELECT_ACTION_REJECT')}); + } + + if (oModules.vacation) + { + this.actionTypeOptions.push({'id': Enums.FiltersAction.Vacation, + 'name': Translator.i18n('POPUPS_FILTER/SELECT_ACTION_VACATION_MESSAGE')}); + + } + } + + this.actionTypeOptions.push({'id': Enums.FiltersAction.Discard, + 'name': Translator.i18n('POPUPS_FILTER/SELECT_ACTION_DISCARD')}); + + this.fieldOptions([ + {'id': Enums.FilterConditionField.From, 'name': Translator.i18n('POPUPS_FILTER/SELECT_FIELD_FROM')}, + {'id': Enums.FilterConditionField.Recipient, 'name': Translator.i18n('POPUPS_FILTER/SELECT_FIELD_RECIPIENTS')}, + {'id': Enums.FilterConditionField.Subject, 'name': Translator.i18n('POPUPS_FILTER/SELECT_FIELD_SUBJECT')} + ]); + + this.typeOptions([ + {'id': Enums.FilterConditionType.Contains, 'name': Translator.i18n('POPUPS_FILTER/SELECT_TYPE_CONTAINS')}, + {'id': Enums.FilterConditionType.NotContains, 'name': Translator.i18n('POPUPS_FILTER/SELECT_TYPE_NOT_CONTAINS')}, + {'id': Enums.FilterConditionType.EqualTo, 'name': Translator.i18n('POPUPS_FILTER/SELECT_TYPE_EQUAL_TO')}, + {'id': Enums.FilterConditionType.NotEqualTo, 'name': Translator.i18n('POPUPS_FILTER/SELECT_TYPE_NOT_EQUAL_TO')} + ]); }; - FilterPopupView.prototype.onShow = function (oFilter) + + FilterPopupView.prototype.removeCondition = function (oConditionToDelete) + { + if (this.filter()) + { + this.filter().removeCondition(oConditionToDelete); + } + }; + + FilterPopupView.prototype.clearPopup = function () + { + this.isNew(true); + + this.fTrueCallback = null; + this.filter(null); + }; + + FilterPopupView.prototype.onShow = function (oFilter, fTrueCallback, bEdit) { this.clearPopup(); + this.fTrueCallback = fTrueCallback; this.filter(oFilter); + + if (oFilter) + { + this.selectedFolderValue(oFilter.actionValue()); + } + + this.isNew(!bEdit); + + if (!bEdit && oFilter) + { + oFilter.name.focused(true); + } + }; + + FilterPopupView.prototype.onShowWithDelay = function () + { + if (this.isNew() && this.filter() && !Globals.bMobile) + { + this.filter().name.focused(true); + } }; module.exports = FilterPopupView; diff --git a/dev/View/Popup/FolderClear.js b/dev/View/Popup/FolderClear.js index 57d716984..d3dea40de 100644 --- a/dev/View/Popup/FolderClear.js +++ b/dev/View/Popup/FolderClear.js @@ -9,10 +9,13 @@ Enums = require('Common/Enums'), Utils = require('Common/Utils'), + Translator = require('Common/Translator'), - Data = require('Storage/App/Data'), - Cache = require('Storage/App/Cache'), - Remote = require('Storage/App/Remote'), + Cache = require('Common/Cache'), + + MessageStore = require('Stores/User/Message'), + + Remote = require('Remote/User/Ajax'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -41,7 +44,7 @@ }, this); this.dangerDescHtml = ko.computed(function () { - return Utils.i18n('POPUPS_CLEAR_FOLDER/DANGER_DESC_HTML_1', { + return Translator.i18n('POPUPS_CLEAR_FOLDER/DANGER_DESC_HTML_1', { 'FOLDER': this.folderNameForClear() }); }, this); @@ -55,8 +58,8 @@ if (oFolderToClear) { - Data.message(null); - Data.messageList([]); + MessageStore.message(null); + MessageStore.messageList([]); this.clearingProcess(true); @@ -70,18 +73,18 @@ self.clearingProcess(false); if (Enums.StorageResultType.Success === sResult && oData && oData.Result) { - require('App/App').reloadMessageList(true); + require('App/User').reloadMessageList(true); self.cancelCommand(); } else { if (oData && oData.ErrorCode) { - self.clearingError(Utils.getNotification(oData.ErrorCode)); + self.clearingError(Translator.getNotification(oData.ErrorCode)); } else { - self.clearingError(Utils.getNotification(Enums.Notification.MailServerError)); + self.clearingError(Translator.getNotification(Enums.Notification.MailServerError)); } } }, oFolderToClear.fullNameRaw); diff --git a/dev/View/Popup/FolderCreate.js b/dev/View/Popup/FolderCreate.js index 255026366..27d2faba5 100644 --- a/dev/View/Popup/FolderCreate.js +++ b/dev/View/Popup/FolderCreate.js @@ -9,10 +9,13 @@ Enums = require('Common/Enums'), Consts = require('Common/Consts'), + Globals = require('Common/Globals'), Utils = require('Common/Utils'), + Translator = require('Common/Translator'), - Data = require('Storage/App/Data'), - Remote = require('Storage/App/Remote'), + FolderStore = require('Stores/User/Folder'), + + Promises = require('Promises/User/Ajax'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -26,8 +29,8 @@ { AbstractView.call(this, 'Popups', 'PopupsFolderCreate'); - Utils.initOnStartOrLangChange(function () { - this.sNoParentText = Utils.i18n('POPUPS_CREATE_FOLDER/SELECT_NO_PARENT'); + Translator.initOnStartOrLangChange(function () { + this.sNoParentText = Translator.i18n('POPUPS_CREATE_FOLDER/SELECT_NO_PARENT'); }, this); this.folderName = ko.observable(''); @@ -41,7 +44,7 @@ aTop = [], fDisableCallback = null, fVisibleCallback = null, - aList = Data.folderList(), + aList = FolderStore.folderList(), fRenameCallback = function (oItem) { return oItem ? (oItem.isSystemFolder() ? oItem.name() + ' ' + oItem.manageFolderSystemName() : oItem.name()) : ''; } @@ -49,11 +52,11 @@ aTop.push(['', this.sNoParentText]); - if ('' !== Data.namespace) + if ('' !== FolderStore.namespace) { fDisableCallback = function (oItem) { - return Data.namespace !== oItem.fullNameRaw.substr(0, Data.namespace.length); + return FolderStore.namespace !== oItem.fullNameRaw.substr(0, FolderStore.namespace.length); }; } @@ -68,26 +71,14 @@ sParentFolderName = this.selectedParentValue() ; - if ('' === sParentFolderName && 1 < Data.namespace.length) + if ('' === sParentFolderName && 1 < FolderStore.namespace.length) { - sParentFolderName = Data.namespace.substr(0, Data.namespace.length - 1); + sParentFolderName = FolderStore.namespace.substr(0, FolderStore.namespace.length - 1); } - Data.foldersCreating(true); - Remote.folderCreate(function (sResult, oData) { - - Data.foldersCreating(false); - if (Enums.StorageResultType.Success === sResult && oData && oData.Result) - { - require('App/App').folders(); - } - else - { - Data.foldersListError( - oData && oData.ErrorCode ? Utils.getNotification(oData.ErrorCode) : Utils.i18n('NOTIFICATIONS/CANT_CREATE_FOLDER')); - } - - }, this.folderName(), sParentFolderName); + require('App/User').foldersPromisesActionHelper( + Promises.folderCreate(this.folderName(), sParentFolderName, FolderStore.foldersCreating), + Enums.Notification.CantCreateFolder); this.cancelCommand(); @@ -122,9 +113,12 @@ this.clearPopup(); }; - FolderCreateView.prototype.onFocus = function () + FolderCreateView.prototype.onShowWithDelay = function () { - this.folderName.focused(true); + if (!Globals.bMobile) + { + this.folderName.focused(true); + } }; module.exports = FolderCreateView; diff --git a/dev/View/Popup/FolderSystem.js b/dev/View/Popup/FolderSystem.js index 9cd095ca7..1f4742de8 100644 --- a/dev/View/Popup/FolderSystem.js +++ b/dev/View/Popup/FolderSystem.js @@ -10,10 +10,12 @@ Enums = require('Common/Enums'), Consts = require('Common/Consts'), Utils = require('Common/Utils'), + Translator = require('Common/Translator'), + + FolderStore = require('Stores/User/Folder'), Settings = require('Storage/Settings'), - Data = require('Storage/App/Data'), - Remote = require('Storage/App/Remote'), + Remote = require('Remote/User/Ajax'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -27,46 +29,45 @@ { AbstractView.call(this, 'Popups', 'PopupsFolderSystem'); - Utils.initOnStartOrLangChange(function () { - this.sChooseOnText = Utils.i18n('POPUPS_SYSTEM_FOLDERS/SELECT_CHOOSE_ONE'); - this.sUnuseText = Utils.i18n('POPUPS_SYSTEM_FOLDERS/SELECT_UNUSE_NAME'); + Translator.initOnStartOrLangChange(function () { + this.sChooseOnText = Translator.i18n('POPUPS_SYSTEM_FOLDERS/SELECT_CHOOSE_ONE'); + this.sUnuseText = Translator.i18n('POPUPS_SYSTEM_FOLDERS/SELECT_UNUSE_NAME'); }, this); this.notification = ko.observable(''); this.folderSelectList = ko.computed(function () { - return Utils.folderListOptionsBuilder([], Data.folderList(), Data.folderListSystemNames(), [ + return Utils.folderListOptionsBuilder([], FolderStore.folderList(), FolderStore.folderListSystemNames(), [ ['', this.sChooseOnText], [Consts.Values.UnuseOptionValue, this.sUnuseText] ], null, null, null, null, null, true); }, this); var - self = this, fSaveSystemFolders = null, fCallback = null ; - this.sentFolder = Data.sentFolder; - this.draftFolder = Data.draftFolder; - this.spamFolder = Data.spamFolder; - this.trashFolder = Data.trashFolder; - this.archiveFolder = Data.archiveFolder; + this.sentFolder = FolderStore.sentFolder; + this.draftFolder = FolderStore.draftFolder; + this.spamFolder = FolderStore.spamFolder; + this.trashFolder = FolderStore.trashFolder; + this.archiveFolder = FolderStore.archiveFolder; fSaveSystemFolders = _.debounce(function () { - Settings.settingsSet('SentFolder', self.sentFolder()); - Settings.settingsSet('DraftFolder', self.draftFolder()); - Settings.settingsSet('SpamFolder', self.spamFolder()); - Settings.settingsSet('TrashFolder', self.trashFolder()); - Settings.settingsSet('ArchiveFolder', self.archiveFolder()); + Settings.settingsSet('SentFolder', FolderStore.sentFolder()); + Settings.settingsSet('DraftFolder', FolderStore.draftFolder()); + Settings.settingsSet('SpamFolder', FolderStore.spamFolder()); + Settings.settingsSet('TrashFolder', FolderStore.trashFolder()); + Settings.settingsSet('ArchiveFolder', FolderStore.archiveFolder()); Remote.saveSystemFolders(Utils.emptyFunction, { - 'SentFolder': self.sentFolder(), - 'DraftFolder': self.draftFolder(), - 'SpamFolder': self.spamFolder(), - 'TrashFolder': self.trashFolder(), - 'ArchiveFolder': self.archiveFolder(), + 'SentFolder': FolderStore.sentFolder(), + 'DraftFolder': FolderStore.draftFolder(), + 'SpamFolder': FolderStore.spamFolder(), + 'TrashFolder': FolderStore.trashFolder(), + 'ArchiveFolder': FolderStore.archiveFolder(), 'NullFolder': 'NullFolder' }); @@ -74,20 +75,20 @@ fCallback = function () { - Settings.settingsSet('SentFolder', self.sentFolder()); - Settings.settingsSet('DraftFolder', self.draftFolder()); - Settings.settingsSet('SpamFolder', self.spamFolder()); - Settings.settingsSet('TrashFolder', self.trashFolder()); - Settings.settingsSet('ArchiveFolder', self.archiveFolder()); + Settings.settingsSet('SentFolder', FolderStore.sentFolder()); + Settings.settingsSet('DraftFolder', FolderStore.draftFolder()); + Settings.settingsSet('SpamFolder', FolderStore.spamFolder()); + Settings.settingsSet('TrashFolder', FolderStore.trashFolder()); + Settings.settingsSet('ArchiveFolder', FolderStore.archiveFolder()); fSaveSystemFolders(); }; - this.sentFolder.subscribe(fCallback); - this.draftFolder.subscribe(fCallback); - this.spamFolder.subscribe(fCallback); - this.trashFolder.subscribe(fCallback); - this.archiveFolder.subscribe(fCallback); + FolderStore.sentFolder.subscribe(fCallback); + FolderStore.draftFolder.subscribe(fCallback); + FolderStore.spamFolder.subscribe(fCallback); + FolderStore.trashFolder.subscribe(fCallback); + FolderStore.archiveFolder.subscribe(fCallback); this.defautOptionsAfterRender = Utils.defautOptionsAfterRender; @@ -112,19 +113,19 @@ switch (iNotificationType) { case Enums.SetSystemFoldersNotification.Sent: - sNotification = Utils.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_SENT'); + sNotification = Translator.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_SENT'); break; case Enums.SetSystemFoldersNotification.Draft: - sNotification = Utils.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_DRAFTS'); + sNotification = Translator.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_DRAFTS'); break; case Enums.SetSystemFoldersNotification.Spam: - sNotification = Utils.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_SPAM'); + sNotification = Translator.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_SPAM'); break; case Enums.SetSystemFoldersNotification.Trash: - sNotification = Utils.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_TRASH'); + sNotification = Translator.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_TRASH'); break; case Enums.SetSystemFoldersNotification.Archive: - sNotification = Utils.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_ARCHIVE'); + sNotification = Translator.i18n('POPUPS_SYSTEM_FOLDERS/NOTIFICATION_ARCHIVE'); break; } diff --git a/dev/View/Popup/Identity.js b/dev/View/Popup/Identity.js index 406fbb079..92c931230 100644 --- a/dev/View/Popup/Identity.js +++ b/dev/View/Popup/Identity.js @@ -8,10 +8,11 @@ ko = require('ko'), Enums = require('Common/Enums'), + Globals = require('Common/Globals'), Utils = require('Common/Utils'), + Translator = require('Common/Translator'), - Remote = require('Storage/App/Remote'), - Data = require('Storage/App/Data'), + Remote = require('Remote/User/Ajax'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -25,6 +26,8 @@ { AbstractView.call(this, 'Popups', 'PopupsIdentity'); + var self = this; + this.id = ''; this.edit = ko.observable(false); this.owner = ko.observable(false); @@ -38,15 +41,36 @@ this.bcc = ko.observable('').validateSimpleEmail(); this.bcc.focused = ko.observable(false); - // this.email.subscribe(function () { - // this.email.hasError(false); - // }, this); + this.signature = ko.observable(''); + this.signatureInsertBefore = ko.observable(false); + + this.showBcc = ko.observable(false); + this.showReplyTo = ko.observable(false); this.submitRequest = ko.observable(false); this.submitError = ko.observable(''); + this.bcc.subscribe(function (aValue) { + if (false === self.showBcc() && 0 < aValue.length) + { + self.showBcc(true); + } + }, this); + + this.replyTo.subscribe(function (aValue) { + if (false === self.showReplyTo() && 0 < aValue.length) + { + self.showReplyTo(true); + } + }, this); + this.addOrEditIdentityCommand = Utils.createCommand(this, function () { + if (this.signature && this.signature.__fetchEditorValue) + { + this.signature.__fetchEditorValue(); + } + if (!this.email.hasError()) { this.email.hasError('' === Utils.trim(this.email())); @@ -83,20 +107,21 @@ { if (oData.Result) { - require('App/App').accountsAndIdentities(); + require('App/User').accountsAndIdentities(); this.cancelCommand(); } else if (oData.ErrorCode) { - this.submitError(Utils.getNotification(oData.ErrorCode)); + this.submitError(Translator.getNotification(oData.ErrorCode)); } } else { - this.submitError(Utils.getNotification(Enums.Notification.UnknownError)); + this.submitError(Translator.getNotification(Enums.Notification.UnknownError)); } - }, this), this.id, this.email(), this.name(), this.replyTo(), this.bcc()); + }, this), this.id, this.email(), this.name(), this.replyTo(), this.bcc(), + this.signature(), this.signatureInsertBefore()); return true; @@ -104,14 +129,6 @@ return !this.submitRequest(); }); - this.label = ko.computed(function () { - return Utils.i18n('POPUPS_IDENTITIES/' + (this.edit() ? 'TITLE_UPDATE_IDENTITY': 'TITLE_ADD_IDENTITY')); - }, this); - - this.button = ko.computed(function () { - return Utils.i18n('POPUPS_IDENTITIES/' + (this.edit() ? 'BUTTON_UPDATE_IDENTITY': 'BUTTON_ADD_IDENTITY')); - }, this); - kn.constructorEnd(this); } @@ -128,11 +145,16 @@ this.email(''); this.replyTo(''); this.bcc(''); + this.signature(''); + this.signatureInsertBefore(false); this.email.hasError(false); this.replyTo.hasError(false); this.bcc.hasError(false); + this.showBcc(false); + this.showReplyTo(false); + this.submitRequest(false); this.submitError(''); }; @@ -148,24 +170,35 @@ { this.edit(true); - this.id = oIdentity.id; + this.id = oIdentity.id(); this.name(oIdentity.name()); this.email(oIdentity.email()); this.replyTo(oIdentity.replyTo()); this.bcc(oIdentity.bcc()); + this.signature(oIdentity.signature()); + this.signatureInsertBefore(oIdentity.signatureInsertBefore()); - this.owner(this.id === Data.accountEmail()); + this.owner(this.id === ''); + } + else + { + this.id = Utils.fakeMd5(); } }; - IdentityPopupView.prototype.onFocus = function () + IdentityPopupView.prototype.onShowWithDelay = function () { - if (!this.owner()) + if (!this.owner() && !Globals.bMobile) { this.email.focused(true); } }; + IdentityPopupView.prototype.onHideWithDelay = function () + { + this.clearPopup(); + }; + module.exports = IdentityPopupView; }()); \ No newline at end of file diff --git a/dev/View/Popup/KeyboardShortcutsHelp.js b/dev/View/Popup/KeyboardShortcutsHelp.js index 511e3a400..5712c0d65 100644 --- a/dev/View/Popup/KeyboardShortcutsHelp.js +++ b/dev/View/Popup/KeyboardShortcutsHelp.js @@ -31,7 +31,7 @@ KeyboardShortcutsHelpPopupView.prototype.onBuild = function (oDom) { - key('tab, shift+tab, left, right', Enums.KeyState.PopupKeyboardShortcutsHelp, _.bind(function (event, handler) { + key('tab, shift+tab, left, right', Enums.KeyState.PopupKeyboardShortcutsHelp, _.throttle(_.bind(function (event, handler) { if (event && handler) { var @@ -56,7 +56,7 @@ $tabs.eq(iIndex).find('a[data-toggle="tab"]').tab('show'); return false; } - }, this)); + }, this), 100)); }; module.exports = KeyboardShortcutsHelpPopupView; diff --git a/dev/View/Popup/Languages.js b/dev/View/Popup/Languages.js index 9762c06aa..cf32623c9 100644 --- a/dev/View/Popup/Languages.js +++ b/dev/View/Popup/Languages.js @@ -8,7 +8,6 @@ ko = require('ko'), Utils = require('Common/Utils'), - Globals = require('Common/Globals'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -22,22 +21,26 @@ { AbstractView.call(this, 'Popups', 'PopupsLanguages'); - this.Data = Globals.__APP__.data(); // TODO + var self = this; - this.exp = ko.observable(false); + this.fLang = null; + this.sUserLanguage = ''; + + this.langs = ko.observableArray([]); this.languages = ko.computed(function () { - return _.map(this.Data.languages(), function (sLanguage) { + return _.map(self.langs(), function (sLanguage) { return { 'key': sLanguage, + 'user': sLanguage === self.sUserLanguage, 'selected': ko.observable(false), 'fullName': Utils.convertLangName(sLanguage) }; }); - }, this); + }); - this.Data.mainLanguage.subscribe(function () { - this.resetMainLanguage(); + this.langs.subscribe(function () { + this.setLanguageSelection(); }, this); kn.constructorEnd(this); @@ -46,34 +49,42 @@ kn.extendAsViewModel(['View/Popup/Languages', 'PopupsLanguagesViewModel'], LanguagesPopupView); _.extend(LanguagesPopupView.prototype, AbstractView.prototype); - LanguagesPopupView.prototype.languageEnName = function (sLanguage) + LanguagesPopupView.prototype.languageTooltipName = function (sLanguage) { - return Utils.convertLangName(sLanguage, true); + var sResult = Utils.convertLangName(sLanguage, true); + return Utils.convertLangName(sLanguage, false) === sResult ? '' : sResult; }; - LanguagesPopupView.prototype.resetMainLanguage = function () + LanguagesPopupView.prototype.setLanguageSelection = function () { - var sCurrent = this.Data.mainLanguage(); + var sCurrent = this.fLang ? ko.unwrap(this.fLang) : ''; _.each(this.languages(), function (oItem) { oItem['selected'](oItem['key'] === sCurrent); }); }; - LanguagesPopupView.prototype.onShow = function () + LanguagesPopupView.prototype.onShow = function (fLanguage, aLangs, sUserLanguage) { - this.exp(true); + this.fLang = fLanguage; + this.sUserLanguage = sUserLanguage || ''; - this.resetMainLanguage(); + this.langs(aLangs); }; LanguagesPopupView.prototype.onHide = function () { - this.exp(false); + this.fLang = null; + this.sUserLanguage = ''; + this.langs([]); }; LanguagesPopupView.prototype.changeLanguage = function (sLang) { - this.Data.mainLanguage(sLang); + if (this.fLang) + { + this.fLang(sLang); + } + this.cancelCommand(); }; diff --git a/dev/View/Popup/NewOpenPgpKey.js b/dev/View/Popup/NewOpenPgpKey.js index d8f2e4831..e929a438a 100644 --- a/dev/View/Popup/NewOpenPgpKey.js +++ b/dev/View/Popup/NewOpenPgpKey.js @@ -9,7 +9,7 @@ Utils = require('Common/Utils'), - Data = require('Storage/App/Data'), + PgpStore = require('Stores/User/Pgp'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -43,7 +43,7 @@ self = this, sUserID = '', mKeyPair = null, - oOpenpgpKeyring = Data.openpgpKeyring + oOpenpgpKeyring = PgpStore.openpgpKeyring ; this.email.error('' === Utils.trim(this.email())); @@ -61,12 +61,17 @@ this.submitRequest(true); _.delay(function () { - // mKeyPair = Data.openpgp.generateKeyPair(1, Utils.pInt(self.keyBitLength()), sUserID, Utils.trim(self.password())); - mKeyPair = Data.openpgp.generateKeyPair({ - 'userId': sUserID, - 'numBits': Utils.pInt(self.keyBitLength()), - 'passphrase': Utils.trim(self.password()) - }); + + mKeyPair = false; + try { + mKeyPair = PgpStore.openpgp.generateKeyPair({ + 'userId': sUserID, + 'numBits': Utils.pInt(self.keyBitLength()), + 'passphrase': Utils.trim(self.password()) + }); + } catch (e) { +// window.console.log(e); + } if (mKeyPair && mKeyPair.privateKeyArmored) { @@ -74,11 +79,12 @@ oOpenpgpKeyring.publicKeys.importKey(mKeyPair.publicKeyArmored); oOpenpgpKeyring.store(); - require('App/App').reloadOpenPgpKeys(); + require('App/User').reloadOpenPgpKeys(); Utils.delegateRun(self, 'cancelCommand'); } self.submitRequest(false); + }, 100); return true; @@ -105,7 +111,7 @@ this.clearPopup(); }; - NewOpenPgpKeyPopupView.prototype.onFocus = function () + NewOpenPgpKeyPopupView.prototype.onShowWithDelay = function () { this.email.focus(true); }; diff --git a/dev/View/Popup/Plugin.js b/dev/View/Popup/Plugin.js index 4b4ba4529..9ca435493 100644 --- a/dev/View/Popup/Plugin.js +++ b/dev/View/Popup/Plugin.js @@ -10,8 +10,9 @@ Enums = require('Common/Enums'), Utils = require('Common/Utils'), + Translator = require('Common/Translator'), - Remote = require('Storage/Admin/Remote'), + Remote = require('Remote/Admin/Ajax'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -47,7 +48,8 @@ this.readmePopoverConf = { 'placement': 'top', 'trigger': 'hover', - 'title': 'About', + 'title': Translator.i18n('POPUPS_PLUGIN/TOOLTIP_ABOUT_TITLE'), + 'container': 'body', 'content': function () { return self.readme(); } @@ -98,11 +100,11 @@ this.saveError(''); if (oData && oData.ErrorCode) { - this.saveError(Utils.getNotification(oData.ErrorCode)); + this.saveError(Translator.getNotification(oData.ErrorCode)); } else { - this.saveError(Utils.getNotification(Enums.Notification.CantSavePluginSettings)); + this.saveError(Translator.getNotification(Enums.Notification.CantSavePluginSettings)); } } }; @@ -145,7 +147,7 @@ if (!kn.isPopupVisible(PopupsAskViewModel)) { - kn.showScreenPopup(PopupsAskViewModel, [Utils.i18n('POPUPS_ASK/DESC_WANT_CLOSE_THIS_WINDOW'), function () { + kn.showScreenPopup(PopupsAskViewModel, [Translator.i18n('POPUPS_ASK/DESC_WANT_CLOSE_THIS_WINDOW'), function () { if (self.modalVisibility()) { Utils.delegateRun(self, 'cancelCommand'); diff --git a/dev/View/Popup/Template.js b/dev/View/Popup/Template.js new file mode 100644 index 000000000..4c4551e5a --- /dev/null +++ b/dev/View/Popup/Template.js @@ -0,0 +1,205 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + ko = require('ko'), + + Enums = require('Common/Enums'), + Utils = require('Common/Utils'), + Translator = require('Common/Translator'), + HtmlEditor = require('Common/HtmlEditor'), + + Remote = require('Remote/User/Ajax'), + + kn = require('Knoin/Knoin'), + AbstractView = require('Knoin/AbstractView') + ; + + /** + * @constructor + * @extends AbstractView + */ + function TemplatePopupView() + { + AbstractView.call(this, 'Popups', 'PopupsTemplate'); + + this.editor = null; + this.signatureDom = ko.observable(null); + + this.id = ko.observable(''); + + this.name = ko.observable(''); + this.name.error = ko.observable(false); + this.name.focus = ko.observable(false); + + this.body = ko.observable(''); + this.body.loading = ko.observable(false); + this.body.error = ko.observable(false); + + this.name.subscribe(function () { + this.name.error(false); + }, this); + + this.body.subscribe(function () { + this.body.error(false); + }, this); + + this.submitRequest = ko.observable(false); + this.submitError = ko.observable(''); + + this.addTemplateCommand = Utils.createCommand(this, function () { + + this.populateBodyFromEditor(); + + this.name.error('' === Utils.trim(this.name())); + this.body.error('' === Utils.trim(this.body()) || + ':HTML:' === Utils.trim(this.body())); + + if (this.name.error() || this.body.error()) + { + return false; + } + + this.submitRequest(true); + + Remote.templateSetup(_.bind(function (sResult, oData) { + + this.submitRequest(false); + if (Enums.StorageResultType.Success === sResult && oData) + { + if (oData.Result) + { + require('App/User').templates(); + this.cancelCommand(); + } + else if (oData.ErrorCode) + { + this.submitError(Translator.getNotification(oData.ErrorCode)); + } + } + else + { + this.submitError(Translator.getNotification(Enums.Notification.UnknownError)); + } + + }, this), this.id(), this.name(), this.body()); + + return true; + + }, function () { + return !this.submitRequest(); + }); + + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/Popup/Template'], TemplatePopupView); + _.extend(TemplatePopupView.prototype, AbstractView.prototype); + + TemplatePopupView.prototype.clearPopup = function () + { + this.id(''); + + this.name(''); + this.name.error(false); + + this.body(''); + this.body.loading(false); + this.body.error(false); + + this.submitRequest(false); + this.submitError(''); + + if (this.editor) + { + this.editor.setPlain('', false); + } + }; + + TemplatePopupView.prototype.populateBodyFromEditor = function () + { + if (this.editor) + { + this.body(this.editor.getDataWithHtmlMark()); + } + }; + + TemplatePopupView.prototype.editorSetBody = function (sBody) + { + if (!this.editor && this.signatureDom()) + { + var self = this; + this.editor = new HtmlEditor(self.signatureDom(), function () { + self.populateBodyFromEditor(); + }, function () { + self.editor.setHtmlOrPlain(sBody); + }); + } + else + { + this.editor.setHtmlOrPlain(sBody); + } + }; + + TemplatePopupView.prototype.onShow = function (oTemplate) + { + var self = this; + + this.clearPopup(); + + if (oTemplate && oTemplate.id) + { + this.id(oTemplate.id); + this.name(oTemplate.name); + this.body(oTemplate.body); + + if (oTemplate.populated) + { + self.editorSetBody(this.body()); + } + else + { + this.body.loading(true); + self.body.error(false); + + Remote.templateGetById(function (sResult, oData) { + + self.body.loading(false); + + if (Enums.StorageResultType.Success === sResult && oData && oData.Result && + 'Object/Template' === oData.Result['@Object'] && Utils.isNormal(oData.Result['Body'])) + { + oTemplate.body = oData.Result['Body']; + oTemplate.populated = true; + + self.body(oTemplate.body); + self.body.error(false); + } + else + { + self.body(''); + self.body.error(true); + } + + self.editorSetBody(self.body()); + + }, this.id()); + } + } + else + { + self.editorSetBody(''); + } + }; + + TemplatePopupView.prototype.onShowWithDelay = function () + { + this.name.focus(true); + }; + + module.exports = TemplatePopupView; + +}()); \ No newline at end of file diff --git a/dev/View/Popup/TwoFactorConfiguration.js b/dev/View/Popup/TwoFactorConfiguration.js new file mode 100644 index 000000000..4258740c9 --- /dev/null +++ b/dev/View/Popup/TwoFactorConfiguration.js @@ -0,0 +1,232 @@ + +(function () { + + 'use strict'; + + var + window = require('window'), + _ = require('_'), + ko = require('ko'), + + Enums = require('Common/Enums'), + Utils = require('Common/Utils'), + Translator = require('Common/Translator'), + + Settings = require('Storage/Settings'), + + Remote = require('Remote/User/Ajax'), + + kn = require('Knoin/Knoin'), + AbstractView = require('Knoin/AbstractView') + ; + + /** + * @constructor + * @extends AbstractView + */ + function TwoFactorConfigurationPopupView() + { + AbstractView.call(this, 'Popups', 'PopupsTwoFactorConfiguration'); + + this.lock = ko.observable(false); + + this.capaTwoFactor = Settings.capa(Enums.Capa.TwoFactor); + + this.processing = ko.observable(false); + this.clearing = ko.observable(false); + this.secreting = ko.observable(false); + + this.viewUser = ko.observable(''); + this.twoFactorStatus = ko.observable(false); + + this.twoFactorTested = ko.observable(false); + + this.viewSecret = ko.observable(''); + this.viewBackupCodes = ko.observable(''); + this.viewUrl = ko.observable(''); + + this.viewEnable_ = ko.observable(false); + + this.viewEnable = ko.computed({ + 'owner': this, + 'read': this.viewEnable_, + 'write': function (bValue) { + + var self = this; + + bValue = !!bValue; + + if (bValue && this.twoFactorTested()) + { + this.viewEnable_(bValue); + + Remote.enableTwoFactor(function (sResult, oData) { + if (Enums.StorageResultType.Success !== sResult || !oData || !oData.Result) + { + self.viewEnable_(false); + } + + }, true); + } + else + { + if (!bValue) + { + this.viewEnable_(bValue); + } + + Remote.enableTwoFactor(function (sResult, oData) { + if (Enums.StorageResultType.Success !== sResult || !oData || !oData.Result) + { + self.viewEnable_(false); + } + + }, false); + } + } + }); + + this.viewTwoFactorEnableTooltip = ko.computed(function () { + Translator.trigger(); + return this.twoFactorTested() || this.viewEnable_() ? '' : + Translator.i18n('POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_TEST_BEFORE_DESC'); + }, this); + + this.viewTwoFactorStatus = ko.computed(function () { + Translator.trigger(); + return Translator.i18n( + this.twoFactorStatus() ? + 'POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_CONFIGURED_DESC' : + 'POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC' + ); + }, this); + + this.twoFactorAllowedEnable = ko.computed(function () { + return this.viewEnable() || this.twoFactorTested(); + }, this); + + this.onResult = _.bind(this.onResult, this); + this.onShowSecretResult = _.bind(this.onShowSecretResult, this); + + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/Popup/TwoFactorConfiguration', 'TwoFactorConfigurationPopupView'], TwoFactorConfigurationPopupView); + _.extend(TwoFactorConfigurationPopupView.prototype, AbstractView.prototype); + + + TwoFactorConfigurationPopupView.prototype.showSecret = function () + { + this.secreting(true); + Remote.showTwoFactorSecret(this.onShowSecretResult); + }; + + TwoFactorConfigurationPopupView.prototype.hideSecret = function () + { + this.viewSecret(''); + this.viewBackupCodes(''); + this.viewUrl(''); + }; + + TwoFactorConfigurationPopupView.prototype.createTwoFactor = function () + { + this.processing(true); + Remote.createTwoFactor(this.onResult); + }; + + TwoFactorConfigurationPopupView.prototype.logout = function () + { + require('App/User').logout(); + }; + + TwoFactorConfigurationPopupView.prototype.testTwoFactor = function () + { + require('Knoin/Knoin').showScreenPopup(require('View/Popup/TwoFactorTest'), [this.twoFactorTested]); + }; + + TwoFactorConfigurationPopupView.prototype.clearTwoFactor = function () + { + this.viewSecret(''); + this.viewBackupCodes(''); + this.viewUrl(''); + + this.twoFactorTested(false); + + this.clearing(true); + Remote.clearTwoFactor(this.onResult); + }; + + TwoFactorConfigurationPopupView.prototype.onShow = function (bLock) + { + this.lock(!!bLock); + + this.viewSecret(''); + this.viewBackupCodes(''); + this.viewUrl(''); + }; + + TwoFactorConfigurationPopupView.prototype.onHide = function () + { + if (this.lock()) + { + window.location.reload(); + } + }; + + TwoFactorConfigurationPopupView.prototype.onResult = function (sResult, oData) + { + this.processing(false); + this.clearing(false); + + if (Enums.StorageResultType.Success === sResult && oData && oData.Result) + { + this.viewUser(Utils.pString(oData.Result.User)); + this.viewEnable_(!!oData.Result.Enable); + this.twoFactorStatus(!!oData.Result.IsSet); + this.twoFactorTested(!!oData.Result.Tested); + + this.viewSecret(Utils.pString(oData.Result.Secret)); + this.viewBackupCodes(Utils.pString(oData.Result.BackupCodes).replace(/[\s]+/g, ' ')); + this.viewUrl(Utils.pString(oData.Result.Url)); + } + else + { + this.viewUser(''); + this.viewEnable_(false); + this.twoFactorStatus(false); + this.twoFactorTested(false); + + this.viewSecret(''); + this.viewBackupCodes(''); + this.viewUrl(''); + } + }; + + TwoFactorConfigurationPopupView.prototype.onShowSecretResult = function (sResult, oData) + { + this.secreting(false); + + if (Enums.StorageResultType.Success === sResult && oData && oData.Result) + { + this.viewSecret(Utils.pString(oData.Result.Secret)); + this.viewUrl(Utils.pString(oData.Result.Url)); + } + else + { + this.viewSecret(''); + this.viewUrl(''); + } + }; + + TwoFactorConfigurationPopupView.prototype.onBuild = function () + { + if (this.capaTwoFactor) + { + this.processing(true); + Remote.getTwoFactor(this.onResult); + } + }; + + module.exports = TwoFactorConfigurationPopupView; + +}()); \ No newline at end of file diff --git a/dev/View/Popup/TwoFactorTest.js b/dev/View/Popup/TwoFactorTest.js index 235f008f4..465f2fe9a 100644 --- a/dev/View/Popup/TwoFactorTest.js +++ b/dev/View/Popup/TwoFactorTest.js @@ -8,9 +8,10 @@ ko = require('ko'), Enums = require('Common/Enums'), + Globals = require('Common/Globals'), Utils = require('Common/Utils'), - Remote = require('Storage/App/Remote'), + Remote = require('Remote/User/Ajax'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -30,6 +31,8 @@ this.code.focused = ko.observable(false); this.code.status = ko.observable(null); + this.koTestedTrigger = null; + this.testing = ko.observable(false); // commands @@ -41,6 +44,11 @@ self.testing(false); self.code.status(Enums.StorageResultType.Success === sResult && oData && oData.Result ? true : false); + if (self.koTestedTrigger && self.code.status()) + { + self.koTestedTrigger(true); + } + }, this.code()); }, function () { @@ -59,16 +67,23 @@ this.code.focused(false); this.code.status(null); this.testing(false); + + this.koTestedTrigger = null; }; - TwoFactorTestPopupView.prototype.onShow = function () + TwoFactorTestPopupView.prototype.onShow = function (koTestedTrigger) { this.clearPopup(); + + this.koTestedTrigger = koTestedTrigger; }; - TwoFactorTestPopupView.prototype.onFocus = function () + TwoFactorTestPopupView.prototype.onShowWithDelay = function () { - this.code.focused(true); + if (!Globals.bMobile) + { + this.code.focused(true); + } }; module.exports = TwoFactorTestPopupView; diff --git a/dev/View/Popup/WelcomePage.js b/dev/View/Popup/WelcomePage.js new file mode 100644 index 000000000..3dd18a686 --- /dev/null +++ b/dev/View/Popup/WelcomePage.js @@ -0,0 +1,62 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + ko = require('ko'), + + Promises = require('Promises/User/Ajax'), + + kn = require('Knoin/Knoin'), + AbstractView = require('Knoin/AbstractView') + ; + + /** + * @constructor + * @extends AbstractView + */ + function WelcomePagePopupView() + { + AbstractView.call(this, 'Popups', 'PopupsWelcomePage'); + + this.welcomePageURL = ko.observable(''); + + this.closeFocused = ko.observable(false); + + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/Popup/WelcomePage', 'WelcomePagePopupViewModel'], WelcomePagePopupView); + _.extend(WelcomePagePopupView.prototype, AbstractView.prototype); + + WelcomePagePopupView.prototype.clearPopup = function () + { + this.welcomePageURL(''); + this.closeFocused(false); + }; + + /** + * @param {string} sUrl + */ + WelcomePagePopupView.prototype.onShow = function (sUrl) + { + this.clearPopup(); + + this.welcomePageURL(sUrl); + }; + + WelcomePagePopupView.prototype.onShowWithDelay = function () + { + this.closeFocused(true); + }; + + WelcomePagePopupView.prototype.onHide = function () + { + Promises.welcomeClose(); + }; + + module.exports = WelcomePagePopupView; + +}()); \ No newline at end of file diff --git a/dev/View/App/About.js b/dev/View/User/About.js similarity index 65% rename from dev/View/App/About.js rename to dev/View/User/About.js index 09754b2af..bec47516f 100644 --- a/dev/View/App/About.js +++ b/dev/View/User/About.js @@ -17,7 +17,7 @@ * @constructor * @extends AbstractView */ - function AboutAppView() + function AboutUserView() { AbstractView.call(this, 'Center', 'About'); @@ -26,9 +26,9 @@ kn.constructorEnd(this); } - kn.extendAsViewModel(['View/App/About', 'AboutViewModel'], AboutAppView); - _.extend(AboutAppView.prototype, AbstractView.prototype); + kn.extendAsViewModel(['View/User/About', 'View/App/About', 'AboutViewModel'], AboutUserView); + _.extend(AboutUserView.prototype, AbstractView.prototype); - module.exports = AboutAppView; + module.exports = AboutUserView; }()); \ No newline at end of file diff --git a/dev/View/User/AbstractSystemDropDown.js b/dev/View/User/AbstractSystemDropDown.js new file mode 100644 index 000000000..76c1d5e9e --- /dev/null +++ b/dev/View/User/AbstractSystemDropDown.js @@ -0,0 +1,129 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + ko = require('ko'), + key = require('key'), + + Enums = require('Common/Enums'), + Utils = require('Common/Utils'), + Links = require('Common/Links'), + Events = require('Common/Events'), + + AppStore = require('Stores/User/App'), + AccountStore = require('Stores/User/Account'), + MessageStore = require('Stores/User/Message'), + + Settings = require('Storage/Settings'), + + AbstractView = require('Knoin/AbstractView') + ; + + /** + * @constructor + * @extends AbstractView + */ + function AbstractSystemDropDownUserView() + { + AbstractView.call(this, 'Right', 'SystemDropDown'); + + this.currentAudio = AppStore.currentAudio; + + this.accountEmail = AccountStore.email; + + this.accounts = AccountStore.accounts; + this.accountsUnreadCount = AccountStore.accountsUnreadCount; + + this.accountMenuDropdownTrigger = ko.observable(false); + this.capaAdditionalAccounts = ko.observable(Settings.capa(Enums.Capa.AdditionalAccounts)); + + this.accountClick = _.bind(this.accountClick, this); + + this.accountClick = _.bind(this.accountClick, this); + + Events.sub('audio.stop', function () { + AppStore.currentAudio(''); + }); + + Events.sub('audio.start', function (sName) { + AppStore.currentAudio(sName); + }); + } + + _.extend(AbstractSystemDropDownUserView.prototype, AbstractView.prototype); + + AbstractSystemDropDownUserView.prototype.stopPlay = function () + { + Events.pub('audio.api.stop'); + }; + + AbstractSystemDropDownUserView.prototype.accountClick = function (oAccount, oEvent) + { + if (oAccount && oEvent && !Utils.isUnd(oEvent.which) && 1 === oEvent.which) + { + AccountStore.accounts.loading(true); + + _.delay(function () { + AccountStore.accounts.loading(false); + }, 1000); + } + + return true; + }; + + AbstractSystemDropDownUserView.prototype.emailTitle = function () + { + return AccountStore.email(); + }; + + AbstractSystemDropDownUserView.prototype.settingsClick = function () + { + require('Knoin/Knoin').setHash(Links.settings()); + }; + + AbstractSystemDropDownUserView.prototype.settingsHelp = function () + { + require('Knoin/Knoin').showScreenPopup(require('View/Popup/KeyboardShortcutsHelp')); + }; + + AbstractSystemDropDownUserView.prototype.addAccountClick = function () + { + if (this.capaAdditionalAccounts()) + { + require('Knoin/Knoin').showScreenPopup(require('View/Popup/Account')); + } + }; + + AbstractSystemDropDownUserView.prototype.logoutClick = function () + { + require('App/User').logout(); + }; + + AbstractSystemDropDownUserView.prototype.onBuild = function () + { + var self = this; + key('`', [Enums.KeyState.MessageList, Enums.KeyState.MessageView, Enums.KeyState.Settings], function () { + if (self.viewModelVisibility()) + { + MessageStore.messageFullScreenMode(false); + + self.accountMenuDropdownTrigger(true); + } + }); + + // shortcuts help + key('shift+/', [Enums.KeyState.MessageList, Enums.KeyState.MessageView, Enums.KeyState.Settings], function () { + if (self.viewModelVisibility()) + { + require('Knoin/Knoin').showScreenPopup(require('View/Popup/KeyboardShortcutsHelp')); + return false; + } + }); + }; + + module.exports = AbstractSystemDropDownUserView; + +}()); \ No newline at end of file diff --git a/dev/View/App/Login.js b/dev/View/User/Login.js similarity index 50% rename from dev/View/App/Login.js rename to dev/View/User/Login.js index 44340b941..f2ea8c45b 100644 --- a/dev/View/App/Login.js +++ b/dev/View/User/Login.js @@ -6,16 +6,23 @@ var window = require('window'), _ = require('_'), - $ = require('$'), ko = require('ko'), Enums = require('Common/Enums'), Utils = require('Common/Utils'), - LinkBuilder = require('Common/LinkBuilder'), + Links = require('Common/Links'), + Translator = require('Common/Translator'), + + Plugins = require('Common/Plugins'), + + LanguageStore = require('Stores/Language'), + AppStore = require('Stores/User/App'), + + Local = require('Storage/Client'), Settings = require('Storage/Settings'), - Data = require('Storage/App/Data'), - Remote = require('Storage/App/Remote'), + + Remote = require('Remote/User/Ajax'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -25,7 +32,7 @@ * @constructor * @extends AbstractView */ - function LoginAppView() + function LoginUserView() { AbstractView.call(this, 'Center', 'Login'); @@ -35,18 +42,33 @@ this.additionalCode = ko.observable(''); this.additionalCode.error = ko.observable(false); + this.additionalCode.errorAnimation = ko.observable(false).extend({'falseTimeout': 500}); this.additionalCode.focused = ko.observable(false); this.additionalCode.visibility = ko.observable(false); this.additionalCodeSignMe = ko.observable(false); this.logoImg = Utils.trim(Settings.settingsGet('LoginLogo')); + this.logoPowered = !!Settings.settingsGet('LoginPowered'); this.loginDescription = Utils.trim(Settings.settingsGet('LoginDescription')); - this.logoCss = Utils.trim(Settings.settingsGet('LoginCss')); + + this.forgotPasswordLinkUrl = Settings.settingsGet('ForgotPasswordLinkUrl'); + this.registrationLinkUrl = Settings.settingsGet('RegistrationLinkUrl'); this.emailError = ko.observable(false); this.passwordError = ko.observable(false); + this.emailErrorAnimation = ko.observable(false).extend({'falseTimeout': 500}); + this.passwordErrorAnimation = ko.observable(false).extend({'falseTimeout': 500}); + + this.formHidden = ko.observable(false); + + this.formError = ko.computed(function () { + return this.emailErrorAnimation() || this.passwordErrorAnimation() || + (this.additionalCode.visibility() && this.additionalCode.errorAnimation()); + }, this); + this.emailFocus = ko.observable(false); + this.passwordFocus = ko.observable(false); this.submitFocus = ko.observable(false); this.email.subscribe(function () { @@ -67,17 +89,39 @@ this.additionalCode.error(false); }, this); + this.emailError.subscribe(function (bV) { + this.emailErrorAnimation(!!bV); + }, this); + + this.passwordError.subscribe(function (bV) { + this.passwordErrorAnimation(!!bV); + }, this); + + this.additionalCode.error.subscribe(function (bV) { + this.additionalCode.errorAnimation(!!bV); + }, this); + this.submitRequest = ko.observable(false); this.submitError = ko.observable(''); + this.submitErrorAddidional = ko.observable(''); - this.allowLanguagesOnLogin = Data.allowLanguagesOnLogin; + this.submitError.subscribe(function (sValue) { + if ('' === sValue) + { + this.submitErrorAddidional(''); + } + }, this); + + this.allowLanguagesOnLogin = AppStore.allowLanguagesOnLogin; this.langRequest = ko.observable(false); - this.mainLanguage = Data.mainLanguage; + this.language = LanguageStore.language; + this.languages = LanguageStore.languages; + this.bSendLanguage = false; - this.mainLanguageFullName = ko.computed(function () { - return Utils.convertLangName(this.mainLanguage()); + this.languageFullName = ko.computed(function () { + return Utils.convertLangName(this.language()); }, this); this.signMeType = ko.observable(Enums.LoginSignMeType.Unused); @@ -94,16 +138,55 @@ Utils.triggerAutocompleteInputChange(); + this.emailError(false); + this.passwordError(false); + this.emailError('' === Utils.trim(this.email())); this.passwordError('' === Utils.trim(this.password())); if (this.additionalCode.visibility()) { + this.additionalCode.error(false); this.additionalCode.error('' === Utils.trim(this.additionalCode())); } - if (this.emailError() || this.passwordError() || this.additionalCode.error()) + if (this.emailError() || this.passwordError() || + (this.additionalCode.visibility() && this.additionalCode.error())) { + switch (true) + { + case this.emailError(): + this.emailFocus(true); + break; + case this.passwordError(): + this.passwordFocus(true); + break; + case this.additionalCode.visibility() && this.additionalCode.error(): + this.additionalCode.focused(true); + break; + } + + return false; + } + + var + iPluginResultCode = 0, + sPluginResultMessage = '', + fSubmitResult = function (iResultCode, sResultMessage) { + iPluginResultCode = iResultCode || 0; + sPluginResultMessage = sResultMessage || ''; + } + ; + + Plugins.runHook('user-login-submit', [fSubmitResult]); + if (0 < iPluginResultCode) + { + this.submitError(Translator.getNotification(iPluginResultCode)); + return false; + } + else if ('' !== sPluginResultMessage) + { + this.submitError(sPluginResultMessage); return false; } @@ -128,19 +211,37 @@ this.submitRequest(false); } + else if (oData.Admin) + { + this.formHidden(true); + require('App/User').redirectToAdminPanel(); + } else { - require('App/App').loginAndLogoutReload(); + this.formHidden(true); + require('App/User').loginAndLogoutReload(false); } } else if (oData.ErrorCode) { this.submitRequest(false); - this.submitError(Utils.getNotification(oData.ErrorCode)); + if (-1 < Utils.inArray(oData.ErrorCode, [Enums.Notification.InvalidInputArgument])) + { + oData.ErrorCode = Enums.Notification.AuthError; + } + + this.submitError(Translator.getNotification(oData.ErrorCode)); if ('' === this.submitError()) { - this.submitError(Utils.getNotification(Enums.Notification.UnknownError)); + this.submitError(Translator.getNotification(Enums.Notification.UnknownError)); + } + else + { + if (oData.ErrorMessageAdditional) + { + this.submitErrorAddidional(oData.ErrorMessageAdditional); + } } } else @@ -151,15 +252,17 @@ else { this.submitRequest(false); - this.submitError(Utils.getNotification(Enums.Notification.UnknownError)); + this.submitError(Translator.getNotification(Enums.Notification.UnknownError)); } }, this), this.email(), '', sPassword, !!this.signMe(), - this.bSendLanguage ? this.mainLanguage() : '', + this.bSendLanguage ? this.language() : '', this.additionalCode.visibility() ? this.additionalCode() : '', this.additionalCode.visibility() ? !!this.additionalCodeSignMe() : false ); + Local.set(Enums.ClientSideKeyName.LastSignMe, !!this.signMe() ? '-1-' : '-0-'); + }, this) ; @@ -183,8 +286,9 @@ this.facebookCommand = Utils.createCommand(this, function () { - window.open(LinkBuilder.socialFacebook(), 'Facebook', - 'left=200,top=100,width=650,height=335,menubar=no,status=no,resizable=yes,scrollbars=yes'); + window.open(Links.socialFacebook(), 'Facebook', + 'left=200,top=100,width=650,height=450,menubar=no,status=no,resizable=yes,scrollbars=yes'); + return true; }, function () { @@ -195,8 +299,9 @@ this.googleCommand = Utils.createCommand(this, function () { - window.open(LinkBuilder.socialGoogle(), 'Google', - 'left=200,top=100,width=650,height=335,menubar=no,status=no,resizable=yes,scrollbars=yes'); + window.open(Links.socialGoogle(), 'Google', + 'left=200,top=100,width=650,height=450,menubar=no,status=no,resizable=yes,scrollbars=yes'); + return true; }, function () { @@ -207,8 +312,8 @@ this.twitterCommand = Utils.createCommand(this, function () { - window.open(LinkBuilder.socialTwitter(), 'Twitter', - 'left=200,top=100,width=650,height=335,menubar=no,status=no,resizable=yes,scrollbars=yes'); + window.open(Links.socialTwitter(), 'Twitter', + 'left=200,top=100,width=650,height=450,menubar=no,status=no,resizable=yes,scrollbars=yes'); return true; @@ -230,67 +335,87 @@ kn.constructorEnd(this); } - kn.extendAsViewModel(['View/App/Login', 'LoginViewModel'], LoginAppView); - _.extend(LoginAppView.prototype, AbstractView.prototype); + kn.extendAsViewModel(['View/User/Login', 'View/App/Login', 'LoginViewModel'], LoginUserView); + _.extend(LoginUserView.prototype, AbstractView.prototype); - LoginAppView.prototype.onShow = function () + LoginUserView.prototype.onShow = function () { kn.routeOff(); - - _.delay(_.bind(function () { - if ('' !== this.email() && '' !== this.password()) - { - this.submitFocus(true); - } - else - { - this.emailFocus(true); - } - - if (Settings.settingsGet('UserLanguage')) - { - $.cookie('rllang', Data.language(), {'expires': 30}); - } - - }, this), 100); }; - LoginAppView.prototype.onHide = function () + LoginUserView.prototype.onShowWithDelay = function () + { + if ('' !== this.email() && '' !== this.password()) + { + this.submitFocus(true); + } + else if ('' === this.email()) + { + this.emailFocus(true); + } + else if ('' === this.password()) + { + this.passwordFocus(true); + } + else + { + this.emailFocus(true); + } + }; + + LoginUserView.prototype.onHide = function () { this.submitFocus(false); this.emailFocus(false); + this.passwordFocus(false); }; - LoginAppView.prototype.onBuild = function () + LoginUserView.prototype.onBuild = function () { var self = this, + sSignMeLocal = Local.get(Enums.ClientSideKeyName.LastSignMe), + sSignMe = (Settings.settingsGet('SignMe') || 'unused').toLowerCase(), sJsHash = Settings.settingsGet('JsHash'), fSocial = function (iErrorCode) { iErrorCode = Utils.pInt(iErrorCode); if (0 === iErrorCode) { self.submitRequest(true); - require('App/App').loginAndLogoutReload(); + self.formHidden(true); + + require('App/User').loginAndLogoutReload(false); } else { - self.submitError(Utils.getNotification(iErrorCode)); + self.submitError(Translator.getNotification(iErrorCode)); } } ; this.facebookLoginEnabled(!!Settings.settingsGet('AllowFacebookSocial')); this.twitterLoginEnabled(!!Settings.settingsGet('AllowTwitterSocial')); - this.googleLoginEnabled(!!Settings.settingsGet('AllowGoogleSocial')); + this.googleLoginEnabled(!!Settings.settingsGet('AllowGoogleSocial') && + !!Settings.settingsGet('AllowGoogleSocialAuth')); - switch ((Settings.settingsGet('SignMe') || 'unused').toLowerCase()) + switch (sSignMe) { case Enums.LoginSignMeTypeAsString.DefaultOff: - this.signMeType(Enums.LoginSignMeType.DefaultOff); - break; case Enums.LoginSignMeTypeAsString.DefaultOn: - this.signMeType(Enums.LoginSignMeType.DefaultOn); + + this.signMeType(Enums.LoginSignMeTypeAsString.DefaultOn === sSignMe ? + Enums.LoginSignMeType.DefaultOn : Enums.LoginSignMeType.DefaultOff); + + switch (sSignMeLocal) + { + case '-1-': + this.signMeType(Enums.LoginSignMeType.DefaultOn); + break; + case '-0-': + this.signMeType(Enums.LoginSignMeType.DefaultOff); + break; + } + break; default: case Enums.LoginSignMeTypeAsString.Unused: @@ -298,8 +423,8 @@ break; } - this.email(Data.devEmail); - this.password(Data.devPassword); + this.email(AppStore.devEmail); + this.password(AppStore.devPassword); if (this.googleLoginEnabled()) { @@ -317,14 +442,14 @@ } _.delay(function () { - Data.language.subscribe(function (sValue) { + LanguageStore.language.subscribe(function (sValue) { self.langRequest(true); - Utils.reloadLanguage(sValue, function() { + Translator.reload(false, sValue, function() { + self.langRequest(false); self.bSendLanguage = true; - $.cookie('rllang', sValue, {'expires': 30}); - }, null, function() { + }, function() { self.langRequest(false); }); @@ -334,16 +459,18 @@ Utils.triggerAutocompleteInputChange(true); }; - LoginAppView.prototype.submitForm = function () + LoginUserView.prototype.submitForm = function () { this.submitCommand(); }; - LoginAppView.prototype.selectLanguage = function () + LoginUserView.prototype.selectLanguage = function () { - kn.showScreenPopup(require('View/Popup/Languages')); + kn.showScreenPopup(require('View/Popup/Languages'), [ + this.language, this.languages(), LanguageStore.userLanguage() + ]); }; - module.exports = LoginAppView; + module.exports = LoginUserView; }()); \ No newline at end of file diff --git a/dev/View/App/MailBox/FolderList.js b/dev/View/User/MailBox/FolderList.js similarity index 64% rename from dev/View/App/MailBox/FolderList.js rename to dev/View/User/MailBox/FolderList.js index 6ca5d49b2..7004b0fb3 100644 --- a/dev/View/App/MailBox/FolderList.js +++ b/dev/View/User/MailBox/FolderList.js @@ -13,11 +13,14 @@ Utils = require('Common/Utils'), Enums = require('Common/Enums'), Globals = require('Common/Globals'), - LinkBuilder = require('Common/LinkBuilder'), + Links = require('Common/Links'), - Settings = require('Storage/Settings'), - Cache = require('Storage/App/Cache'), - Data = require('Storage/App/Data'), + Cache = require('Common/Cache'), + + AppStore = require('Stores/User/App'), + SettingsStore = require('Stores/User/Settings'), + FolderStore = require('Stores/User/Folder'), + MessageStore = require('Stores/User/Message'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -27,31 +30,39 @@ * @constructor * @extends AbstractView */ - function FolderListMailBoxAppView() + function FolderListMailBoxUserView() { AbstractView.call(this, 'Left', 'MailFolderList'); this.oContentVisible = null; this.oContentScrollable = null; - this.messageList = Data.messageList; - this.folderList = Data.folderList; - this.folderListSystem = Data.folderListSystem; - this.foldersChanging = Data.foldersChanging; + this.composeInEdit = AppStore.composeInEdit; + + this.messageList = MessageStore.messageList; + this.folderList = FolderStore.folderList; + this.folderListSystem = FolderStore.folderListSystem; + this.foldersChanging = FolderStore.foldersChanging; + + this.foldersListWithSingleInboxRootFolder = FolderStore.foldersListWithSingleInboxRootFolder; this.leftPanelDisabled = Globals.leftPanelDisabled; this.iDropOverTimer = 0; - this.allowContacts = !!Settings.settingsGet('ContactsIsAllowed'); + this.allowContacts = !!AppStore.contactsIsAllowed(); + + this.folderListFocused = ko.computed(function () { + return Enums.Focused.FolderList === AppStore.focusedState(); + }); kn.constructorEnd(this); } - kn.extendAsViewModel(['View/App/MailBox/FolderList', 'MailBoxFolderListViewModel'], FolderListMailBoxAppView); - _.extend(FolderListMailBoxAppView.prototype, AbstractView.prototype); + kn.extendAsViewModel(['View/User/MailBox/FolderList', 'View/App/MailBox/FolderList', 'MailBoxFolderListViewModel'], FolderListMailBoxUserView); + _.extend(FolderListMailBoxUserView.prototype, AbstractView.prototype); - FolderListMailBoxAppView.prototype.onBuild = function (oDom) + FolderListMailBoxUserView.prototype.onBuild = function (oDom) { this.oContentVisible = $('.b-content', oDom); this.oContentScrollable = $('.content', this.oContentVisible); @@ -69,7 +80,7 @@ if (oFolder && oEvent) { bCollapsed = oFolder.collapsed(); - require('App/App').setExpandedFolder(oFolder.fullNameHash, bCollapsed); + require('App/User').setExpandedFolder(oFolder.fullNameHash, bCollapsed); oFolder.collapsed(!bCollapsed); oEvent.preventDefault(); @@ -86,17 +97,17 @@ if (oFolder) { - if (Enums.Layout.NoPreview === Data.layout()) + if (Enums.Layout.NoPreview === SettingsStore.layout()) { - Data.message(null); + MessageStore.message(null); } - if (oFolder.fullNameRaw === Data.currentFolderFullNameRaw()) + if (oFolder.fullNameRaw === FolderStore.currentFolderFullNameRaw()) { Cache.setFolderHash(oFolder.fullNameRaw, ''); } - kn.setHash(LinkBuilder.mailBox(oFolder.fullNameHash)); + kn.setHash(Links.mailBox(oFolder.fullNameHash)); } }) ; @@ -137,7 +148,7 @@ var $items = $('.b-folders .e-item .e-link:not(.hidden).focused', oDom); if ($items.length && $items[0]) { - self.folderList.focused(false); + AppStore.focusedState(Enums.Focused.MessageList); $items.click(); } @@ -152,7 +163,7 @@ if (oFolder) { bCollapsed = oFolder.collapsed(); - require('App/App').setExpandedFolder(oFolder.fullNameHash, bCollapsed); + require('App/User').setExpandedFolder(oFolder.fullNameHash, bCollapsed); oFolder.collapsed(!bCollapsed); } } @@ -161,38 +172,38 @@ }); key('esc, tab, shift+tab, right', Enums.KeyState.FolderList, function () { - self.folderList.focused(false); + AppStore.focusedState(Enums.Focused.MessageList); return false; }); - self.folderList.focused.subscribe(function (bValue) { + AppStore.focusedState.subscribe(function (mValue) { $('.b-folders .e-item .e-link.focused', oDom).removeClass('focused'); - if (bValue) + if (Enums.Focused.FolderList === mValue) { $('.b-folders .e-item .e-link.selected', oDom).addClass('focused'); } }); }; - FolderListMailBoxAppView.prototype.messagesDropOver = function (oFolder) + FolderListMailBoxUserView.prototype.messagesDropOver = function (oFolder) { window.clearTimeout(this.iDropOverTimer); if (oFolder && oFolder.collapsed()) { this.iDropOverTimer = window.setTimeout(function () { oFolder.collapsed(false); - require('App/App').setExpandedFolder(oFolder.fullNameHash, true); + require('App/User').setExpandedFolder(oFolder.fullNameHash, true); Utils.windowResize(); }, 500); } }; - FolderListMailBoxAppView.prototype.messagesDropOut = function () + FolderListMailBoxUserView.prototype.messagesDropOut = function () { window.clearTimeout(this.iDropOverTimer); }; - FolderListMailBoxAppView.prototype.scrollToFocused = function () + FolderListMailBoxUserView.prototype.scrollToFocused = function () { if (!this.oContentVisible || !this.oContentScrollable) { @@ -229,7 +240,7 @@ * @param {FolderModel} oToFolder * @param {{helper:jQuery}} oUi */ - FolderListMailBoxAppView.prototype.messagesDrop = function (oToFolder, oUi) + FolderListMailBoxUserView.prototype.messagesDrop = function (oToFolder, oUi) { if (oToFolder && oUi && oUi.helper) { @@ -241,27 +252,27 @@ if (Utils.isNormal(sFromFolderFullNameRaw) && '' !== sFromFolderFullNameRaw && Utils.isArray(aUids)) { - require('App/App').moveMessagesToFolder(sFromFolderFullNameRaw, aUids, oToFolder.fullNameRaw, bCopy); + require('App/User').moveMessagesToFolder(sFromFolderFullNameRaw, aUids, oToFolder.fullNameRaw, bCopy); } } }; - FolderListMailBoxAppView.prototype.composeClick = function () + FolderListMailBoxUserView.prototype.composeClick = function () { kn.showScreenPopup(require('View/Popup/Compose')); }; - FolderListMailBoxAppView.prototype.createFolder = function () + FolderListMailBoxUserView.prototype.createFolder = function () { kn.showScreenPopup(require('View/Popup/FolderCreate')); }; - FolderListMailBoxAppView.prototype.configureFolders = function () + FolderListMailBoxUserView.prototype.configureFolders = function () { - kn.setHash(LinkBuilder.settings('folders')); + kn.setHash(Links.settings('folders')); }; - FolderListMailBoxAppView.prototype.contactsClick = function () + FolderListMailBoxUserView.prototype.contactsClick = function () { if (this.allowContacts) { @@ -269,6 +280,6 @@ } }; - module.exports = FolderListMailBoxAppView; + module.exports = FolderListMailBoxUserView; }()); diff --git a/dev/View/App/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js similarity index 53% rename from dev/View/App/MailBox/MessageList.js rename to dev/View/User/MailBox/MessageList.js index 9fb301878..cd254167f 100644 --- a/dev/View/App/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -4,6 +4,7 @@ 'use strict'; var + window = require('window'), _ = require('_'), $ = require('$'), ko = require('ko'), @@ -15,14 +16,21 @@ Consts = require('Common/Consts'), Globals = require('Common/Globals'), Utils = require('Common/Utils'), - LinkBuilder = require('Common/LinkBuilder'), + Links = require('Common/Links'), Events = require('Common/Events'), Selector = require('Common/Selector'), + Translator = require('Common/Translator'), + + Cache = require('Common/Cache'), + + AppStore = require('Stores/User/App'), + QuotaStore = require('Stores/User/Quota'), + SettingsStore = require('Stores/User/Settings'), + FolderStore = require('Stores/User/Folder'), + MessageStore = require('Stores/User/Message'), Settings = require('Storage/Settings'), - Cache = require('Storage/App/Cache'), - Data = require('Storage/App/Data'), - Remote = require('Storage/App/Remote'), + Remote = require('Remote/User/Ajax'), kn = require('Knoin/Knoin'), AbstractView = require('Knoin/AbstractView') @@ -32,7 +40,7 @@ * @constructor * @extends AbstractView */ - function MessageListMailBoxAppView() + function MessageListMailBoxUserView() { AbstractView.call(this, 'Right', 'MailMessageList'); @@ -44,32 +52,37 @@ this.popupVisibility = Globals.popupVisibility; - this.message = Data.message; - this.messageList = Data.messageList; - this.folderList = Data.folderList; - this.currentMessage = Data.currentMessage; - this.isMessageSelected = Data.isMessageSelected; - this.messageListSearch = Data.messageListSearch; - this.messageListError = Data.messageListError; - this.folderMenuForMove = Data.folderMenuForMove; + this.message = MessageStore.message; + this.messageList = MessageStore.messageList; + this.messageListDisableAutoSelect = MessageStore.messageListDisableAutoSelect; - this.useCheckboxesInList = Data.useCheckboxesInList; + this.folderList = FolderStore.folderList; - this.mainMessageListSearch = Data.mainMessageListSearch; - this.messageListEndFolder = Data.messageListEndFolder; + this.selectorMessageSelected = MessageStore.selectorMessageSelected; + this.selectorMessageFocused = MessageStore.selectorMessageFocused; + this.isMessageSelected = MessageStore.isMessageSelected; + this.messageListSearch = MessageStore.messageListSearch; + this.messageListError = MessageStore.messageListError; + this.folderMenuForMove = FolderStore.folderMenuForMove; - this.messageListChecked = Data.messageListChecked; - this.messageListCheckedOrSelected = Data.messageListCheckedOrSelected; - this.messageListCheckedOrSelectedUidsWithSubMails = Data.messageListCheckedOrSelectedUidsWithSubMails; - this.messageListCompleteLoadingThrottle = Data.messageListCompleteLoadingThrottle; + this.useCheckboxesInList = SettingsStore.useCheckboxesInList; - Utils.initOnStartOrLangChange(function () { - this.emptySubjectValue = Utils.i18n('MESSAGE_LIST/EMPTY_SUBJECT_TEXT'); + this.mainMessageListSearch = MessageStore.mainMessageListSearch; + this.messageListEndFolder = MessageStore.messageListEndFolder; + + this.messageListChecked = MessageStore.messageListChecked; + this.messageListCheckedOrSelected = MessageStore.messageListCheckedOrSelected; + this.messageListCheckedOrSelectedUidsWithSubMails = MessageStore.messageListCheckedOrSelectedUidsWithSubMails; + this.messageListCompleteLoadingThrottle = MessageStore.messageListCompleteLoadingThrottle; + this.messageListCompleteLoadingThrottleForAnimation = MessageStore.messageListCompleteLoadingThrottleForAnimation; + + Translator.initOnStartOrLangChange(function () { + this.emptySubjectValue = Translator.i18n('MESSAGE_LIST/EMPTY_SUBJECT_TEXT'); }, this); - this.userQuota = Data.userQuota; - this.userUsageSize = Data.userUsageSize; - this.userUsageProc = Data.userUsageProc; + this.userQuota = QuotaStore.quota; + this.userUsageSize = QuotaStore.usage; + this.userUsageProc = QuotaStore.percentage; this.moveDropdownTrigger = ko.observable(false); this.moreDropdownTrigger = ko.observable(false); @@ -81,25 +94,26 @@ this.dragOverBodyArea = ko.observable(null); this.messageListItemTemplate = ko.computed(function () { - return Enums.Layout.NoPreview !== Data.layout() ? + return Enums.Layout.SidePreview === SettingsStore.layout() ? 'MailMessageListItem' : 'MailMessageListItemNoPreviewPane'; }); this.messageListSearchDesc = ko.computed(function () { - var sValue = Data.messageListEndSearch(); - return '' === sValue ? '' : Utils.i18n('MESSAGE_LIST/SEARCH_RESULT_FOR', {'SEARCH': sValue}); + var sValue = MessageStore.messageListEndSearch(); + return '' === sValue ? '' : Translator.i18n('MESSAGE_LIST/SEARCH_RESULT_FOR', {'SEARCH': sValue}); }); - this.messageListPagenator = ko.computed(Utils.computedPagenatorHelper(Data.messageListPage, Data.messageListPageCount)); + this.messageListPagenator = ko.computed(Utils.computedPagenatorHelper( + MessageStore.messageListPage, MessageStore.messageListPageCount)); this.checkAll = ko.computed({ 'read': function () { - return 0 < Data.messageListChecked().length; + return 0 < MessageStore.messageListChecked().length; }, 'write': function (bValue) { bValue = !!bValue; - _.each(Data.messageList(), function (oMessage) { + _.each(MessageStore.messageList(), function (oMessage) { oMessage.checked(bValue); }); } @@ -118,8 +132,8 @@ this.isIncompleteChecked = ko.computed(function () { var - iM = Data.messageList().length, - iC = Data.messageListChecked().length + iM = MessageStore.messageList().length, + iC = MessageStore.messageListChecked().length ; return 0 < iM && 0 < iC && iM > iC; }, this); @@ -133,162 +147,242 @@ }, this); this.isSpamFolder = ko.computed(function () { - return Data.spamFolder() === this.messageListEndFolder() && - '' !== Data.spamFolder(); + return FolderStore.spamFolder() === this.messageListEndFolder() && + '' !== FolderStore.spamFolder(); }, this); this.isSpamDisabled = ko.computed(function () { - return Consts.Values.UnuseOptionValue === Data.spamFolder(); + return Consts.Values.UnuseOptionValue === FolderStore.spamFolder(); }, this); this.isTrashFolder = ko.computed(function () { - return Data.trashFolder() === this.messageListEndFolder() && - '' !== Data.trashFolder(); + return FolderStore.trashFolder() === this.messageListEndFolder() && + '' !== FolderStore.trashFolder(); }, this); this.isDraftFolder = ko.computed(function () { - return Data.draftFolder() === this.messageListEndFolder() && - '' !== Data.draftFolder(); + return FolderStore.draftFolder() === this.messageListEndFolder() && + '' !== FolderStore.draftFolder(); }, this); this.isSentFolder = ko.computed(function () { - return Data.sentFolder() === this.messageListEndFolder() && - '' !== Data.sentFolder(); + return FolderStore.sentFolder() === this.messageListEndFolder() && + '' !== FolderStore.sentFolder(); }, this); this.isArchiveFolder = ko.computed(function () { - return Data.archiveFolder() === this.messageListEndFolder() && - '' !== Data.archiveFolder(); + return FolderStore.archiveFolder() === this.messageListEndFolder() && + '' !== FolderStore.archiveFolder(); }, this); this.isArchiveDisabled = ko.computed(function () { - return Consts.Values.UnuseOptionValue === Data.archiveFolder(); + return Consts.Values.UnuseOptionValue === FolderStore.archiveFolder(); }, this); + this.isArchiveVisible = ko.computed(function () { + return !this.isArchiveFolder() && !this.isArchiveDisabled() && !this.isDraftFolder(); + }, this); + + this.isSpamVisible = ko.computed(function () { + return !this.isSpamFolder() && !this.isSpamDisabled() && !this.isDraftFolder() && !this.isSentFolder(); + }, this); + + this.isUnSpamVisible = ko.computed(function () { + return this.isSpamFolder() && !this.isSpamDisabled() && !this.isDraftFolder() && !this.isSentFolder(); + }, this); + + this.messageListFocused = ko.computed(function () { + return Enums.Focused.MessageList === AppStore.focusedState(); + }); + this.canBeMoved = this.hasCheckedOrSelectedLines; this.clearCommand = Utils.createCommand(this, function () { - kn.showScreenPopup(require('View/Popup/FolderClear'), [Data.currentFolder()]); + kn.showScreenPopup(require('View/Popup/FolderClear'), [FolderStore.currentFolder()]); }); this.multyForwardCommand = Utils.createCommand(this, function () { kn.showScreenPopup(require('View/Popup/Compose'), [ - Enums.ComposeType.ForwardAsAttachment, Data.messageListCheckedOrSelected()]); + Enums.ComposeType.ForwardAsAttachment, MessageStore.messageListCheckedOrSelected()]); }, this.canBeMoved); this.deleteWithoutMoveCommand = Utils.createCommand(this, function () { - require('App/App').deleteMessagesFromFolder(Enums.FolderType.Trash, - Data.currentFolderFullNameRaw(), - Data.messageListCheckedOrSelectedUidsWithSubMails(), false); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.Trash, + FolderStore.currentFolderFullNameRaw(), + MessageStore.messageListCheckedOrSelectedUidsWithSubMails(), false); }, this.canBeMoved); this.deleteCommand = Utils.createCommand(this, function () { - require('App/App').deleteMessagesFromFolder(Enums.FolderType.Trash, - Data.currentFolderFullNameRaw(), - Data.messageListCheckedOrSelectedUidsWithSubMails(), true); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.Trash, + FolderStore.currentFolderFullNameRaw(), + MessageStore.messageListCheckedOrSelectedUidsWithSubMails(), true); }, this.canBeMoved); this.archiveCommand = Utils.createCommand(this, function () { - require('App/App').deleteMessagesFromFolder(Enums.FolderType.Archive, - Data.currentFolderFullNameRaw(), - Data.messageListCheckedOrSelectedUidsWithSubMails(), true); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.Archive, + FolderStore.currentFolderFullNameRaw(), + MessageStore.messageListCheckedOrSelectedUidsWithSubMails(), true); }, this.canBeMoved); this.spamCommand = Utils.createCommand(this, function () { - require('App/App').deleteMessagesFromFolder(Enums.FolderType.Spam, - Data.currentFolderFullNameRaw(), - Data.messageListCheckedOrSelectedUidsWithSubMails(), true); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.Spam, + FolderStore.currentFolderFullNameRaw(), + MessageStore.messageListCheckedOrSelectedUidsWithSubMails(), true); }, this.canBeMoved); this.notSpamCommand = Utils.createCommand(this, function () { - require('App/App').deleteMessagesFromFolder(Enums.FolderType.NotSpam, - Data.currentFolderFullNameRaw(), - Data.messageListCheckedOrSelectedUidsWithSubMails(), true); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.NotSpam, + FolderStore.currentFolderFullNameRaw(), + MessageStore.messageListCheckedOrSelectedUidsWithSubMails(), true); }, this.canBeMoved); this.moveCommand = Utils.createCommand(this, Utils.emptyFunction, this.canBeMoved); this.reloadCommand = Utils.createCommand(this, function () { - if (!Data.messageListCompleteLoadingThrottle()) + if (!MessageStore.messageListCompleteLoadingThrottleForAnimation()) { - require('App/App').reloadMessageList(false, true); + require('App/User').reloadMessageList(false, true); } }); this.quotaTooltip = _.bind(this.quotaTooltip, this); - this.selector = new Selector(this.messageList, this.currentMessage, + this.selector = new Selector(this.messageList, this.selectorMessageSelected, this.selectorMessageFocused, '.messageListItem .actionHandle', '.messageListItem.selected', '.messageListItem .checkboxMessage', '.messageListItem.focused'); this.selector.on('onItemSelect', _.bind(function (oMessage) { - if (oMessage) - { - Data.message(Data.staticMessageList.populateByMessageListItem(oMessage)); - this.populateMessageBody(Data.message()); - - if (Enums.Layout.NoPreview === Data.layout()) - { - kn.setHash(LinkBuilder.messagePreview(), true); - Data.message.focused(true); - } - } - else - { - Data.message(null); - } + MessageStore.selectMessage(oMessage); }, this)); this.selector.on('onItemGetUid', function (oMessage) { return oMessage ? oMessage.generateUid() : ''; }); - Data.messageListEndHash.subscribe(function () { - this.selector.scrollToTop(); - }, this); + this.selector.on('onAutoSelect', _.bind(function () { + return this.useAutoSelect(); + }, this)); - Data.layout.subscribe(function (mValue) { - this.selector.autoSelect(Enums.Layout.NoPreview !== mValue); - }, this); - - Data.layout.valueHasMutated(); + this.selector.on('onUpUpOrDownDown', _.bind(function (bV) { + this.goToUpUpOrDownDown(bV); + }, this)); Events - .sub('mailbox.message-list.selector.go-down', function () { - this.selector.goDown(true); + .sub('mailbox.message-list.selector.go-down', function (bSelect) { + this.selector.goDown(bSelect); }, this) - .sub('mailbox.message-list.selector.go-up', function () { - this.selector.goUp(true); + .sub('mailbox.message-list.selector.go-up', function (bSelect) { + this.selector.goUp(bSelect); }, this) ; + MessageStore.messageListEndHash.subscribe(function () { + this.selector.scrollToTop(); + }, this); + kn.constructorEnd(this); } - kn.extendAsViewModel(['View/App/MailBox/MessageList', 'MailBoxMessageListViewModel'], MessageListMailBoxAppView); - _.extend(MessageListMailBoxAppView.prototype, AbstractView.prototype); + kn.extendAsViewModel(['View/User/MailBox/MessageList', 'View/App/MailBox/MessageList', 'MailBoxMessageListViewModel'], MessageListMailBoxUserView); + _.extend(MessageListMailBoxUserView.prototype, AbstractView.prototype); /** * @type {string} */ - MessageListMailBoxAppView.prototype.emptySubjectValue = ''; + MessageListMailBoxUserView.prototype.emptySubjectValue = ''; - MessageListMailBoxAppView.prototype.searchEnterAction = function () + MessageListMailBoxUserView.prototype.iGoToUpUpOrDownDownTimeout = 0; + + MessageListMailBoxUserView.prototype.goToUpUpOrDownDown = function (bUp) + { + var self = this; + + window.clearTimeout(this.iGoToUpUpOrDownDownTimeout); + this.iGoToUpUpOrDownDownTimeout = window.setTimeout(function () { + + var + oPrev = null, + oNext = null, + oTemp = null, + oCurrent = null, + aPages = self.messageListPagenator() + ; + + _.find(aPages, function (oItem) { + + if (oItem) + { + if (oCurrent) + { + oNext = oItem; + } + + if (oItem.current) + { + oCurrent = oItem; + oPrev = oTemp; + } + + if (oNext) + { + return true; + } + + oTemp = oItem; + } + + return false; + }); + + if (Enums.Layout.NoPreview === SettingsStore.layout() && !self.message()) + { + self.selector.iFocusedNextHelper = bUp ? -1 : 1; + } + else + { + self.selector.iSelectNextHelper = bUp ? -1 : 1; + } + + if (bUp ? oPrev : oNext) + { + self.selector.unselect(); + self.gotoPage(bUp ? oPrev : oNext); + } + + }, 350); + }; + + MessageListMailBoxUserView.prototype.useAutoSelect = function () + { + if (this.messageListDisableAutoSelect()) + { + return false; + } + + if (/is:unseen/.test(this.mainMessageListSearch())) + { + return false; + } + + return Enums.Layout.NoPreview !== SettingsStore.layout(); + }; + + MessageListMailBoxUserView.prototype.searchEnterAction = function () { this.mainMessageListSearch(this.sLastSearchValue); this.inputMessageListSearchFocus(false); }; /** - * @returns {string} + * @return {string} */ - MessageListMailBoxAppView.prototype.printableMessageCountForDeletion = function () + MessageListMailBoxUserView.prototype.printableMessageCountForDeletion = function () { var iCnt = this.messageListCheckedOrSelectedUidsWithSubMails().length; return 1 < iCnt ? ' (' + (100 > iCnt ? iCnt : '99+') + ')' : ''; }; - MessageListMailBoxAppView.prototype.cancelSearch = function () + MessageListMailBoxUserView.prototype.cancelSearch = function () { this.mainMessageListSearch(''); this.inputMessageListSearchFocus(false); @@ -299,19 +393,19 @@ * @param {boolean} bCopy * @return {boolean} */ - MessageListMailBoxAppView.prototype.moveSelectedMessagesToFolder = function (sToFolderFullNameRaw, bCopy) + MessageListMailBoxUserView.prototype.moveSelectedMessagesToFolder = function (sToFolderFullNameRaw, bCopy) { if (this.canBeMoved()) { - require('App/App').moveMessagesToFolder( - Data.currentFolderFullNameRaw(), - Data.messageListCheckedOrSelectedUidsWithSubMails(), sToFolderFullNameRaw, bCopy); + require('App/User').moveMessagesToFolder( + FolderStore.currentFolderFullNameRaw(), + MessageStore.messageListCheckedOrSelectedUidsWithSubMails(), sToFolderFullNameRaw, bCopy); } return false; }; - MessageListMailBoxAppView.prototype.dragAndDronHelper = function (oMessageListItem) + MessageListMailBoxUserView.prototype.dragAndDronHelper = function (oMessageListItem) { if (oMessageListItem) { @@ -319,16 +413,16 @@ } var - oEl = Utils.draggeblePlace(), - aUids = Data.messageListCheckedOrSelectedUidsWithSubMails() + oEl = Utils.draggablePlace(), + aUids = MessageStore.messageListCheckedOrSelectedUidsWithSubMails() ; - oEl.data('rl-folder', Data.currentFolderFullNameRaw()); + oEl.data('rl-folder', FolderStore.currentFolderFullNameRaw()); oEl.data('rl-uids', aUids); oEl.find('.text').text('' + aUids.length); _.defer(function () { - var aUids = Data.messageListCheckedOrSelectedUidsWithSubMails(); + var aUids = MessageStore.messageListCheckedOrSelectedUidsWithSubMails(); oEl.data('rl-uids', aUids); oEl.find('.text').text('' + aUids.length); @@ -337,140 +431,26 @@ return oEl; }; - /** - * @param {string} sResult - * @param {AjaxJsonDefaultResponse} oData - * @param {boolean} bCached - */ - MessageListMailBoxAppView.prototype.onMessageResponse = function (sResult, oData, bCached) - { - Data.hideMessageBodies(); - Data.messageLoading(false); - - if (Enums.StorageResultType.Success === sResult && oData && oData.Result) - { - Data.setMessage(oData, bCached); - } - else if (Enums.StorageResultType.Unload === sResult) - { - Data.message(null); - Data.messageError(''); - } - else if (Enums.StorageResultType.Abort !== sResult) - { - Data.message(null); - Data.messageError((oData && oData.ErrorCode ? - Utils.getNotification(oData.ErrorCode) : - Utils.getNotification(Enums.Notification.UnknownError))); - } - }; - - MessageListMailBoxAppView.prototype.populateMessageBody = function (oMessage) - { - if (oMessage) - { - if (Remote.message(this.onMessageResponse, oMessage.folderFullNameRaw, oMessage.uid)) - { - Data.messageLoading(true); - } - else - { - Utils.log('Error: Unknown message request: ' + oMessage.folderFullNameRaw + ' ~ ' + oMessage.uid + ' [e-101]'); - } - } - }; - /** * @param {string} sFolderFullNameRaw + * @param {string|bool} sUid * @param {number} iSetAction * @param {Array=} aMessages = null */ - MessageListMailBoxAppView.prototype.setAction = function (sFolderFullNameRaw, iSetAction, aMessages) + MessageListMailBoxUserView.prototype.setAction = function (sFolderFullNameRaw, mUid, iSetAction, aMessages) { - var - aUids = [], - oFolder = null, - iAlreadyUnread = 0 - ; - - if (Utils.isUnd(aMessages)) - { - aMessages = Data.messageListChecked(); - } - - aUids = _.map(aMessages, function (oMessage) { - return oMessage.uid; - }); - - if ('' !== sFolderFullNameRaw && 0 < aUids.length) - { - switch (iSetAction) { - case Enums.MessageSetAction.SetSeen: - _.each(aMessages, function (oMessage) { - if (oMessage.unseen()) - { - iAlreadyUnread++; - } - - oMessage.unseen(false); - Cache.storeMessageFlagsToCache(oMessage); - }); - - oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw); - if (oFolder) - { - oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread); - } - - Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, aUids, true); - break; - case Enums.MessageSetAction.UnsetSeen: - _.each(aMessages, function (oMessage) { - if (oMessage.unseen()) - { - iAlreadyUnread++; - } - - oMessage.unseen(true); - Cache.storeMessageFlagsToCache(oMessage); - }); - - oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw); - if (oFolder) - { - oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread + aUids.length); - } - Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, aUids, false); - break; - case Enums.MessageSetAction.SetFlag: - _.each(aMessages, function (oMessage) { - oMessage.flagged(true); - Cache.storeMessageFlagsToCache(oMessage); - }); - Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, aUids, true); - break; - case Enums.MessageSetAction.UnsetFlag: - _.each(aMessages, function (oMessage) { - oMessage.flagged(false); - Cache.storeMessageFlagsToCache(oMessage); - }); - Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, aUids, false); - break; - } - - require('App/App').reloadFlagsCurrentMessageListAndMessageFromCache(); - } + require('App/User').messageListAction(sFolderFullNameRaw, mUid, iSetAction, aMessages); }; /** * @param {string} sFolderFullNameRaw * @param {number} iSetAction */ - MessageListMailBoxAppView.prototype.setActionForAll = function (sFolderFullNameRaw, iSetAction) + MessageListMailBoxUserView.prototype.setActionForAll = function (sFolderFullNameRaw, iSetAction) { var oFolder = null, - aMessages = Data.messageList() + aMessages = MessageStore.messageList() ; if ('' !== sFolderFullNameRaw) @@ -509,37 +489,41 @@ break; } - require('App/App').reloadFlagsCurrentMessageListAndMessageFromCache(); + require('App/User').reloadFlagsCurrentMessageListAndMessageFromCache(); } } }; - MessageListMailBoxAppView.prototype.listSetSeen = function () + MessageListMailBoxUserView.prototype.listSetSeen = function () { - this.setAction(Data.currentFolderFullNameRaw(), Enums.MessageSetAction.SetSeen, Data.messageListCheckedOrSelected()); + this.setAction(FolderStore.currentFolderFullNameRaw(), true, + Enums.MessageSetAction.SetSeen, MessageStore.messageListCheckedOrSelected()); }; - MessageListMailBoxAppView.prototype.listSetAllSeen = function () + MessageListMailBoxUserView.prototype.listSetAllSeen = function () { - this.setActionForAll(Data.currentFolderFullNameRaw(), Enums.MessageSetAction.SetSeen); + this.setActionForAll(FolderStore.currentFolderFullNameRaw(), Enums.MessageSetAction.SetSeen); }; - MessageListMailBoxAppView.prototype.listUnsetSeen = function () + MessageListMailBoxUserView.prototype.listUnsetSeen = function () { - this.setAction(Data.currentFolderFullNameRaw(), Enums.MessageSetAction.UnsetSeen, Data.messageListCheckedOrSelected()); + this.setAction(FolderStore.currentFolderFullNameRaw(), true, + Enums.MessageSetAction.UnsetSeen, MessageStore.messageListCheckedOrSelected()); }; - MessageListMailBoxAppView.prototype.listSetFlags = function () + MessageListMailBoxUserView.prototype.listSetFlags = function () { - this.setAction(Data.currentFolderFullNameRaw(), Enums.MessageSetAction.SetFlag, Data.messageListCheckedOrSelected()); + this.setAction(FolderStore.currentFolderFullNameRaw(), true, + Enums.MessageSetAction.SetFlag, MessageStore.messageListCheckedOrSelected()); }; - MessageListMailBoxAppView.prototype.listUnsetFlags = function () + MessageListMailBoxUserView.prototype.listUnsetFlags = function () { - this.setAction(Data.currentFolderFullNameRaw(), Enums.MessageSetAction.UnsetFlag, Data.messageListCheckedOrSelected()); + this.setAction(FolderStore.currentFolderFullNameRaw(), true, + Enums.MessageSetAction.UnsetFlag, MessageStore.messageListCheckedOrSelected()); }; - MessageListMailBoxAppView.prototype.flagMessages = function (oCurrentMessage) + MessageListMailBoxUserView.prototype.flagMessages = function (oCurrentMessage) { var aChecked = this.messageListCheckedOrSelected(), @@ -557,18 +541,18 @@ if (0 < aCheckedUids.length && -1 < Utils.inArray(oCurrentMessage.uid, aCheckedUids)) { - this.setAction(oCurrentMessage.folderFullNameRaw, oCurrentMessage.flagged() ? + this.setAction(oCurrentMessage.folderFullNameRaw, true, oCurrentMessage.flagged() ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, aChecked); } else { - this.setAction(oCurrentMessage.folderFullNameRaw, oCurrentMessage.flagged() ? + this.setAction(oCurrentMessage.folderFullNameRaw, true, oCurrentMessage.flagged() ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, [oCurrentMessage]); } } }; - MessageListMailBoxAppView.prototype.flagMessagesFast = function (bFlag) + MessageListMailBoxUserView.prototype.flagMessagesFast = function (bFlag) { var aChecked = this.messageListCheckedOrSelected(), @@ -583,18 +567,18 @@ if (Utils.isUnd(bFlag)) { - this.setAction(aChecked[0].folderFullNameRaw, + this.setAction(aChecked[0].folderFullNameRaw, true, aChecked.length === aFlagged.length ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, aChecked); } else { - this.setAction(aChecked[0].folderFullNameRaw, + this.setAction(aChecked[0].folderFullNameRaw, true, !bFlag ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, aChecked); } } }; - MessageListMailBoxAppView.prototype.seenMessagesFast = function (bSeen) + MessageListMailBoxUserView.prototype.seenMessagesFast = function (bSeen) { var aChecked = this.messageListCheckedOrSelected(), @@ -609,75 +593,47 @@ if (Utils.isUnd(bSeen)) { - this.setAction(aChecked[0].folderFullNameRaw, + this.setAction(aChecked[0].folderFullNameRaw, true, 0 < aUnseen.length ? Enums.MessageSetAction.SetSeen : Enums.MessageSetAction.UnsetSeen, aChecked); } else { - this.setAction(aChecked[0].folderFullNameRaw, + this.setAction(aChecked[0].folderFullNameRaw, true, bSeen ? Enums.MessageSetAction.SetSeen : Enums.MessageSetAction.UnsetSeen, aChecked); } } }; - MessageListMailBoxAppView.prototype.onBuild = function (oDom) + MessageListMailBoxUserView.prototype.gotoPage = function (oPage) + { + if (oPage) + { + kn.setHash(Links.mailBox( + FolderStore.currentFolderFullNameHash(), + oPage.value, + MessageStore.messageListSearch() + )); + } + }; + + MessageListMailBoxUserView.prototype.onBuild = function (oDom) { var self = this; this.oContentVisible = $('.b-content', oDom); this.oContentScrollable = $('.content', this.oContentVisible); - this.oContentVisible.on('click', '.fullThreadHandle', function () { - var - aList = [], - oMessage = ko.dataFor(this) - ; - - if (oMessage && !oMessage.lastInCollapsedThreadLoading()) - { - Data.messageListThreadFolder(oMessage.folderFullNameRaw); - - aList = Data.messageListThreadUids(); - - if (oMessage.lastInCollapsedThread()) - { - aList.push(0 < oMessage.parentUid() ? oMessage.parentUid() : oMessage.uid); - } - else - { - aList = _.without(aList, 0 < oMessage.parentUid() ? oMessage.parentUid() : oMessage.uid); - } - - Data.messageListThreadUids(_.uniq(aList)); - - oMessage.lastInCollapsedThreadLoading(true); - oMessage.lastInCollapsedThread(!oMessage.lastInCollapsedThread()); - - require('App/App').reloadMessageList(); - } - - return false; - }); - this.selector.init(this.oContentVisible, this.oContentScrollable, Enums.KeyState.MessageList); oDom .on('click', '.messageList .b-message-list-wrapper', function () { - if (self.message.focused()) + if (Enums.Focused.MessageView === AppStore.focusedState()) { - self.message.focused(false); + AppStore.focusedState(Enums.Focused.MessageList); } }) .on('click', '.e-pagenator .e-page', function () { - var oPage = ko.dataFor(this); - if (oPage) - { - kn.setHash(LinkBuilder.mailBox( - Data.currentFolderFullNameHash(), - oPage.value, - Data.messageListSearch() - )); - } + self.gotoPage(ko.dataFor(this)); }) .on('click', '.messageList .checkboxCkeckAll', function () { self.checkAll(!self.checkAll()); @@ -700,7 +656,7 @@ } }; - MessageListMailBoxAppView.prototype.initShortcuts = function () + MessageListMailBoxUserView.prototype.initShortcuts = function () { var self = this; @@ -709,6 +665,14 @@ return false; }); + key('enter', Enums.KeyState.MessageList, function () { + if (self.message() && self.useAutoSelect()) + { + Events.pub('mailbox.message-view.toggle-full-screen'); + return false; + } + }); + // archive (zip) key('z', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { self.archiveCommand(); @@ -719,7 +683,7 @@ key('delete, shift+delete, shift+3', Enums.KeyState.MessageList, function (event, handler) { if (event) { - if (0 < Data.messageListCheckedOrSelected().length) + if (0 < MessageStore.messageListCheckedOrSelected().length) { if (handler && 'shift+delete' === handler.shortcut) { @@ -801,28 +765,26 @@ key('tab, shift+tab, left, right', Enums.KeyState.MessageList, function (event, handler) { if (event && handler && ('shift+tab' === handler.shortcut || 'left' === handler.shortcut)) { - self.folderList.focused(true); + AppStore.focusedState(Enums.Focused.FolderList); } else if (self.message()) { - self.message.focused(true); + AppStore.focusedState(Enums.Focused.MessageView); } return false; }); - // TODO key('ctrl+left, command+left', Enums.KeyState.MessageView, function () { return false; }); - // TODO key('ctrl+right, command+right', Enums.KeyState.MessageView, function () { return false; }); }; - MessageListMailBoxAppView.prototype.prefetchNextTick = function () + MessageListMailBoxUserView.prototype.prefetchNextTick = function () { if (ifvisible && !this.bPrefetch && !ifvisible.now() && this.viewModelVisibility()) { @@ -857,26 +819,26 @@ } }; - MessageListMailBoxAppView.prototype.composeClick = function () + MessageListMailBoxUserView.prototype.composeClick = function () { kn.showScreenPopup(require('View/Popup/Compose')); }; - MessageListMailBoxAppView.prototype.advancedSearchClick = function () + MessageListMailBoxUserView.prototype.advancedSearchClick = function () { kn.showScreenPopup(require('View/Popup/AdvancedSearch')); }; - MessageListMailBoxAppView.prototype.quotaTooltip = function () + MessageListMailBoxUserView.prototype.quotaTooltip = function () { - return Utils.i18n('MESSAGE_LIST/QUOTA_SIZE', { + return Translator.i18n('MESSAGE_LIST/QUOTA_SIZE', { 'SIZE': Utils.friendlySize(this.userUsageSize()), 'PROC': this.userUsageProc(), 'LIMIT': Utils.friendlySize(this.userQuota()) }); }; - MessageListMailBoxAppView.prototype.initUploaderForAppend = function () + MessageListMailBoxUserView.prototype.initUploaderForAppend = function () { if (!Settings.settingsGet('AllowAppendMessage') || !this.dragOverArea()) { @@ -885,14 +847,13 @@ var oJua = new Jua({ - 'action': LinkBuilder.append(), + 'action': Links.append(), 'name': 'AppendFile', 'queueSize': 1, 'multipleSizeLimit': 1, - 'disableFolderDragAndDrop': true, 'hidden': { 'Folder': function () { - return Data.currentFolderFullNameRaw(); + return FolderStore.currentFolderFullNameRaw(); } }, 'dragAndDropElement': this.dragOverArea(), @@ -900,6 +861,13 @@ }) ; + this.dragOver.subscribe(function (bValue) { + if (bValue) + { + this.selector.scrollToTop(); + } + }, this); + oJua .on('onDragEnter', _.bind(function () { this.dragOverEnter(true); @@ -914,22 +882,24 @@ this.dragOver(false); }, this)) .on('onSelect', _.bind(function (sUid, oData) { + if (sUid && oData && 'message/rfc822' === oData['Type']) { - Data.messageListLoading(true); + MessageStore.messageListLoading(true); return true; } return false; + }, this)) .on('onComplete', _.bind(function () { - require('App/App').reloadMessageList(true, true); + require('App/User').reloadMessageList(true, true); }, this)) ; return !!oJua; }; - module.exports = MessageListMailBoxAppView; + module.exports = MessageListMailBoxUserView; }()); diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js new file mode 100644 index 000000000..fdba28c18 --- /dev/null +++ b/dev/View/User/MailBox/MessageView.js @@ -0,0 +1,1253 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + $ = require('$'), + ko = require('ko'), + key = require('key'), + + PhotoSwipe = require('PhotoSwipe'), + PhotoSwipeUI_Default = require('PhotoSwipeUI_Default'), + + Consts = require('Common/Consts'), + Enums = require('Common/Enums'), + Globals = require('Common/Globals'), + Utils = require('Common/Utils'), + Events = require('Common/Events'), + Translator = require('Common/Translator'), + Audio = require('Common/Audio'), + + Cache = require('Common/Cache'), + + AppStore = require('Stores/User/App'), + SettingsStore = require('Stores/User/Settings'), + AccountStore = require('Stores/User/Account'), + FolderStore = require('Stores/User/Folder'), + MessageStore = require('Stores/User/Message'), + + Local = require('Storage/Client'), + Remote = require('Remote/User/Ajax'), + + Promises = require('Promises/User/Ajax'), + + kn = require('Knoin/Knoin'), + AbstractView = require('Knoin/AbstractView') + ; + + /** + * @constructor + * @extends AbstractView + */ + function MessageViewMailBoxUserView() + { + AbstractView.call(this, 'Right', 'MailMessageView'); + + var + self = this, + sLastEmail = '', + createCommandHelper = function (sType) { + return Utils.createCommand(self, function () { + this.lastReplyAction(sType); + this.replyOrforward(sType); + }, self.canBeRepliedOrForwarded); + } + ; + + this.oDom = null; + this.oHeaderDom = null; + this.oMessageScrollerDom = null; + + this.pswp = null; + + this.message = MessageStore.message; + this.messageListChecked = MessageStore.messageListChecked; + this.hasCheckedMessages = MessageStore.hasCheckedMessages; + this.messageListCheckedOrSelectedUidsWithSubMails = MessageStore.messageListCheckedOrSelectedUidsWithSubMails; + this.messageLoadingThrottle = MessageStore.messageLoadingThrottle; + this.messagesBodiesDom = MessageStore.messagesBodiesDom; + this.useThreads = SettingsStore.useThreads; + this.replySameFolder = SettingsStore.replySameFolder; + this.layout = SettingsStore.layout; + this.usePreviewPane = SettingsStore.usePreviewPane; + this.isMessageSelected = MessageStore.isMessageSelected; + this.messageActiveDom = MessageStore.messageActiveDom; + this.messageError = MessageStore.messageError; + + this.fullScreenMode = MessageStore.messageFullScreenMode; + + this.messageListOfThreadsLoading = ko.observable(false).extend({'rateLimit': 1}); + + this.allowAttachmnetControls = ko.observable(false); + this.showAttachmnetControls = ko.observable(false); + + this.downloadAsZipLoading = ko.observable(false); + this.saveToOwnCloudLoading = ko.observable(false); + this.saveToDropboxLoading = ko.observable(false); + + this.showAttachmnetControls.subscribe(function (bV) { + if (this.message()) + { + _.each(this.message().attachments(), function (oItem) { + if (oItem) + { + oItem.checked(!!bV); + } + }); + } + }, this); + + this.lastReplyAction_ = ko.observable(''); + this.lastReplyAction = ko.computed({ + read: this.lastReplyAction_, + write: function (sValue) { + sValue = -1 === Utils.inArray(sValue, [ + Enums.ComposeType.Reply, Enums.ComposeType.ReplyAll, Enums.ComposeType.Forward + ]) ? Enums.ComposeType.Reply : sValue; + this.lastReplyAction_(sValue); + }, + owner: this + }); + + this.lastReplyAction(Local.get(Enums.ClientSideKeyName.LastReplyAction) || Enums.ComposeType.Reply); + this.lastReplyAction_.subscribe(function (sValue) { + Local.set(Enums.ClientSideKeyName.LastReplyAction, sValue); + }); + + this.showFullInfo = ko.observable(false); + this.moreDropdownTrigger = ko.observable(false); + this.messageDomFocused = ko.observable(false).extend({'rateLimit': 0}); + + this.messageVisibility = ko.computed(function () { + return !this.messageLoadingThrottle() && !!this.message(); + }, this); + + this.message.subscribe(function (oMessage) { + if (!oMessage) + { + MessageStore.selectorMessageSelected(null); + } + }, this); + + this.canBeRepliedOrForwarded = ko.computed(function () { + var bV = this.messageVisibility(); + return !this.isDraftFolder() && bV; + }, this); + + // commands + this.closeMessage = Utils.createCommand(this, function () { + MessageStore.message(null); + }); + + this.replyCommand = createCommandHelper(Enums.ComposeType.Reply); + this.replyAllCommand = createCommandHelper(Enums.ComposeType.ReplyAll); + this.forwardCommand = createCommandHelper(Enums.ComposeType.Forward); + this.forwardAsAttachmentCommand = createCommandHelper(Enums.ComposeType.ForwardAsAttachment); + this.editAsNewCommand = createCommandHelper(Enums.ComposeType.EditAsNew); + + this.messageVisibilityCommand = Utils.createCommand(this, Utils.emptyFunction, this.messageVisibility); + + this.messageEditCommand = Utils.createCommand(this, function () { + this.editMessage(); + }, this.messageVisibility); + + this.deleteCommand = Utils.createCommand(this, function () { + var oMessage = this.message(); + if (oMessage) + { + this.message(null); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.Trash, + oMessage.folderFullNameRaw, [oMessage.uid], true); + } + }, this.messageVisibility); + + this.deleteWithoutMoveCommand = Utils.createCommand(this, function () { + var oMessage = this.message(); + if (oMessage) + { + this.message(null); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.Trash, + oMessage.folderFullNameRaw, [oMessage.uid], false); + } + }, this.messageVisibility); + + this.archiveCommand = Utils.createCommand(this, function () { + var oMessage = this.message(); + if (oMessage) + { + this.message(null); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.Archive, + oMessage.folderFullNameRaw, [oMessage.uid], true); + } + }, this.messageVisibility); + + this.spamCommand = Utils.createCommand(this, function () { + var oMessage = this.message(); + if (oMessage) + { + this.message(null); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.Spam, + oMessage.folderFullNameRaw, [oMessage.uid], true); + } + }, this.messageVisibility); + + this.notSpamCommand = Utils.createCommand(this, function () { + var oMessage = this.message(); + if (oMessage) + { + this.message(null); + require('App/User').deleteMessagesFromFolder(Enums.FolderType.NotSpam, + oMessage.folderFullNameRaw, [oMessage.uid], true); + } + }, this.messageVisibility); + + // viewer + + this.viewBodyTopValue = ko.observable(0); + + this.viewFolder = ''; + this.viewUid = ''; + this.viewHash = ''; + this.viewSubject = ko.observable(''); + this.viewFromShort = ko.observable(''); + this.viewFromDkimData = ko.observable(['none', '']); + this.viewToShort = ko.observable(''); + this.viewFrom = ko.observable(''); + this.viewTo = ko.observable(''); + this.viewCc = ko.observable(''); + this.viewBcc = ko.observable(''); + this.viewReplyTo = ko.observable(''); + this.viewTimeStamp = ko.observable(0); + this.viewSize = ko.observable(''); + this.viewLineAsCss = ko.observable(''); + this.viewViewLink = ko.observable(''); + this.viewDownloadLink = ko.observable(''); + this.viewUserPic = ko.observable(Consts.DataImages.UserDotPic); + this.viewUserPicVisible = ko.observable(false); + this.viewIsImportant = ko.observable(false); + this.viewIsFlagged = ko.observable(false); + +// THREADS + this.viewThreads = ko.observableArray([]); + this.viewThreads.trigger = ko.observable(false); + + this.viewThreadMessages = MessageStore.messageThreadList; + + this.viewThreadMessages.error = ko.observable(''); + this.viewThreadMessages.showMore = ko.observable(false); + this.viewThreadMessages.limit = 6; + + this.viewThreadMessages.subscribe(function (aList) { + + var iSelectedIndex = -1; + + this.viewThreadMessages.error(''); + if (aList && 0 < aList.length) + { + _.each(aList, function (oM, iIndex) { + if (oM && oM.selected()) + { + iSelectedIndex = iIndex; + } + }); + + this.viewThreadMessages.showMore(this.viewThreadMessages.limit >= aList.length); + + if (-1 < iSelectedIndex && !this.viewThreadMessages.showMore() && + this.viewThreadMessages.limit < iSelectedIndex) + { + this.viewThreadMessages.showMore(true); + } + + this.selectSelectedThreadMessage(); + require('App/User').reloadFlagsCurrentMessageListAndMessageFromCache(); + } + + }, this); + + MessageStore.messageLastThreadUidsData.subscribe(function (oData) { + if (oData && oData['Uids']) + { + oData['Uid'] = Utils.pString(oData['Uid']); + if (this.viewFolder === oData['Folder'] && this.viewUid === oData['Uid']) + { + this.viewThreads(oData['Uids']); + this.viewThreads.trigger(!this.viewThreads.trigger()); + } + + var oMessage = MessageStore.message(); + if (oMessage && oMessage.folderFullNameRaw === oData['Folder'] && oMessage.uid === oData['Uid']) + { + oMessage.threads(oData['Uids']); + } + + oMessage = _.find(MessageStore.messageList(), function (oMessage) { + return oMessage && oMessage.folderFullNameRaw === oData['Folder'] && oMessage.uid === oData['Uid']; + }); + + if (oMessage && oMessage.folderFullNameRaw === oData['Folder'] && oMessage.uid === oData['Uid']) + { + oMessage.threads(oData['Uids']); + } + } + }, this); + + this.viewThreads.status = ko.computed(function () { + + this.viewThreads.trigger(); + + var + iIndex = 0, + aResult = [false, '', '', '', ''], + aThreads = this.viewThreads.peek(), + iLen = aThreads.length + ; + + if (1 < iLen) + { + iIndex = Utils.inArray(this.viewUid, aThreads); + if (-1 < iIndex) + { + aResult[0] = true; + aResult[1] = (iIndex + 1) + '/' + iLen; + aResult[2] = aThreads[iIndex]; + aResult[3] = 0 < iIndex && aThreads[iIndex - 1] ? aThreads[iIndex - 1] : ''; + aResult[4] = aThreads[iIndex + 1] ? aThreads[iIndex + 1] : ''; + } + } + + return aResult; + + }, this).extend({'notify': 'always'}); + + this.viewThreadsControlVisibility = ko.computed(function () { + return !!this.viewThreads.status()[0]; + }, this); + + this.viewThreadsControlDesc = ko.computed(function () { + return this.viewThreads.status()[1]; + }, this); + + this.viewThreadsControlBackAllow = ko.computed(function () { + return '' !== this.viewThreads.status()[4] && !this.messageLoadingThrottle(); + }, this); + + this.viewThreadsControlForwardAllow = ko.computed(function () { + return '' !== this.viewThreads.status()[3] && !this.messageLoadingThrottle(); + }, this); + + this.threadBackCommand = Utils.createCommand(this, function () { + var aStatus = this.viewThreads.status(); + this.openThreadMessage(aStatus[4]); + }, this.viewThreadsControlBackAllow); + + this.threadForwardCommand = Utils.createCommand(this, function () { + var aStatus = this.viewThreads.status(); + this.openThreadMessage(aStatus[3]); + }, this.viewThreadsControlForwardAllow); + + this.threadsDropdownTrigger = ko.observable(false); + + this.threadListCommand = Utils.createCommand(this, function () { + + var + self = this, + sFolder = this.viewFolder, + sUid = this.viewUid, + aUids = this.viewThreads(), + aStatus = this.viewThreads.status() + ; + + if (aStatus && aStatus[0]) + { + self.viewThreadMessages([]); + + Promises.messageListSimple(sFolder, aUids, this.messageListOfThreadsLoading).then(function (aList) { + + _.each(aList, function (oItem) { + if (oItem && oItem.uid) + { + oItem.selected(sUid === oItem.uid); + } + }); + + self.viewThreadMessages(aList); + + }).fail(function (iErrorCode) { + self.viewThreadMessages([]); + self.viewThreadMessages.error(Translator.getNotification( + iErrorCode, '', Enums.Notification.CantGetMessageList)); + }).done(); + } + + }, function () { + return !this.messageLoadingThrottle() && + !this.messageListOfThreadsLoading(); + }); + +// PGP + this.viewPgpPassword = ko.observable(''); + this.viewPgpSignedVerifyStatus = ko.computed(function () { + return this.message() ? this.message().pgpSignedVerifyStatus() : Enums.SignedVerifyStatus.None; + }, this); + + this.viewPgpSignedVerifyUser = ko.computed(function () { + return this.message() ? this.message().pgpSignedVerifyUser() : ''; + }, this); + + this.viewFromDkimStatusIconClass = ko.computed(function () { + + var sResult = 'icon-none iconcolor-display-none'; +// var sResult = 'icon-warning-alt iconcolor-grey'; + switch (this.viewFromDkimData()[0]) + { + case 'none': + break; + case 'pass': + sResult = 'icon-ok iconcolor-green'; +// sResult = 'icon-warning-alt iconcolor-green'; + break; + default: + sResult = 'icon-warning-alt iconcolor-red'; + break; + } + + return sResult; + + }, this); + + this.viewFromDkimStatusTitle = ko.computed(function () { + + var aStatus = this.viewFromDkimData(); + if (Utils.isNonEmptyArray(aStatus)) + { + if (aStatus[0] && aStatus[1]) + { + return aStatus[1]; + } + else if (aStatus[0]) + { + return 'DKIM: ' + aStatus[0]; + } + } + + return ''; + + }, this); + + this.message.subscribe(function (oMessage) { + + this.messageActiveDom(null); + + this.viewPgpPassword(''); + + if (oMessage) + { + this.showAttachmnetControls(false); + + if (this.viewHash !== oMessage.hash) + { + this.scrollMessageToTop(); + } + + this.viewFolder = oMessage.folderFullNameRaw; + this.viewUid = oMessage.uid; + this.viewHash = oMessage.hash; + this.viewSubject(oMessage.subject()); + this.viewFromShort(oMessage.fromToLine(true, true)); + this.viewFromDkimData(oMessage.fromDkimData()); + this.viewToShort(oMessage.toToLine(true, true)); + this.viewFrom(oMessage.fromToLine(false)); + this.viewTo(oMessage.toToLine(false)); + this.viewCc(oMessage.ccToLine(false)); + this.viewBcc(oMessage.bccToLine(false)); + this.viewReplyTo(oMessage.replyToToLine(false)); + this.viewTimeStamp(oMessage.dateTimeStampInUTC()); + this.viewSize(oMessage.friendlySize()); + this.viewLineAsCss(oMessage.lineAsCss()); + this.viewViewLink(oMessage.viewLink()); + this.viewDownloadLink(oMessage.downloadLink()); + this.viewIsImportant(oMessage.isImportant()); + this.viewIsFlagged(oMessage.flagged()); + + this.viewThreads(oMessage.threads()); + this.viewThreads.trigger(!this.viewThreads.trigger()); + + sLastEmail = oMessage.fromAsSingleEmail(); + Cache.getUserPic(sLastEmail, function (sPic, sEmail) { + if (sPic !== self.viewUserPic() && sLastEmail === sEmail) + { + self.viewUserPicVisible(false); + self.viewUserPic(Consts.DataImages.UserDotPic); + if ('' !== sPic) + { + self.viewUserPicVisible(true); + self.viewUserPic(sPic); + } + } + }); + } + else + { + this.viewFolder = ''; + this.viewUid = ''; + this.viewHash = ''; + + this.viewThreads([]); + + this.scrollMessageToTop(); + } + + }, this); + + this.message.viewTrigger.subscribe(function () { + var oMessage = this.message(); + if (oMessage) + { + this.viewIsFlagged(oMessage.flagged()); + } + else + { + this.viewIsFlagged(false); + } + }, this); + + this.fullScreenMode.subscribe(function (bValue) { + Globals.$html.toggleClass('rl-message-fullscreen', bValue); + Utils.windowResize(); + }); + + this.messageLoadingThrottle.subscribe(Utils.windowResizeCallback); + + this.messageFocused = ko.computed(function () { + return Enums.Focused.MessageView === AppStore.focusedState(); + }); + + this.messageListAndMessageViewLoading = ko.computed(function () { + return MessageStore.messageListCompleteLoadingThrottle() || MessageStore.messageLoadingThrottle(); + }); + + this.goUpCommand = Utils.createCommand(this, function () { + Events.pub('mailbox.message-list.selector.go-up', [ + Enums.Layout.NoPreview === this.layout() ? !!this.message() : true + ]); + }, function () { + return !this.messageListAndMessageViewLoading(); + }); + + this.goDownCommand = Utils.createCommand(this, function () { + Events.pub('mailbox.message-list.selector.go-down', [ + Enums.Layout.NoPreview === this.layout() ? !!this.message() : true + ]); + }, function () { + return !this.messageListAndMessageViewLoading(); + }); + + Events.sub('mailbox.message-view.toggle-full-screen', function () { + this.toggleFullScreen(); + }, this); + + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/User/MailBox/MessageView', 'View/App/MailBox/MessageView', 'MailBoxMessageViewViewModel'], MessageViewMailBoxUserView); + _.extend(MessageViewMailBoxUserView.prototype, AbstractView.prototype); + + MessageViewMailBoxUserView.prototype.openThreadMessage = function (sUid) + { + var oMessage = this.message(); + if (oMessage && sUid) + { + MessageStore.selectThreadMessage(oMessage.folderFullNameRaw, sUid); + } + }; + + MessageViewMailBoxUserView.prototype.isPgpActionVisible = function () + { + return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus(); + }; + + MessageViewMailBoxUserView.prototype.isPgpStatusVerifyVisible = function () + { + return Enums.SignedVerifyStatus.None !== this.viewPgpSignedVerifyStatus(); + }; + + MessageViewMailBoxUserView.prototype.isPgpStatusVerifySuccess = function () + { + return Enums.SignedVerifyStatus.Success === this.viewPgpSignedVerifyStatus(); + }; + + MessageViewMailBoxUserView.prototype.pgpStatusVerifyMessage = function () + { + var sResult = ''; + switch (this.viewPgpSignedVerifyStatus()) + { + case Enums.SignedVerifyStatus.UnknownPublicKeys: + sResult = Translator.i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND'); + break; + case Enums.SignedVerifyStatus.UnknownPrivateKey: + sResult = Translator.i18n('PGP_NOTIFICATIONS/NO_PRIVATE_KEY_FOUND'); + break; + case Enums.SignedVerifyStatus.Unverified: + sResult = Translator.i18n('PGP_NOTIFICATIONS/UNVERIFIRED_SIGNATURE'); + break; + case Enums.SignedVerifyStatus.Error: + sResult = Translator.i18n('PGP_NOTIFICATIONS/DECRYPTION_ERROR'); + break; + case Enums.SignedVerifyStatus.Success: + sResult = Translator.i18n('PGP_NOTIFICATIONS/GOOD_SIGNATURE', { + 'USER': this.viewPgpSignedVerifyUser() + }); + break; + } + + return sResult; + }; + + MessageViewMailBoxUserView.prototype.fullScreen = function () + { + this.fullScreenMode(true); + Utils.windowResize(); + }; + + MessageViewMailBoxUserView.prototype.unFullScreen = function () + { + this.fullScreenMode(false); + Utils.windowResize(); + }; + + MessageViewMailBoxUserView.prototype.toggleFullScreen = function () + { + Utils.removeSelection(); + + this.fullScreenMode(!this.fullScreenMode()); + Utils.windowResize(); + }; + + /** + * @param {string} sType + */ + MessageViewMailBoxUserView.prototype.replyOrforward = function (sType) + { + kn.showScreenPopup(require('View/Popup/Compose'), [sType, MessageStore.message()]); + }; + + MessageViewMailBoxUserView.prototype.checkHeaderHeight = function () + { + if (this.oHeaderDom) + { + this.viewBodyTopValue(this.message() ? this.oHeaderDom.height() + + 20 /* padding-(top/bottom): 20px */ + 1 /* borded-bottom: 1px */ : 0); + } + }; + + MessageViewMailBoxUserView.prototype.onBuild = function (oDom) + { + var + self = this, + sErrorMessage = Translator.i18n('PREVIEW_POPUP/IMAGE_ERROR'), + fCheckHeaderHeight = _.bind(this.checkHeaderHeight, this) + ; + + this.oDom = oDom; + + this.fullScreenMode.subscribe(function (bValue) { + if (bValue && self.message()) + { + AppStore.focusedState(Enums.Focused.MessageView); + } + }, this); + + this.fullScreenMode.subscribe(fCheckHeaderHeight); + this.showFullInfo.subscribe(fCheckHeaderHeight); + this.message.subscribe(fCheckHeaderHeight); + + Events.sub('window.resize', _.throttle(function () { + _.delay(fCheckHeaderHeight, 1); + _.delay(fCheckHeaderHeight, 200); + _.delay(fCheckHeaderHeight, 500); + }, 50)); + + this.showFullInfo.subscribe(function () { + Utils.windowResize(); + Utils.windowResize(250); + }); + + this.oHeaderDom = $('.messageItemHeader', oDom); + this.oHeaderDom = this.oHeaderDom[0] ? this.oHeaderDom : null; + + this.pswpDom = $('.pswp', oDom)[0]; + + if (this.pswpDom) + { + oDom + .on('click', '.attachmentImagePreview[data-index]', function (oEvent) { + + var + oPs = null, + oEl = oEvent.currentTarget || null, + aItems = [] +// fThumbBoundsFn = function (index) { +// var oRes = null, oEl = aItems[index], oPos = null; +// if (oEl && oEl.el) +// { +// oPos = oEl.el.find('.iconBG').offset(); +// oRes = oPos && oPos.top && oPos.left ? +// {x: oPos.left, y: oPos.top, w: 60} : null; +// } +// +// return oRes; +// } + ; + + oDom.find('.attachmentImagePreview[data-index]').each(function (index, oSubElement) { + + var + $oItem = $(oSubElement) + ; + + aItems.push({ +// 'el': $oItem, + 'w': 600, 'h': 400, + 'src': $oItem.attr('href'), + 'title': $oItem.attr('title') || '' + }); + }); + + if (aItems && 0 < aItems.length) + { + Globals.useKeyboardShortcuts(false); + + oPs = new PhotoSwipe(self.pswpDom, PhotoSwipeUI_Default, aItems, { + 'index': Utils.pInt($(oEl).data('index')), + 'bgOpacity': 0.85, + 'loadingIndicatorDelay': 500, + 'errorMsg': '
' + sErrorMessage + '
', + 'showHideOpacity': true, + 'tapToToggleControls': false, +// 'getThumbBoundsFn': fThumbBoundsFn, + 'timeToIdle': 0, + 'timeToIdleOutside': 0, + 'history': false, + 'arrowEl': 1 < aItems.length, + 'counterEl': 1 < aItems.length, + 'shareEl': false + }); + + oPs.listen('imageLoadComplete', function(index, item) { + if (item && item.img && item.img.width && item.img.height) + { + item.w = item.img.width; + item.h = item.img.height; + + oPs.updateSize(true); + } + }); + + oPs.listen('close', function() { + Globals.useKeyboardShortcuts(true); + }); + + oPs.init(); + } + + return false; + }); + } + + oDom + .on('click', 'a', function (oEvent) { + // setup maito protocol + return !(!!oEvent && 3 !== oEvent['which'] && Utils.mailToHelper($(this).attr('href'), require('View/Popup/Compose'))); + }) + .on('click', '.attachmentsPlace .attachmentIconParent', function (oEvent) { + if (oEvent && oEvent.stopPropagation) + { + oEvent.stopPropagation(); + } + }) + .on('click', '.attachmentsPlace .showPreplay', function (oEvent) { + if (oEvent && oEvent.stopPropagation) + { + oEvent.stopPropagation(); + } + + var oAttachment = ko.dataFor(this); + if (oAttachment && Audio.supported) + { + switch (true) + { + case Audio.supportedMp3 && oAttachment.isMp3(): + Audio.playMp3(oAttachment.linkDownload(), oAttachment.fileName); + break; + case Audio.supportedOgg && oAttachment.isOgg(): + Audio.playOgg(oAttachment.linkDownload(), oAttachment.fileName); + break; + case Audio.supportedWav && oAttachment.isWav(): + Audio.playWav(oAttachment.linkDownload(), oAttachment.fileName); + break; + } + } + }) + .on('click', '.thread-list .more-threads', function (e) { + + var oLast = null; + if (!e || 0 === e.clientX) // probably enter + { + // It's a bad bad hack :( + oLast = $('.thread-list .e-item.thread-list-message.real-msg.more-that:first a.e-link', oDom); + } + + self.viewThreadMessages.showMore(true); + self.threadsDropdownTrigger(true); + + if (oLast && oLast[0]) + { + oLast.focus(); + } + + return false; + }) + .on('click', '.thread-list .thread-list-message', function () { + var oMessage = ko.dataFor(this); + if (oMessage && oMessage.folder && oMessage.uid) + { + self.openThreadMessage(oMessage.uid); + } + }) + .on('click', '.attachmentsPlace .attachmentItem .attachmentNameParent', function () { + + var + oAttachment = ko.dataFor(this) + ; + + if (oAttachment && oAttachment.download) + { + require('App/User').download(oAttachment.linkDownload()); + } + }) + .on('click', '.messageItemHeader .subjectParent .flagParent', function () { + var oMessage = self.message(); + if (oMessage) + { + require('App/User').messageListAction(oMessage.folderFullNameRaw, oMessage.uid, + oMessage.flagged() ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, [oMessage]); + } + }) + .on('click', '.thread-list .flagParent', function () { + var oMessage = ko.dataFor(this); + if (oMessage && oMessage.folder && oMessage.uid) + { + require('App/User').messageListAction( + oMessage.folder, oMessage.uid, + oMessage.flagged() ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, [oMessage]); + } + + self.threadsDropdownTrigger(true); + + return false; + }) + ; + + AppStore.focusedState.subscribe(function (sValue) { + if (Enums.Focused.MessageView !== sValue) + { + this.scrollMessageToTop(); + this.scrollMessageToLeft(); + } + }, this); + + Globals.keyScopeReal.subscribe(function (sValue) { + this.messageDomFocused(Enums.KeyState.MessageView === sValue && !Utils.inFocus()); + }, this); + + this.oMessageScrollerDom = oDom.find('.messageItem .content'); + this.oMessageScrollerDom = this.oMessageScrollerDom && this.oMessageScrollerDom[0] ? this.oMessageScrollerDom : null; + + this.initShortcuts(); + }; + + MessageViewMailBoxUserView.prototype.selectSelectedThreadMessage = function () + { + var self = this; + _.delay(function () { + var oLast = self.oDom ? $('.thread-list .e-item.thread-list-message.real-msg.selected a.e-link', self.oDom) : null; + if (oLast && oLast[0]) + { + oLast.focus(); + } + }, 30); + }; + + /** + * @return {boolean} + */ + MessageViewMailBoxUserView.prototype.escShortcuts = function () + { + if (this.viewModelVisibility() && this.message()) + { + if (this.fullScreenMode()) + { + this.fullScreenMode(false); + + if (Enums.Layout.NoPreview !== this.layout()) + { + AppStore.focusedState(Enums.Focused.MessageList); + } + } + else if (Enums.Layout.NoPreview === this.layout()) + { + this.message(null); + } + else + { + AppStore.focusedState(Enums.Focused.MessageList); + } + + return false; + } + }; + + MessageViewMailBoxUserView.prototype.initShortcuts = function () + { + var + self = this + ; + + // exit fullscreen, back + key('esc, backspace', Enums.KeyState.MessageView, _.bind(this.escShortcuts, this)); + + // fullscreen + key('enter', Enums.KeyState.MessageView, function () { + self.toggleFullScreen(); + return false; + }); + + // reply + key('r', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + if (MessageStore.message()) + { + self.replyCommand(); + return false; + } + }); + + // replaAll + key('a', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + if (MessageStore.message()) + { + self.replyAllCommand(); + return false; + } + }); + + // forward + key('f', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + if (MessageStore.message()) + { + self.forwardCommand(); + return false; + } + }); + + // message information + // key('i', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + // if (MessageStore.message()) + // { + // self.showFullInfo(!self.showFullInfo()); + // return false; + // } + // }); + + // toggle message blockquotes + key('b', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + if (MessageStore.message() && MessageStore.message().body) + { + MessageStore.message().body.find('.rlBlockquoteSwitcher').click(); + return false; + } + }); + + key('t', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + if (MessageStore.message() && self.viewThreadsControlVisibility()) + { + self.threadsDropdownTrigger(true); + self.threadListCommand(); + return false; + } + }); + + key('ctrl+up, command+up', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + self.goUpCommand(); + return false; + }); + + key('ctrl+down, command+down', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + self.goDownCommand(); + return false; + }); + + key('ctrl+left, command+left', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + self.threadForwardCommand(); + return false; + }); + + key('ctrl+right, command+right', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { + self.threadBackCommand(); + return false; + }); + + // print + key('ctrl+p, command+p', Enums.KeyState.MessageView, function () { + if (self.message()) + { + self.message().printMessage(); + } + + return false; + }); + + // delete + key('delete, shift+delete', Enums.KeyState.MessageView, function (event, handler) { + if (event) + { + if (handler && 'shift+delete' === handler.shortcut) + { + self.deleteWithoutMoveCommand(); + } + else + { + self.deleteCommand(); + } + + return false; + } + }); + + // change focused state + key('tab, shift+tab, left', Enums.KeyState.MessageView, function (event, handler) { + if (!self.fullScreenMode() && self.message() && Enums.Layout.NoPreview !== self.layout()) + { + if (event && handler && 'left' === handler.shortcut) + { + if (self.oMessageScrollerDom && 0 < self.oMessageScrollerDom.scrollLeft()) + { + return true; + } + + AppStore.focusedState(Enums.Focused.MessageList); + } + else + { + AppStore.focusedState(Enums.Focused.MessageList); + } + } + else if (self.message() && Enums.Layout.NoPreview === self.layout() && event && handler && 'left' === handler.shortcut) + { + return true; + } + + return false; + }); + }; + + /** + * @return {boolean} + */ + MessageViewMailBoxUserView.prototype.isDraftFolder = function () + { + return MessageStore.message() && FolderStore.draftFolder() === MessageStore.message().folderFullNameRaw; + }; + + /** + * @return {boolean} + */ + MessageViewMailBoxUserView.prototype.isSentFolder = function () + { + return MessageStore.message() && FolderStore.sentFolder() === MessageStore.message().folderFullNameRaw; + }; + + /** + * @return {boolean} + */ + MessageViewMailBoxUserView.prototype.isSpamFolder = function () + { + return MessageStore.message() && FolderStore.spamFolder() === MessageStore.message().folderFullNameRaw; + }; + + /** + * @return {boolean} + */ + MessageViewMailBoxUserView.prototype.isSpamDisabled = function () + { + return MessageStore.message() && FolderStore.spamFolder() === Consts.Values.UnuseOptionValue; + }; + + /** + * @return {boolean} + */ + MessageViewMailBoxUserView.prototype.isArchiveFolder = function () + { + return MessageStore.message() && FolderStore.archiveFolder() === MessageStore.message().folderFullNameRaw; + }; + + /** + * @return {boolean} + */ + MessageViewMailBoxUserView.prototype.isArchiveDisabled = function () + { + return MessageStore.message() && FolderStore.archiveFolder() === Consts.Values.UnuseOptionValue; + }; + + /** + * @return {boolean} + */ + MessageViewMailBoxUserView.prototype.isDraftOrSentFolder = function () + { + return this.isDraftFolder() || this.isSentFolder(); + }; + + MessageViewMailBoxUserView.prototype.composeClick = function () + { + kn.showScreenPopup(require('View/Popup/Compose')); + }; + + MessageViewMailBoxUserView.prototype.editMessage = function () + { + if (MessageStore.message()) + { + kn.showScreenPopup(require('View/Popup/Compose'), [Enums.ComposeType.Draft, MessageStore.message()]); + } + }; + + MessageViewMailBoxUserView.prototype.scrollMessageToTop = function () + { + if (this.oMessageScrollerDom) + { + if (50 < this.oMessageScrollerDom.scrollTop()) + { + this.oMessageScrollerDom + .scrollTop(50) + .animate({'scrollTop': 0}, 200) + ; + } + else + { + this.oMessageScrollerDom.scrollTop(0); + } + + Utils.windowResize(); + } + }; + + MessageViewMailBoxUserView.prototype.scrollMessageToLeft = function () + { + if (this.oMessageScrollerDom) + { + this.oMessageScrollerDom.scrollLeft(0); + Utils.windowResize(); + } + }; + + MessageViewMailBoxUserView.prototype.getAttachmentsHashes = function () + { + return _.compact(_.map(this.message() ? this.message().attachments() : [], function (oItem) { + return oItem && oItem.checked() ? oItem.download : ''; + })); + }; + + MessageViewMailBoxUserView.prototype.downloadAsZip = function () + { + var aHashes = this.getAttachmentsHashes(); + if (0 < aHashes.length) + { + Promises.attachmentsActions('Zip', aHashes, this.downloadAsZipLoading); + } + }; + + MessageViewMailBoxUserView.prototype.saveToOwnCloud = function () + { + var aHashes = this.getAttachmentsHashes(); + if (0 < aHashes.length) + { + Promises.attachmentsActions('OwnCloud', aHashes, this.saveToOwnCloudLoading); + } + }; + + MessageViewMailBoxUserView.prototype.saveToDropbox = function () + { + var aHashes = this.getAttachmentsHashes(); + if (0 < aHashes.length) + { + Promises.attachmentsActions('Dropbox', aHashes, this.saveToDropboxLoading); + } + }; + + /** + * @param {MessageModel} oMessage + */ + MessageViewMailBoxUserView.prototype.showImages = function (oMessage) + { + if (oMessage && oMessage.showExternalImages) + { + oMessage.showExternalImages(true); + } + }; + + /** + * @return {string} + */ + MessageViewMailBoxUserView.prototype.printableCheckedMessageCount = function () + { + var iCnt = this.messageListCheckedOrSelectedUidsWithSubMails().length; + return 0 < iCnt ? (100 > iCnt ? iCnt : '99+') : ''; + }; + + + /** + * @param {MessageModel} oMessage + */ + MessageViewMailBoxUserView.prototype.verifyPgpSignedClearMessage = function (oMessage) + { + if (oMessage) + { + oMessage.verifyPgpSignedClearMessage(); + } + }; + + /** + * @param {MessageModel} oMessage + */ + MessageViewMailBoxUserView.prototype.decryptPgpEncryptedMessage = function (oMessage) + { + if (oMessage) + { + oMessage.decryptPgpEncryptedMessage(this.viewPgpPassword()); + } + }; + + /** + * @param {MessageModel} oMessage + */ + MessageViewMailBoxUserView.prototype.readReceipt = function (oMessage) + { + if (oMessage && '' !== oMessage.readReceipt()) + { + Remote.sendReadReceiptMessage(Utils.emptyFunction, oMessage.folderFullNameRaw, oMessage.uid, + oMessage.readReceipt(), + Translator.i18n('READ_RECEIPT/SUBJECT', {'SUBJECT': oMessage.subject()}), + Translator.i18n('READ_RECEIPT/BODY', {'READ-RECEIPT': AccountStore.email()})); + + oMessage.isReadReceipt(true); + + Cache.storeMessageFlagsToCache(oMessage); + + require('App/User').reloadFlagsCurrentMessageListAndMessageFromCache(); + } + }; + + module.exports = MessageViewMailBoxUserView; + +}()); \ No newline at end of file diff --git a/dev/View/User/MailBox/SystemDropDown.js b/dev/View/User/MailBox/SystemDropDown.js new file mode 100644 index 000000000..bf0811c93 --- /dev/null +++ b/dev/View/User/MailBox/SystemDropDown.js @@ -0,0 +1,28 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + kn = require('Knoin/Knoin'), + AbstractSystemDropDownViewModel = require('View/User/AbstractSystemDropDown') + ; + + /** + * @constructor + * @extends AbstractSystemDropDownViewModel + */ + function SystemDropDownMailBoxUserView() + { + AbstractSystemDropDownViewModel.call(this); + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/User/MailBox/SystemDropDown', 'View/App/MailBox/SystemDropDown', 'MailBoxSystemDropDownViewModel'], SystemDropDownMailBoxUserView); + _.extend(SystemDropDownMailBoxUserView.prototype, AbstractSystemDropDownViewModel.prototype); + + module.exports = SystemDropDownMailBoxUserView; + +}()); diff --git a/dev/View/User/Settings/Menu.js b/dev/View/User/Settings/Menu.js new file mode 100644 index 000000000..e642fdeb2 --- /dev/null +++ b/dev/View/User/Settings/Menu.js @@ -0,0 +1,90 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + key = require('key'), + + Enums = require('Common/Enums'), + Globals = require('Common/Globals'), + Links = require('Common/Links'), + + Cache = require('Common/Cache'), + + kn = require('Knoin/Knoin'), + AbstractView = require('Knoin/AbstractView') + ; + + /** + * @param {?} oScreen + * + * @constructor + * @extends AbstractView + */ + function MenuSettingsUserView(oScreen) + { + AbstractView.call(this, 'Left', 'SettingsMenu'); + + this.leftPanelDisabled = Globals.leftPanelDisabled; + + this.menu = oScreen.menu; + + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/User/Settings/Menu', 'View/App/Settings/Menu', 'SettingsMenuViewModel'], MenuSettingsUserView); + _.extend(MenuSettingsUserView.prototype, AbstractView.prototype); + + MenuSettingsUserView.prototype.onBuild = function (oDom) + { +// var self = this; +// key('esc', Enums.KeyState.Settings, function () { +// self.backToMailBoxClick(); +// }); + + key('up, down', Enums.KeyState.Settings, _.throttle(function (event, handler) { + + var + sH = '', + iIndex = -1, + bUp = handler && 'up' === handler.shortcut, + $items = $('.b-settings-menu .e-item', oDom) + ; + + if (event && $items.length) + { + iIndex = $items.index($items.filter('.selected')); + if (bUp && iIndex > 0) + { + iIndex--; + } + else if (!bUp && iIndex < $items.length - 1) + { + iIndex++; + } + + sH = $items.eq(iIndex).attr('href'); + if (sH) + { + kn.setHash(sH, false, true); + } + } + + }, 200)); + }; + + MenuSettingsUserView.prototype.link = function (sRoute) + { + return Links.settings(sRoute); + }; + + MenuSettingsUserView.prototype.backToMailBoxClick = function () + { + kn.setHash(Links.inbox(Cache.getFolderInboxName())); + }; + + module.exports = MenuSettingsUserView; + +}()); \ No newline at end of file diff --git a/dev/View/User/Settings/Pane.js b/dev/View/User/Settings/Pane.js new file mode 100644 index 000000000..dff7f0fe3 --- /dev/null +++ b/dev/View/User/Settings/Pane.js @@ -0,0 +1,40 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + kn = require('Knoin/Knoin'), + AbstractView = require('Knoin/AbstractView') + ; + + /** + * @constructor + * @extends AbstractView + */ + function PaneSettingsUserView() + { + AbstractView.call(this, 'Right', 'SettingsPane'); + + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/User/Settings/Pane', 'View/App/Settings/Pane', 'SettingsPaneViewModel'], PaneSettingsUserView); + _.extend(PaneSettingsUserView.prototype, AbstractView.prototype); + + PaneSettingsUserView.prototype.onShow = function () + { + require('Stores/User/Message').message(null); + }; + + PaneSettingsUserView.prototype.backToMailBoxClick = function () + { + kn.setHash(require('Common/Links').inbox( + require('Common/Cache').getFolderInboxName())); + }; + + module.exports = PaneSettingsUserView; + +}()); \ No newline at end of file diff --git a/dev/View/User/Settings/SystemDropDown.js b/dev/View/User/Settings/SystemDropDown.js new file mode 100644 index 000000000..d612c2688 --- /dev/null +++ b/dev/View/User/Settings/SystemDropDown.js @@ -0,0 +1,28 @@ + +(function () { + + 'use strict'; + + var + _ = require('_'), + + kn = require('Knoin/Knoin'), + AbstractSystemDropDownUserView = require('View/User/AbstractSystemDropDown') + ; + + /** + * @constructor + * @extends AbstractSystemDropDownUserView + */ + function SystemDropDownSettingsUserView() + { + AbstractSystemDropDownUserView.call(this); + kn.constructorEnd(this); + } + + kn.extendAsViewModel(['View/User/Settings/SystemDropDown', 'View/App/Settings/SystemDropDown', 'SettingsSystemDropDownViewModel'], SystemDropDownSettingsUserView); + _.extend(SystemDropDownSettingsUserView.prototype, AbstractSystemDropDownUserView.prototype); + + module.exports = SystemDropDownSettingsUserView; + +}()); \ No newline at end of file diff --git a/dev/admin.js b/dev/admin.js index 41533dd74..a2b208559 100644 --- a/dev/admin.js +++ b/dev/admin.js @@ -1 +1 @@ -require('App/Boot')(require('App/Admin')); \ No newline at end of file +require('bootstrap')(require('App/Admin')); \ No newline at end of file diff --git a/dev/app.js b/dev/app.js index 10451577b..a02273f58 100644 --- a/dev/app.js +++ b/dev/app.js @@ -1 +1 @@ -require('App/Boot')(require('App/App')); \ No newline at end of file +require('bootstrap')(require('App/User')); \ No newline at end of file diff --git a/dev/App/Boot.js b/dev/bootstrap.js similarity index 75% rename from dev/App/Boot.js rename to dev/bootstrap.js index eec1bff40..d88505169 100644 --- a/dev/App/Boot.js +++ b/dev/bootstrap.js @@ -14,6 +14,7 @@ Plugins = require('Common/Plugins'), Utils = require('Common/Utils'), Enums = require('Common/Enums'), + Translator = require('Common/Translator'), EmailModel = require('Model/Email') ; @@ -21,8 +22,8 @@ Globals.__APP__ = App; Globals.$win - .keydown(Utils.killCtrlAandS) - .keyup(Utils.killCtrlAandS) + .keydown(Utils.kill_CtrlA_CtrlS) + .keyup(Utils.kill_CtrlA_CtrlS) .unload(function () { Globals.bUnload = true; }) @@ -37,18 +38,28 @@ // export window['rl'] = window['rl'] || {}; + window['rl']['i18n'] = _.bind(Translator.i18n, Translator); + window['rl']['addHook'] = _.bind(Plugins.addHook, Plugins); window['rl']['settingsGet'] = _.bind(Plugins.mainSettingsGet, Plugins); - window['rl']['remoteRequest'] = _.bind(Plugins.remoteRequest, Plugins); - window['rl']['pluginSettingsGet'] = _.bind(Plugins.settingsGet, Plugins); window['rl']['createCommand'] = Utils.createCommand; + window['rl']['addSettingsViewModel'] = _.bind(Plugins.addSettingsViewModel, Plugins); + + window['rl']['pluginRemoteRequest'] = _.bind(Plugins.remoteRequest, Plugins); + window['rl']['pluginSettingsGet'] = _.bind(Plugins.settingsGet, Plugins); + window['rl']['EmailModel'] = EmailModel; window['rl']['Enums'] = Enums; window['__APP_BOOT'] = function (fCall) { - $(function () { + $(_.delay(function () { + + if (!$('#rl-content').is(':visible')) + { + Globals.$html.addClass('no-css'); + } if (window['rainloopTEMPLATES'] && window['rainloopTEMPLATES'][0]) { @@ -57,7 +68,7 @@ _.delay(function () { App.bootstart(); - + Globals.$html .removeClass('no-js rl-booted-trigger') .addClass('rl-booted') @@ -71,7 +82,8 @@ } window['__APP_BOOT'] = null; - }); + + }, 10)); }; }; diff --git a/gulpfile.js b/gulpfile.js index 6046ba05a..4612b4f6d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,6 +7,7 @@ var devVersion: '0.0.0', releasesPath: 'build/dist/releases', + rainloopBuilded: false, destPath: '', cleanPath: '', zipSrcPath: '', @@ -29,11 +30,12 @@ var gulp = require('gulp'), concat = require('gulp-concat-util'), header = require('gulp-header'), - footer = require('gulp-footer'), eol = require('gulp-eol'), + stripbom = require('gulp-stripbom'), rename = require('gulp-rename'), replace = require('gulp-replace'), uglify = require('gulp-uglify'), + plumber = require('gulp-plumber'), gutil = require('gulp-util') ; @@ -94,10 +96,12 @@ cfg.paths.css = { 'vendors/normalize/normalize.css', 'vendors/fontastic/styles.css', 'vendors/jquery-nanoscroller/nanoscroller.css', - 'vendors/jquery-magnific-popup/magnific-popup.css', - 'vendors/jquery-magnific-popup/magnific-popup-animations.css', + 'vendors/jquery-letterfx/jquery-letterfx.min.css', 'vendors/simple-pace/styles.css', 'vendors/inputosaurus/inputosaurus.css', + 'vendors/opentip/opentip.css', + 'vendors/photoswipe/photoswipe.css', + 'vendors/photoswipe/default-skin/default-skin.css', 'vendors/flags/flags-fixed.css', cfg.paths.staticCSS + cfg.paths.less.main.name ] @@ -111,7 +115,7 @@ cfg.paths.js = { 'vendors/json2.min.js', 'vendors/labjs/LAB.min.js', 'vendors/simple-pace/simple-pace-1.0.min.js', - 'vendors/rl/rl-1.2.min.js' + 'vendors/rl/rl-1.5.min.js' ] }, openpgp: { @@ -138,8 +142,10 @@ cfg.paths.js = { src: [ 'vendors/modernizr.js', 'vendors/underscore/1.6.0/underscore-min.js', - 'vendors/jquery/jquery-1.11.1.min.js', + 'vendors/is.js/is.min.js', + 'vendors/jquery/jquery-1.11.2.min.js', 'vendors/jquery-ui/js/jquery-ui-1.10.3.custom.min.js', +// 'vendors/jquery-ui.touch-punch/jquery.ui.touch-punch.min.js', 'vendors/jquery-cookie/jquery.cookie-1.4.0.min.js', 'vendors/jquery-finger/jquery.finger.min.js', 'vendors/jquery-mousewheel/jquery.mousewheel-3.1.4.min.js', @@ -147,20 +153,29 @@ cfg.paths.js = { 'vendors/jquery-lazyload/jquery.lazyload.min.js', 'vendors/jquery-nanoscroller/jquery.nanoscroller-0.7.min.js', 'vendors/jquery-wakeup/jquery.wakeup.min.js', + 'vendors/jquery-letterfx/jquery-letterfx.min.js', + 'vendors/jquery-backstretch/jquery.backstretch.min.js', + 'vendors/queue/queue.min.js', 'vendors/inputosaurus/inputosaurus.min.js', 'vendors/moment/min/moment.min.js ', + 'vendors/tinycon/tinycon.min.js ', 'vendors/routes/signals.min.js', 'vendors/routes/hasher.min.js', 'vendors/routes/crossroads.min.js', - 'vendors/knockout/knockout-3.2.0.js', + 'vendors/knockout/knockout-3.3.0.js', +// 'vendors/knockout-punches/knockout.punches.min.js', 'vendors/knockout-projections/knockout-projections-1.0.0.min.js', + 'vendors/knockout-sortable/knockout-sortable.min.js', 'vendors/ssm/ssm.min.js', 'vendors/jua/jua.min.js', + 'vendors/Q/q.min.js', + 'vendors/opentip/opentip-jquery.min.js', 'vendors/Autolinker/Autolinker.min.js', - 'vendors/jsencrypt/jsencrypt.min.js', + 'vendors/photoswipe/photoswipe.min.js', + 'vendors/photoswipe/photoswipe-ui-default.min.js', +// 'vendors/jsencrypt/jsencrypt.min.js', 'vendors/keymaster/keymaster.min.js', 'vendors/ifvisible/ifvisible.min.js', - 'vendors/jquery-magnific-popup/jquery.magnific-popup.min.js', 'vendors/bootstrap/js/bootstrap.min.js' ] }, @@ -181,21 +196,23 @@ gulp.task('less:main', function() { })) .pipe(rename(cfg.paths.less.main.name)) .pipe(eol('\n', true)) - .pipe(gulp.dest(cfg.paths.staticCSS)); + .pipe(gulp.dest(cfg.paths.staticCSS)) + .on('error', gutil.log); }); -gulp.task('css:main', ['less:main'], function() { +gulp.task('css:main-begin', ['less:main'], function() { var // csslint = require('gulp-csslint'), - csscomb = require('gulp-csscomb'), +// csscomb = require('gulp-csscomb'), autoprefixer = require('gulp-autoprefixer') ; return gulp.src(cfg.paths.css.main.src) + .pipe(plumber()) .pipe(concat(cfg.paths.css.main.name)) .pipe(autoprefixer('last 3 versions', '> 1%', 'ie 9', 'Firefox ESR', 'Opera 12.1')) - .pipe(csscomb()) +// .pipe(csscomb()) // .pipe(csslint()) // .pipe(csslint.reporter()) .pipe(eol('\n', true)) @@ -203,9 +220,18 @@ gulp.task('css:main', ['less:main'], function() { ; }); +gulp.task('css:clear-less', ['css:main-begin'], function() { + + return gulp.src(cfg.paths.staticCSS + cfg.paths.less.main.name, {read: false}) + .pipe(require('gulp-rimraf')()); +}); + +gulp.task('css:main', ['css:clear-less']); + gulp.task('css:main:min', ['css:main'], function() { var minifyCss = require('gulp-minify-css'); return gulp.src(cfg.paths.staticCSS + cfg.paths.css.main.name) + .pipe(plumber()) .pipe(minifyCss({ 'keepSpecialComments': 0 })) @@ -225,8 +251,8 @@ gulp.task('js:boot', function() { gulp.task('js:encrypt', function() { return gulp.src(cfg.paths.js.encrypt.src) .pipe(concat(cfg.paths.js.encrypt.name)) - .pipe(header(cfg.paths.js.encrypt.header || '')) - .pipe(footer(cfg.paths.js.encrypt.footer || '')) + .pipe(concat.header(cfg.paths.js.encrypt.header || '')) + .pipe(concat.footer(cfg.paths.js.encrypt.footer || '')) .pipe(uglify(cfg.uglify)) .pipe(eol('\n', true)) .pipe(gulp.dest(cfg.paths.js.encrypt.dest)) @@ -259,7 +285,7 @@ gulp.task('js:ckeditor:beautify', function() { }); gulp.task('js:webpack:clear', function() { - return gulp.src([cfg.paths.staticJS + '*.chunk.js', cfg.paths.staticMinJS + '*.chunk.js'], {read: false}) + return gulp.src([cfg.paths.staticJS + '*.subapp.js', cfg.paths.staticMinJS + '*.subapp.js'], {read: false}) .pipe(require('gulp-rimraf')()); }); @@ -300,7 +326,7 @@ gulp.task('js:admin', ['js:webpack'], function() { }); gulp.task('js:chunks', ['js:webpack'], function() { - return gulp.src(cfg.paths.staticJS + '*.chunk.js') + return gulp.src(cfg.paths.staticJS + '*.subapp.js') .pipe(header('/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */\n')) .pipe(eol('\n', true)) .pipe(gulp.dest(cfg.paths.staticJS)) @@ -366,13 +392,44 @@ regOtherMinTask('other:inputosaurus', 'vendors/inputosaurus/', 'inputosaurus.js' regOtherMinTask('other:pace', 'vendors/simple-pace/', 'simple-pace.js', 'simple-pace-1.0.min.js', '/*! RainLoop Simple Pace v1.0 (c) 2014 RainLoop Team; Licensed under MIT */\n'); -regOtherMinTask('other:rl', 'vendors/rl/', 'rl.js', 'rl-1.2.min.js', - '/*! RainLoop Index Helper v1.2 (c) 2014 RainLoop Team; Licensed under MIT */\n'); +regOtherMinTask('other:rl', 'vendors/rl/', 'rl.js', 'rl-1.5.min.js', + '/*! RainLoop Index Helper v1.5 (c) 2015 RainLoop Team; Licensed under MIT */\n'); -gulp.task('package-inc-release', function() { - fs.writeFileSync('package.json', - fs.readFileSync('package.json', 'utf8').replace(/"release":\s?"[\d]+",/, '"release": "' + - (1 + parseInt(pkg.release, 10)) + '",')); +regOtherMinTask('other:q', 'vendors/Q/', 'q.js', 'q.min.js', + '/*! (c) 2009-2012 Kris Kowal Licensed under MIT */\n'); + +gulp.task('fontastic-fonts:clear', function() { + return cleanDir('rainloop/v/' + cfg.devVersion + '/static/css/fonts/rainloop.*'); +}); + +gulp.task('fontastic-fonts:copy', ['fontastic-fonts:clear'], function() { + return gulp.src('vendors/fontastic/fonts/rainloop.*') + .pipe(gulp.dest('rainloop/v/' + cfg.devVersion + '/static/css/fonts')); +}); + +gulp.task('fontastic', ['fontastic-fonts:copy']); + +gulp.task('ckeditor:clear', function() { + return cleanDir('rainloop/v/' + cfg.devVersion + '/static/ckeditor'); +}); + +gulp.task('ckeditor:copy', ['ckeditor:clear'], function() { + return gulp.src('vendors/ckeditor/**/*') + .pipe(gulp.dest('rainloop/v/' + cfg.devVersion + '/static/ckeditor')); +}); + +gulp.task('ckeditor:copy-plugins', ['ckeditor:copy'], function() { + return gulp.src('vendors/ckeditor-plugins/**/*') + .pipe(gulp.dest('rainloop/v/' + cfg.devVersion + '/static/ckeditor/plugins')); +}); + +gulp.task('ckeditor', ['ckeditor:copy-plugins'], function () { + return gulp.src('rainloop/v/' + cfg.devVersion + '/static/ckeditor/*.js') + .pipe(stripbom()) +// .pipe(replace("\u200B", "\\u200B")) + .pipe(replace('console.log("Detecting changes using MutationObservers")', 'true')) + .pipe(header("\uFEFF")) // BOM + .pipe(gulp.dest('rainloop/v/' + cfg.devVersion + '/static/ckeditor')); }); // BUILD (RainLoop) @@ -406,13 +463,13 @@ gulp.task('rainloop:setup', ['rainloop:copy'], function() { fs.writeFileSync(dist + 'rainloop/v/' + versionFull + '/index.php.root', fs.readFileSync(dist + 'index.php')); - fs.unlinkSync(dist + 'rainloop/v/' + versionFull + '/static/css/less.css'); - cfg.destPath = cfg.releasesPath + '/webmail/' + versionFull + '/'; cfg.cleanPath = dist; cfg.zipSrcPath = dist; cfg.zipFile = 'rainloop-' + versionFull + '.zip'; cfg.md5File = cfg.zipFile; + + cfg.rainloopBuilded = true; }); gulp.task('rainloop:zip', ['rainloop:copy', 'rainloop:setup'], function() { @@ -444,7 +501,28 @@ gulp.task('rainloop:owncloud:copy', function() { .pipe(gulp.dest(dist + 'rainloop')); }); -gulp.task('rainloop:owncloud:setup', ['rainloop:owncloud:copy'], function() { +gulp.task('rainloop:owncloud:copy-rainloop', ['rainloop:start', 'rainloop:owncloud:copy'], function() { + + var + versionFull = pkg.ownCloudPackageVersion, + dist = cfg.releasesPath + '/owncloud/' + versionFull + '/src/rainloop/' + ; + + if (cfg.rainloopBuilded && cfg.destPath) + { + return gulp.src(cfg.destPath + '/src/**/*', {base: cfg.destPath + '/src/'}) + .pipe(gulp.dest(dist + 'app/')); + } + + return true; +}); + +gulp.task('rainloop:owncloud:copy-rainloop:clean', ['rainloop:owncloud:copy-rainloop'], function() { + return (cfg.cleanPath) ? cleanDir(cfg.cleanPath) : false; +}); + +gulp.task('rainloop:owncloud:setup', ['rainloop:owncloud:copy', + 'rainloop:owncloud:copy-rainloop'], function() { var versionFull = pkg.ownCloudPackageVersion, @@ -480,13 +558,15 @@ gulp.task('rainloop:owncloud:clean', ['rainloop:owncloud:copy', 'rainloop:ownclo }); // MAIN -gulp.task('default', ['js:libs', 'js:boot', 'js:openpgp', 'js:min', 'css:main:min']); +gulp.task('default', ['js:libs', 'js:boot', 'js:openpgp', 'js:min', 'css:main:min', 'ckeditor', 'fontastic']); gulp.task('fast', ['js:app', 'js:admin', 'js:chunks', 'css:main']); -gulp.task('rainloop', ['js:lint', 'rainloop:copy', 'rainloop:setup', 'rainloop:zip', 'rainloop:md5', 'rainloop:clean']); -gulp.task('rainloop+', ['rainloop', 'package-inc-release']); +gulp.task('rainloop:start', ['js:lint', 'rainloop:copy', 'rainloop:setup']); +gulp.task('rainloop', ['rainloop:start', 'rainloop:zip', 'rainloop:md5', 'rainloop:clean']); -gulp.task('owncloud', ['rainloop:owncloud:copy', 'rainloop:owncloud:setup', 'rainloop:owncloud:zip', 'rainloop:owncloud:md5', 'rainloop:owncloud:clean']); +gulp.task('owncloud', ['rainloop:owncloud:copy', + 'rainloop:owncloud:copy-rainloop', 'rainloop:owncloud:copy-rainloop:clean', + 'rainloop:owncloud:setup', 'rainloop:owncloud:zip', 'rainloop:owncloud:md5', 'rainloop:owncloud:clean']); //WATCH gulp.task('watch', ['fast'], function() { @@ -494,15 +574,14 @@ gulp.task('watch', ['fast'], function() { gulp.watch(cfg.paths.less.main.watch, {interval: 500}, ['css:main']); }); -// aliases +// ALIASES +gulp.task('build', ['rainloop']); +gulp.task('js:hint', ['js:lint']); + +gulp.task('own', ['owncloud']); +gulp.task('rl', ['rainloop']); gulp.task('w', ['watch']); gulp.task('f', ['fast']); - -gulp.task('rl', ['rainloop']); -gulp.task('rl+', ['rainloop+']); -gulp.task('build', ['rainloop']); -gulp.task('build+', ['rainloop+']); -gulp.task('b', ['rainloop']); -gulp.task('b+', ['rainloop+']); - -gulp.task('own', ['owncloud']); \ No newline at end of file +gulp.task('o', ['owncloud']); +gulp.task('b', ['build']); +gulp.task('h', ['js:lint']); \ No newline at end of file diff --git a/index.php b/index.php index 7dd8734e7..3731a17e7 100644 --- a/index.php +++ b/index.php @@ -7,9 +7,9 @@ if (!defined('APP_VERSION')) define('APP_INDEX_ROOT_PATH', str_replace('\\', '/', rtrim(dirname(__FILE__), '\\/').'/')); } -if (file_exists(APP_INDEX_ROOT_PATH.'rainloop/v/'.APP_VERSION.'/index.php')) +if (file_exists(APP_INDEX_ROOT_PATH.'rainloop/v/'.APP_VERSION.'/include.php')) { - return include APP_INDEX_ROOT_PATH.'rainloop/v/'.APP_VERSION.'/index.php'; + include APP_INDEX_ROOT_PATH.'rainloop/v/'.APP_VERSION.'/include.php'; } else { diff --git a/package.json b/package.json index 2baf4e043..66ced8835 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "RainLoop", "title": "RainLoop Webmail", - "version": "1.6.9", - "release": "169", + "version": "1.8.3", + "release": "302", "description": "Simple, modern & fast web-based email client", "homepage": "http://rainloop.net", "main": "gulpfile.js", @@ -36,7 +36,7 @@ "plugins" ], "readmeFilename": "README.md", - "ownCloudPackageVersion": "2.2", + "ownCloudPackageVersion": "3.3", "engines": { "node": ">= 0.10.0" }, @@ -52,19 +52,24 @@ "gulp-uglify": "*", "gulp-rimraf": "*", "gulp-jshint": "*", - "gulp-less": "*", + "gulp-less": "1.3.6", "gulp-zip": "*", "gulp-rename": "*", "gulp-replace": "*", "gulp-header": "*", - "gulp-footer": "*", "gulp-eol": "*", + "gulp-stripbom": "*", "gulp-minify-css": "*", "gulp-autoprefixer": "*", "gulp-csscomb": "*", "gulp-closure-compiler": "*", "gulp-csslint": "*", "gulp-beautify": "*", + "gulp-plumber": "*", "gulp-concat-util": "*" + }, + "scripts": { + "gulp": "gulp", + "w": "gulp w" } } diff --git a/plugins/convert-headers-styles/CssToInlineStyles.php b/plugins/_depricated/convert-headers-styles/CssToInlineStyles.php similarity index 100% rename from plugins/convert-headers-styles/CssToInlineStyles.php rename to plugins/_depricated/convert-headers-styles/CssToInlineStyles.php diff --git a/plugins/convert-headers-styles/LICENSE b/plugins/_depricated/convert-headers-styles/LICENSE similarity index 100% rename from plugins/convert-headers-styles/LICENSE rename to plugins/_depricated/convert-headers-styles/LICENSE diff --git a/plugins/convert-headers-styles/README b/plugins/_depricated/convert-headers-styles/README similarity index 100% rename from plugins/convert-headers-styles/README rename to plugins/_depricated/convert-headers-styles/README diff --git a/plugins/convert-headers-styles/VERSION b/plugins/_depricated/convert-headers-styles/VERSION similarity index 100% rename from plugins/convert-headers-styles/VERSION rename to plugins/_depricated/convert-headers-styles/VERSION diff --git a/plugins/convert-headers-styles/index.php b/plugins/_depricated/convert-headers-styles/index.php similarity index 100% rename from plugins/convert-headers-styles/index.php rename to plugins/_depricated/convert-headers-styles/index.php diff --git a/plugins/_depricated/recaptcha/LICENSE b/plugins/_depricated/recaptcha/LICENSE new file mode 100644 index 000000000..271342337 --- /dev/null +++ b/plugins/_depricated/recaptcha/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 RainLoop Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/_depricated/recaptcha/README b/plugins/_depricated/recaptcha/README new file mode 100644 index 000000000..17e4ab032 --- /dev/null +++ b/plugins/_depricated/recaptcha/README @@ -0,0 +1,3 @@ +A CAPTCHA is a program that can generate and grade tests that humans can pass but current computer programs cannot. +For example, humans can read distorted text as the one shown below, but current computer programs can't. +More info at http://www.google.com/recaptcha \ No newline at end of file diff --git a/plugins/_depricated/recaptcha/VERSION b/plugins/_depricated/recaptcha/VERSION new file mode 100644 index 000000000..872765e5f --- /dev/null +++ b/plugins/_depricated/recaptcha/VERSION @@ -0,0 +1 @@ +1.9 \ No newline at end of file diff --git a/plugins/_depricated/recaptcha/index.php b/plugins/_depricated/recaptcha/index.php new file mode 100644 index 000000000..2875839f7 --- /dev/null +++ b/plugins/_depricated/recaptcha/index.php @@ -0,0 +1,139 @@ +UseLangs(true); + + $this->addJs('js/recaptcha.js'); + + $this->addHook('ajax.action-pre-call', 'AjaxActionPreCall'); + $this->addHook('filter.ajax-response', 'FilterAjaxResponse'); + + $this->addTemplate('templates/PluginLoginReCaptchaGroup.html'); + $this->addTemplateHook('Login', 'BottomControlGroup', 'PluginLoginReCaptchaGroup'); + } + + /** + * @return array + */ + public function configMapping() + { + return array( + \RainLoop\Plugins\Property::NewInstance('public_key')->SetLabel('Public Key') + ->SetAllowedInJs(true) + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('private_key')->SetLabel('Private Key') + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('error_limit')->SetLabel('Limit') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) + ->SetDefaultValue(array(0, 1, 2, 3, 4, 5)) + ->SetDescription('') + ); + } + + /** + * @return string + */ + private function getCaptchaCacherKey() + { + return 'Captcha/Login/'.\RainLoop\Utils::GetConnectionToken(); + } + + /** + * @return int + */ + private function getLimit() + { + $iConfigLimit = $this->Config()->Get('plugin', 'error_limit', 0); + if (0 < $iConfigLimit) + { + $oCacher = $this->Manager()->Actions()->Cacher(); + $sLimit = $oCacher && $oCacher->IsInited() ? $oCacher->Get($this->getCaptchaCacherKey()) : '0'; + + if (0 < strlen($sLimit) && is_numeric($sLimit)) + { + $iConfigLimit -= (int) $sLimit; + } + } + + return $iConfigLimit; + } + + /** + * @return void + */ + public function FilterAppDataPluginSection($bAdmin, $bAuth, &$aData) + { + if (!$bAdmin && !$bAuth && is_array($aData)) + { + $aData['show_captcha_on_login'] = 1 > $this->getLimit(); + } + } + + /** + * @param string $sAction + */ + public function AjaxActionPreCall($sAction) + { + if ('Login' === $sAction && 0 >= $this->getLimit()) + { + require_once __DIR__.'/recaptchalib.php'; + + $oResp = recaptcha_check_answer( + $this->Config()->Get('plugin', 'private_key', ''), + isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '', + $this->Manager()->Actions()->GetActionParam('RecaptchaChallenge', ''), + $this->Manager()->Actions()->GetActionParam('RecaptchaResponse', '') + ); + + if (!$oResp || !isset($oResp->is_valid) || !$oResp->is_valid) + { + $this->Manager()->Actions()->Logger()->WriteDump($oResp); + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CaptchaError); + } + } + } + + /** + * @param string $sAction + * @param array $aResponseItem + */ + public function FilterAjaxResponse($sAction, &$aResponseItem) + { + if ('Login' === $sAction && $aResponseItem && isset($aResponseItem['Result'])) + { + $oCacher = $this->Manager()->Actions()->Cacher(); + $iConfigLimit = (int) $this->Config()->Get('plugin', 'error_limit', 0); + $sKey = $this->getCaptchaCacherKey(); + + if (0 < $iConfigLimit && $oCacher && $oCacher->IsInited()) + { + if (false === $aResponseItem['Result']) + { + $iLimit = 0; + $sLimut = $oCacher->Get($sKey); + if (0 < strlen($sLimut) && is_numeric($sLimut)) + { + $iLimit = (int) $sLimut; + } + + $oCacher->Set($sKey, ++$iLimit); + + if ($iConfigLimit <= $iLimit) + { + $aResponseItem['Captcha'] = true; + } + } + else + { + $oCacher->Delete($sKey); + } + } + } + } +} diff --git a/plugins/_depricated/recaptcha/js/recaptcha.js b/plugins/_depricated/recaptcha/js/recaptcha.js new file mode 100644 index 000000000..cbda2d216 --- /dev/null +++ b/plugins/_depricated/recaptcha/js/recaptcha.js @@ -0,0 +1,78 @@ + +$(function () { + + var + bStarted = false, + bShown = false + ; + + function ShowRecaptcha() + { + if (window.Recaptcha) + { + if (bShown) + { + window.Recaptcha.reload(); + } + else + { + window.Recaptcha.create(window.rl.pluginSettingsGet('recaptcha', 'public_key'), 'recaptcha-place', { + 'theme': 'custom', + 'lang': window.rl.settingsGet('Language') + }); + } + + bShown = true; + } + } + + function StartRecaptcha() + { + if (!window.Recaptcha) + { + $.getScript('//www.google.com/recaptcha/api/js/recaptcha_ajax.js', ShowRecaptcha); + } + else + { + ShowRecaptcha(); + } + } + + if (window.rl) + { + window.rl.addHook('view-model-on-show', function (sName, oViewModel) { + if (!bStarted && oViewModel && + ('View:RainLoop:Login' === sName || 'View/App/Login' === sName || 'LoginViewModel' === sName || 'LoginAppView' === sName) && + window.rl.pluginSettingsGet('recaptcha', 'show_captcha_on_login')) + { + bStarted = true; + StartRecaptcha(); + } + }); + + window.rl.addHook('ajax-default-request', function (sAction, oParameters) { + if ('Login' === sAction && oParameters && bShown && window.Recaptcha) + { + oParameters['RecaptchaChallenge'] = window.Recaptcha.get_challenge(); + oParameters['RecaptchaResponse'] = window.Recaptcha.get_response(); + } + }); + + window.rl.addHook('ajax-default-response', function (sAction, oData, sType) { + if ('Login' === sAction) + { + if (!oData || 'success' !== sType || !oData['Result']) + { + if (bShown && window.Recaptcha) + { + window.Recaptcha.reload(); + } + else if (oData && oData['Captcha']) + { + StartRecaptcha(); + } + } + } + }); + } +}); \ No newline at end of file diff --git a/plugins/recaptcha/langs/en.ini b/plugins/_depricated/recaptcha/langs/en.ini similarity index 98% rename from plugins/recaptcha/langs/en.ini rename to plugins/_depricated/recaptcha/langs/en.ini index 782b117df..87ed5f76c 100644 --- a/plugins/recaptcha/langs/en.ini +++ b/plugins/_depricated/recaptcha/langs/en.ini @@ -1,2 +1,2 @@ -[PLUGIN] +[PLUGIN] LABEL_ENTER_THE_WORDS_ABOVE = "Enter the words above" \ No newline at end of file diff --git a/plugins/recaptcha/langs/ru.ini b/plugins/_depricated/recaptcha/langs/ru.ini similarity index 98% rename from plugins/recaptcha/langs/ru.ini rename to plugins/_depricated/recaptcha/langs/ru.ini index bc91f3555..77329095c 100644 --- a/plugins/recaptcha/langs/ru.ini +++ b/plugins/_depricated/recaptcha/langs/ru.ini @@ -1,2 +1,2 @@ -[PLUGIN] +[PLUGIN] LABEL_ENTER_THE_WORDS_ABOVE = "Введите слова с изображения" \ No newline at end of file diff --git a/plugins/recaptcha/recaptchalib.php b/plugins/_depricated/recaptcha/recaptchalib.php similarity index 100% rename from plugins/recaptcha/recaptchalib.php rename to plugins/_depricated/recaptcha/recaptchalib.php diff --git a/plugins/_depricated/recaptcha/templates/PluginLoginReCaptchaGroup.html b/plugins/_depricated/recaptcha/templates/PluginLoginReCaptchaGroup.html new file mode 100644 index 000000000..9af9b8aec --- /dev/null +++ b/plugins/_depricated/recaptcha/templates/PluginLoginReCaptchaGroup.html @@ -0,0 +1,18 @@ + \ No newline at end of file diff --git a/plugins/change-password-example/ChangePasswordExampleDriver.php b/plugins/change-password-example/ChangePasswordExampleDriver.php index ed2845fe7..1ffd3f3a9 100644 --- a/plugins/change-password-example/ChangePasswordExampleDriver.php +++ b/plugins/change-password-example/ChangePasswordExampleDriver.php @@ -19,7 +19,7 @@ class ChangePasswordExampleDriver implements \RainLoop\Providers\ChangePassword\ } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return bool */ @@ -30,7 +30,7 @@ class ChangePasswordExampleDriver implements \RainLoop\Providers\ChangePassword\ } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sPrevPassword * @param string $sNewPassword * diff --git a/plugins/change-smtp-ehlo-message/index.php b/plugins/change-smtp-ehlo-message/index.php index 252db9000..696ea84b0 100644 --- a/plugins/change-smtp-ehlo-message/index.php +++ b/plugins/change-smtp-ehlo-message/index.php @@ -8,12 +8,12 @@ class ChangeSmtpEhloMessagePlugin extends \RainLoop\Plugins\AbstractPlugin } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param array $aSmtpCredentials */ public function FilterSmtpCredentials($oAccount, &$aSmtpCredentials) { - if ($oAccount instanceof \RainLoop\Account && \is_array($aSmtpCredentials)) + if ($oAccount instanceof \RainLoop\Model\Account && \is_array($aSmtpCredentials)) { // Default: // $aSmtpCredentials['Ehlo'] = \MailSo\Smtp\SmtpClient::EhloHelper(); diff --git a/plugins/cpanel-change-password/CpanelChangePasswordDriver.php b/plugins/cpanel-change-password/CpanelChangePasswordDriver.php index 08d4a6070..310dfe27a 100644 --- a/plugins/cpanel-change-password/CpanelChangePasswordDriver.php +++ b/plugins/cpanel-change-password/CpanelChangePasswordDriver.php @@ -84,7 +84,7 @@ class CpanelChangePasswordDriver implements \RainLoop\Providers\ChangePassword\C } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return bool */ @@ -95,7 +95,7 @@ class CpanelChangePasswordDriver implements \RainLoop\Providers\ChangePassword\C } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sPrevPassword * @param string $sNewPassword * diff --git a/plugins/custom-settings-tab/LICENSE b/plugins/custom-settings-tab/LICENSE new file mode 100644 index 000000000..4aed64b3a --- /dev/null +++ b/plugins/custom-settings-tab/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 RainLoop Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/custom-settings-tab/VERSION b/plugins/custom-settings-tab/VERSION new file mode 100644 index 000000000..b123147e2 --- /dev/null +++ b/plugins/custom-settings-tab/VERSION @@ -0,0 +1 @@ +1.1 \ No newline at end of file diff --git a/plugins/custom-settings-tab/index.php b/plugins/custom-settings-tab/index.php new file mode 100644 index 000000000..686eb649a --- /dev/null +++ b/plugins/custom-settings-tab/index.php @@ -0,0 +1,57 @@ +UseLangs(true); // start use langs folder + + $this->addJs('js/CustomUserSettings.js'); // add js file + + $this->addAjaxHook('AjaxGetCustomUserData', 'AjaxGetCustomUserData'); + $this->addAjaxHook('AjaxSaveCustomUserData', 'AjaxSaveCustomUserData'); + + $this->addTemplate('templates/PluginCustomSettingsTag.html'); + } + + /** + * @return array + */ + public function AjaxGetCustomUserData() + { + $aSettings = $this->getUserSettings(); + + $sUserFacebook = isset($aSettings['UserFacebook']) ? $aSettings['UserFacebook'] : ''; + $sUserSkype = isset($aSettings['UserSkype']) ? $aSettings['UserSkype'] : ''; + + // or get user's data from your custom storage ( DB / LDAP / ... ). + + \sleep(1); + return $this->ajaxResponse(__FUNCTION__, array( + 'UserFacebook' => $sUserFacebook, + 'UserSkype' => $sUserSkype + )); + } + + /** + * @return array + */ + public function AjaxSaveCustomUserData() + { + $sUserFacebook = $this->ajaxParam('UserFacebook'); + $sUserSkype = $this->ajaxParam('UserSkype'); + + // or put user's data to your custom storage ( DB / LDAP / ... ). + + \sleep(1); + return $this->ajaxResponse(__FUNCTION__, $this->saveUserSettings(array( + 'UserFacebook' => $sUserFacebook, + 'UserSkype' => $sUserSkype + ))); + } + +} + diff --git a/plugins/custom-settings-tab/js/CustomUserSettings.js b/plugins/custom-settings-tab/js/CustomUserSettings.js new file mode 100644 index 000000000..cd71c0877 --- /dev/null +++ b/plugins/custom-settings-tab/js/CustomUserSettings.js @@ -0,0 +1,73 @@ + +(function () { + + /** + * @constructor + */ + function CustomUserSettings() + { + this.userSkype = ko.observable(''); + this.userFacebook = ko.observable(''); + + this.loading = ko.observable(false); + this.saving = ko.observable(false); + + this.savingOrLoading = ko.computed(function () { + return this.loading() || this.saving(); + }, this); + } + + CustomUserSettings.prototype.customAjaxSaveData = function () + { + var self = this; + + if (this.saving()) + { + return false; + } + + this.saving(true); + + window.rl.pluginRemoteRequest(function (sResult, oData) { + + self.saving(false); + + if (window.rl.Enums.StorageResultType.Success === sResult && oData && oData.Result) + { + // true + } + else + { + // false + } + + }, 'AjaxSaveCustomUserData', { + 'UserSkype': this.userSkype(), + 'UserFacebook': this.userFacebook() + }); + }; + + CustomUserSettings.prototype.onBuild = function () // special function + { + var self = this; + + this.loading(true); + + window.rl.pluginRemoteRequest(function (sResult, oData) { + + self.loading(false); + + if (window.rl.Enums.StorageResultType.Success === sResult && oData && oData.Result) + { + self.userSkype(oData.Result.UserSkype || ''); + self.userFacebook(oData.Result.UserFacebook || ''); + } + + }, 'AjaxGetCustomUserData'); + + }; + + window.rl.addSettingsViewModel(CustomUserSettings, 'PluginCustomSettingsTag', + 'SETTINGS_CUSTOM_PLUGIN/TAB_NAME', 'custom'); + +}()); \ No newline at end of file diff --git a/plugins/custom-settings-tab/langs/en.ini b/plugins/custom-settings-tab/langs/en.ini new file mode 100644 index 000000000..1f3e63378 --- /dev/null +++ b/plugins/custom-settings-tab/langs/en.ini @@ -0,0 +1,6 @@ +[SETTINGS_CUSTOM_PLUGIN] +TAB_NAME = "Custom Plugin" +LEGEND_CUSTOM = "Custom Plugin (Example)" +LABEL_SKYPE = "Skype" +LABEL_FACEBOOK = "Facebook" +BUTTON_SAVE = "Save" \ No newline at end of file diff --git a/plugins/custom-settings-tab/langs/ru.ini b/plugins/custom-settings-tab/langs/ru.ini new file mode 100644 index 000000000..6b275600c --- /dev/null +++ b/plugins/custom-settings-tab/langs/ru.ini @@ -0,0 +1,6 @@ +[SETTINGS_CUSTOM_PLUGIN] +TAB_NAME = "Плагин" +LEGEND_CUSTOM = "Плагин (Пример)" +LABEL_SKYPE = "Skype" +LABEL_FACEBOOK = "Facebook" +BUTTON_SAVE = "Сохранить" \ No newline at end of file diff --git a/plugins/custom-settings-tab/templates/PluginCustomSettingsTag.html b/plugins/custom-settings-tab/templates/PluginCustomSettingsTag.html new file mode 100644 index 000000000..31d4eb82b --- /dev/null +++ b/plugins/custom-settings-tab/templates/PluginCustomSettingsTag.html @@ -0,0 +1,34 @@ +
+
+
+ +     + +
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/plugins/custom-system-folders/index.php b/plugins/custom-system-folders/index.php index b9f6d2b97..92d69ecb5 100644 --- a/plugins/custom-system-folders/index.php +++ b/plugins/custom-system-folders/index.php @@ -40,7 +40,7 @@ class CustomSystemFoldersPlugin extends \RainLoop\Plugins\AbstractPlugin } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param array $aSystemTypes */ public function FilterFoldersSystemTypes($oAccount, &$aSystemTypes) @@ -88,7 +88,7 @@ class CustomSystemFoldersPlugin extends \RainLoop\Plugins\AbstractPlugin } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param array $aSystemFolderNames */ public function FilterSystemFoldersNames($oAccount, &$aSystemFolderNames) @@ -110,7 +110,7 @@ class CustomSystemFoldersPlugin extends \RainLoop\Plugins\AbstractPlugin $this->helperFolderType($this->sArchiveFolder, \MailSo\Imap\Enumerations\FolderType::ALL, $aPrepend, $aSystemFolderNames); - + if (0 < \count($aPrepend)) { $aSystemFolderNames = \array_merge($aPrepend, $aSystemFolderNames); diff --git a/plugins/demo-account/VERSION b/plugins/demo-account/VERSION new file mode 100644 index 000000000..9f8e9b69a --- /dev/null +++ b/plugins/demo-account/VERSION @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/plugins/demo-account/index.php b/plugins/demo-account/index.php new file mode 100644 index 000000000..74266b16c --- /dev/null +++ b/plugins/demo-account/index.php @@ -0,0 +1,107 @@ +addHook('filter.app-data', 'FilterAppData'); + $this->addHook('filter.action-params', 'FilterActionParams'); + $this->addHook('ajax.action-pre-call', 'AjaxActionPreCall'); + $this->addHook('filter.send-message', 'FilterSendMessage'); + $this->addHook('main.fabrica', 'MainFabrica'); + } + + /** + * @return array + */ + protected function configMapping() + { + return array( + \RainLoop\Plugins\Property::NewInstance('email')->SetLabel('Demo Email') + ->SetDefaultValue('demo@domain.com'), + \RainLoop\Plugins\Property::NewInstance('password')->SetLabel('Demo Password') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::PASSWORD) + ); + } + + /** + * @return void + */ + public function FilterAppData($bAdmin, &$aResult) + { + if (!$bAdmin && \is_array($aResult) && isset($aResult['Auth']) && !$aResult['Auth']) + { + $aResult['DevEmail'] = $this->Config()->Get('plugin', 'email', $aResult['DevEmail']); + $aResult['DevPassword'] = APP_DUMMY; + } + } + + /** + * @return void + */ + public function FilterActionParams($sMethodName, &$aActionParams) + { + if ('DoLogin' === $sMethodName && isset($aActionParams['Email']) && isset($aActionParams['Password'])) + { + if ($this->Config()->Get('plugin', 'email') === $aActionParams['Email']) + { + $aActionParams['Password'] = $this->Config()->Get('plugin', 'password'); + } + } + } + + /** + * @param \RainLoop\Model\Account $oAccount + * + * @return bool + */ + public function isDemoAccount($oAccount) + { + return ($oAccount && $oAccount->Email() === $this->Config()->Get('plugin', 'email')); + } + + public function AjaxActionPreCall($sAction) + { + if ('AccountSetup' === $sAction && + $this->isDemoAccount($this->Manager()->Actions()->GetAccount())) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::DemoAccountError); + } + } + + public function FilterSendMessage(&$oMessage) + { + if ($oMessage && $this->isDemoAccount($this->Manager()->Actions()->GetAccount())) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::DemoSendMessageError); + } + } + + /** + * @param string $sName + * @param mixed $oDriver + */ + public function MainFabrica($sName, &$oDriver) + { + switch ($sName) + { + case 'storage': + case 'storage-local': + if (\class_exists('\\RainLoop\\Providers\\Storage\\TemproryApcStorage') && + \function_exists('apc_store')) + { + $oAccount = $this->Manager()->Actions()->GetAccount(); + if ($this->isDemoAccount($oAccount)) + { + $oDriver = new \RainLoop\Providers\Storage\TemproryApcStorage(APP_PRIVATE_DATA.'storage', + $sName === 'storage-local'); + } + } + break; + } + } +} + diff --git a/plugins/hmailserver-change-password/HmailserverChangePasswordDriver.php b/plugins/hmailserver-change-password/HmailserverChangePasswordDriver.php index fece38a28..d03c9c615 100644 --- a/plugins/hmailserver-change-password/HmailserverChangePasswordDriver.php +++ b/plugins/hmailserver-change-password/HmailserverChangePasswordDriver.php @@ -63,7 +63,7 @@ class HmailserverChangePasswordDriver implements \RainLoop\Providers\ChangePassw } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return bool */ @@ -74,7 +74,7 @@ class HmailserverChangePasswordDriver implements \RainLoop\Providers\ChangePassw } /** - * @param \RainLoop\Account $oHmailAccount + * @param \RainLoop\Model\Account $oHmailAccount * @param string $sPrevPassword * @param string $sNewPassword * diff --git a/plugins/ispconfig-change-password/IspConfigChangePasswordDriver.php b/plugins/ispconfig-change-password/IspConfigChangePasswordDriver.php index 776bda038..40059247a 100644 --- a/plugins/ispconfig-change-password/IspConfigChangePasswordDriver.php +++ b/plugins/ispconfig-change-password/IspConfigChangePasswordDriver.php @@ -70,7 +70,7 @@ class IspConfigChangePasswordDriver implements \RainLoop\Providers\ChangePasswor } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return bool */ @@ -81,7 +81,7 @@ class IspConfigChangePasswordDriver implements \RainLoop\Providers\ChangePasswor } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sPrevPassword * @param string $sNewPassword * diff --git a/plugins/ldap-change-password/ChangePasswordLdapDriver.php b/plugins/ldap-change-password/ChangePasswordLdapDriver.php new file mode 100644 index 000000000..5b266f289 --- /dev/null +++ b/plugins/ldap-change-password/ChangePasswordLdapDriver.php @@ -0,0 +1,214 @@ +sHostName = $sHostName; + $this->sUserDnFormat = $sUserDnFormat; + $this->sPasswordField = $sPasswordField; + $this->sPasswordEncType = $sPasswordEncType; + + return $this; + } + + /** + * @param string $sAllowedEmails + * + * @return \ChangePasswordLdapDriver + */ + public function SetAllowedEmails($sAllowedEmails) + { + $this->sAllowedEmails = $sAllowedEmails; + + return $this; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \ChangePasswordLdapDriver + */ + public function SetLogger($oLogger) + { + if ($oLogger instanceof \MailSo\Log\Logger) + { + $this->oLogger = $oLogger; + } + + return $this; + } + + /** + * @param \RainLoop\Account $oAccount + * + * @return bool + */ + public function PasswordChangePossibility($oAccount) + { + return $oAccount && $oAccount->Email() && + \RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails); + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param string $sPrevPassword + * @param string $sNewPassword + * + * @return bool + */ + public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword) + { + $bResult = false; + + try + { + $sDomain = \MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email()); + $sUserDn = \strtr($this->sUserDnFormat, array( + '{domain}' => $sDomain, + '{domain:dc}' => 'dc='.\strtr($sDomain, array('.' => ',dc=')), + '{email}' => $oAccount->Email(), + '{email:user}' => \MailSo\Base\Utils::GetAccountNameFromEmail($oAccount->Email()), + '{email:domain}' => $sDomain, + '{login}' => $oAccount->Login(), + '{imap:login}' => $oAccount->Login(), + '{imap:host}' => $oAccount->DomainIncHost(), + '{imap:port}' => $oAccount->DomainIncPort() + )); + + $oCon = @\ldap_connect($this->sHostName); + if ($oCon) + { + @\ldap_set_option($oCon, LDAP_OPT_PROTOCOL_VERSION, 3); + + if (!@\ldap_bind($oCon, $sUserDn, $sPrevPassword)) + { + if ($this->oLogger) + { + $sError = $oCon ? @\ldap_error($oCon) : ''; + $iErrno = $oCon ? @\ldap_errno($oCon) : 0; + + $this->oLogger->Write('ldap_bind error: '.$sError.' ('.$iErrno.')', + \MailSo\Log\Enumerations\Type::WARNING, 'LDAP'); + } + + return false; + } + } + + $sEncodedNewPassword = $sNewPassword; + switch (\strtolower($this->sPasswordEncType)) + { + case 'sha': + switch (true) + { + default: + case \function_exists('sha1'): + $sEncodedNewPassword = '{SHA}'.\base64_encode(\pack('H*', \sha1($sNewPassword))); + break; + case \function_exists('hash'): + $sEncodedNewPassword = '{SHA}'.\base64_encode(\hash('sha1', $sNewPassword, true)); + break; + case \function_exists('mhash') && defined('MHASH_SHA1'): + $sEncodedNewPassword = '{SHA}'.\base64_encode(\mhash(MHASH_SHA1, $sNewPassword)); + break; + } + break; + case 'md5': + $sEncodedNewPassword = '{MD5}'.\base64_encode(\pack('H*', \md5($sNewPassword))); + break; + case 'crypt': + $sEncodedNewPassword = '{CRYPT}'.\crypt($sNewPassword, $this->getSalt(2)); + break; + } + + $aEntry = array(); + $aEntry[$this->sPasswordField] = (string) $sEncodedNewPassword; + + if (!!@\ldap_modify($oCon, $sUserDn, $aEntry)) + { + $bResult = true; + } + else + { + if ($this->oLogger) + { + $sError = $oCon ? @\ldap_error($oCon) : ''; + $iErrno = $oCon ? @\ldap_errno($oCon) : 0; + + $this->oLogger->Write('ldap_modify error: '.$sError.' ('.$iErrno.')', + \MailSo\Log\Enumerations\Type::WARNING, 'LDAP'); + } + } + } + catch (\Exception $oException) + { + if ($this->oLogger) + { + $this->oLogger->WriteException($oException, + \MailSo\Log\Enumerations\Type::WARNING, 'LDAP'); + } + + $bResult = false; + } + + return $bResult; + } + + /** + * @param int $iLength + * + * @return string + */ + private function getSalt($iLength) + { + $sChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $iCharsLength = \strlen($sChars); + + $sResult = ''; + while (\strlen($sResult) < $iLength) + { + $sResult .= \substr($sChars, \rand() % $iCharsLength, 1); + } + + return $sResult; + } +} diff --git a/plugins/ldap-change-password/LICENSE b/plugins/ldap-change-password/LICENSE new file mode 100644 index 000000000..4aed64b3a --- /dev/null +++ b/plugins/ldap-change-password/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 RainLoop Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/ldap-change-password/README b/plugins/ldap-change-password/README new file mode 100644 index 000000000..53c1abf7f --- /dev/null +++ b/plugins/ldap-change-password/README @@ -0,0 +1 @@ +Plugin that adds functionality to change the email account password (LDAP Password). diff --git a/plugins/ldap-change-password/VERSION b/plugins/ldap-change-password/VERSION new file mode 100644 index 000000000..9f8e9b69a --- /dev/null +++ b/plugins/ldap-change-password/VERSION @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/plugins/ldap-change-password/index.php b/plugins/ldap-change-password/index.php new file mode 100644 index 000000000..639a6ed03 --- /dev/null +++ b/plugins/ldap-change-password/index.php @@ -0,0 +1,75 @@ +addHook('main.fabrica', 'MainFabrica'); + } + + /** + * @return string + */ + public function Supported() + { + if (!\function_exists('ldap_connect')) + { + return 'The LDAP PHP exention must be installed to use this plugin'; + } + + return ''; + } + + /** + * @param string $sName + * @param mixed $oProvider + */ + public function MainFabrica($sName, &$oProvider) + { + switch ($sName) + { + case 'change-password': + + $sHostName = \trim($this->Config()->Get('plugin', 'hostname', '')); + $sUserDnFormat = \trim($this->Config()->Get('plugin', 'user_dn_format', '')); + $sPasswordField = \trim($this->Config()->Get('plugin', 'password_field', '')); + $sPasswordEncType = \trim($this->Config()->Get('plugin', 'password_enc_type', '')); + + if (!empty($sHostName) && !empty($sUserDnFormat) && !empty($sPasswordField) && !empty($sPasswordEncType)) + { + include_once __DIR__.'/ChangePasswordLdapDriver.php'; + + $oProvider = new \ChangePasswordLdapDriver(); + + $oProvider + ->SetConfig($sHostName, $sUserDnFormat, $sPasswordField, $sPasswordEncType) + ->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', '')))) + ->SetLogger($this->Manager()->Actions()->Logger()) + ; + } + break; + } + } + + /** + * @return array + */ + public function configMapping() + { + return array( + \RainLoop\Plugins\Property::NewInstance('hostname')->SetLabel('LDAP hostname') + ->SetDefaultValue('127.0.0.1'), + \RainLoop\Plugins\Property::NewInstance('user_dn_format')->SetLabel('User DN format') + ->SetDescription('LDAP user dn format. Supported tokens: {email}, {login}, {domain}, {domain:dc}, {imap:login}, {imap:host}, {imap:port}') + ->SetDefaultValue('uid={imap:login},ou=Users,{domain:dc}'), + \RainLoop\Plugins\Property::NewInstance('password_field')->SetLabel('Password field') + ->SetDefaultValue('userPassword'), + \RainLoop\Plugins\Property::NewInstance('password_enc_type')->SetLabel('Encryption type') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) + ->SetDefaultValue(array('SHA', 'MD5', 'Crypt', 'Clear')), + \RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails') + ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net') + ->SetDefaultValue('*') + ); + } +} diff --git a/plugins/override-smtp-credentials/index.php b/plugins/override-smtp-credentials/index.php index bb7f6767c..ed26330c6 100644 --- a/plugins/override-smtp-credentials/index.php +++ b/plugins/override-smtp-credentials/index.php @@ -8,12 +8,12 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param array $aSmtpCredentials */ public function FilterSmtpCredentials($oAccount, &$aSmtpCredentials) { - if ($oAccount instanceof \RainLoop\Account && \is_array($aSmtpCredentials)) + if ($oAccount instanceof \RainLoop\Model\Account && \is_array($aSmtpCredentials)) { $sEmail = $oAccount->Email(); diff --git a/plugins/piwik-analytics/index.php b/plugins/piwik-analytics/index.php index 11368a99a..1932a0717 100644 --- a/plugins/piwik-analytics/index.php +++ b/plugins/piwik-analytics/index.php @@ -1,35 +1,36 @@ -Config()->Get('plugin', 'account', '')) - { - $this->addJs('js/include.js'); - } - } - - /** - * @return array - */ - public function configMapping() - { - $oUrl = \RainLoop\Plugins\Property::NewInstance('piwik_url')->SetLabel('Piwik URL') - ->SetAllowedInJs(true); - - $oSiteID = \RainLoop\Plugins\Property::NewInstance('site_id')->SetLabel('Site ID') - ->SetAllowedInJs(true); - - if (\method_exists($oUrl, 'SetPlaceholder')) - { - $oUrl->SetPlaceholder('http://'); - $oSiteID->SetPlaceholder(''); - } - - return array($oUrl, $oSiteID); - } -} +Config()->Get('plugin', 'piwik_url', '') && + '' !== $this->Config()->Get('plugin', 'site_id', '')) + { + $this->addJs('js/include.js'); + } + } + + /** + * @return array + */ + public function configMapping() + { + $oUrl = \RainLoop\Plugins\Property::NewInstance('piwik_url')->SetLabel('Piwik URL') + ->SetAllowedInJs(true); + + $oSiteID = \RainLoop\Plugins\Property::NewInstance('site_id')->SetLabel('Site ID') + ->SetAllowedInJs(true); + + if (\method_exists($oUrl, 'SetPlaceholder')) + { + $oUrl->SetPlaceholder('http://'); + $oSiteID->SetPlaceholder(''); + } + + return array($oUrl, $oSiteID); + } +} diff --git a/plugins/piwik-analytics/js/include.js b/plugins/piwik-analytics/js/include.js index 26afba6ff..df4b4dc6d 100644 --- a/plugins/piwik-analytics/js/include.js +++ b/plugins/piwik-analytics/js/include.js @@ -1,6 +1,6 @@ $(function () { - + var sPiwikURL = '' + window.rl.pluginSettingsGet('piwik-analytics', 'piwik_url'), sSiteID = '' + window.rl.pluginSettingsGet('piwik-analytics', 'site_id') @@ -9,7 +9,7 @@ $(function () { if ('' !== sPiwikURL && '' !== sSiteID) { sPiwikURL = sPiwikURL.replace(/[\\\/\s]+$/, '') + '/'; - if (/^https?:/i.test(sPiwikURL)) + if (!/^https?:/i.test(sPiwikURL)) { sPiwikURL = 'http://' + sPiwikURL; } diff --git a/plugins/poppassd-change-password/ChangePasswordPoppassdDriver.php b/plugins/poppassd-change-password/ChangePasswordPoppassdDriver.php index 6f6f6d727..9cb2851c2 100644 --- a/plugins/poppassd-change-password/ChangePasswordPoppassdDriver.php +++ b/plugins/poppassd-change-password/ChangePasswordPoppassdDriver.php @@ -71,7 +71,7 @@ class ChangePasswordPoppassdDriver implements \RainLoop\Providers\ChangePassword } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return bool */ @@ -82,7 +82,7 @@ class ChangePasswordPoppassdDriver implements \RainLoop\Providers\ChangePassword } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sPrevPassword * @param string $sNewPassword * diff --git a/plugins/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php b/plugins/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php index 80ece64f5..ccd1dbf8a 100644 --- a/plugins/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php +++ b/plugins/postfixadmin-change-password/ChangePasswordPostfixAdminDriver.php @@ -135,7 +135,7 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return bool */ @@ -146,7 +146,7 @@ class ChangePasswordPostfixAdminDriver implements \RainLoop\Providers\ChangePass } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sPrevPassword * @param string $sNewPassword * diff --git a/plugins/proxyauth-login-example/LICENSE b/plugins/proxyauth-login-example/LICENSE new file mode 100644 index 000000000..4a4ca8d81 --- /dev/null +++ b/plugins/proxyauth-login-example/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 RainLoop Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/proxyauth-login-example/VERSION b/plugins/proxyauth-login-example/VERSION new file mode 100644 index 000000000..9f8e9b69a --- /dev/null +++ b/plugins/proxyauth-login-example/VERSION @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/plugins/proxyauth-login-example/index.php b/plugins/proxyauth-login-example/index.php new file mode 100644 index 000000000..747d219d1 --- /dev/null +++ b/plugins/proxyauth-login-example/index.php @@ -0,0 +1,50 @@ +addHook('event.login-post-login-provide', 'EventLoginPostLoginProvide'); + } + + /** + * @param string $sLogin + * @param string $sPassword + */ + public function isValidAccount($sLogin, $sPassword) + { + return !empty($sLogin) && !empty($sPassword); + } + + /** + * @param \RainLoop\Model\Account $oAccount + */ + public function EventLoginPostLoginProvide(&$oAccount) + { + if ($oAccount instanceof \RainLoop\Model\Account) + { + // Verify logic + $bValid = $this->isValidAccount($oAccount->Login(), $oAccount->Password()); + + /** + * $oAccount->Email(); // Email (It is not a IMAP login) + * $oAccount->Login(); // IMAP login + * $oAccount->Password(); // IMAP password + * $oAccount->DomainIncHost(); // IMAP host + * + * @see \RainLoo\Model\Account for more + */ + + if (!$bValid) // if verify failed + { + // throw a Auth Error Exception + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); + } + else // Or setup your proxyauth admin account credentials + { + $oAccount->SetProxyAuthUser('admin@domain.com'); + $oAccount->SetProxyAuthPassword('secret-admin-password'); + } + } + } +} diff --git a/plugins/recaptcha/LICENSE b/plugins/recaptcha/LICENSE index 271342337..4aed64b3a 100644 --- a/plugins/recaptcha/LICENSE +++ b/plugins/recaptcha/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 RainLoop Team +Copyright (c) 2015 RainLoop Team Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/plugins/recaptcha/README b/plugins/recaptcha/README index 17e4ab032..ee29dbafc 100644 --- a/plugins/recaptcha/README +++ b/plugins/recaptcha/README @@ -1,3 +1,3 @@ -A CAPTCHA is a program that can generate and grade tests that humans can pass but current computer programs cannot. +A CAPTCHA (v2) is a program that can generate and grade tests that humans can pass but current computer programs cannot. For example, humans can read distorted text as the one shown below, but current computer programs can't. More info at http://www.google.com/recaptcha \ No newline at end of file diff --git a/plugins/recaptcha/VERSION b/plugins/recaptcha/VERSION index 468437494..415b19fc3 100644 --- a/plugins/recaptcha/VERSION +++ b/plugins/recaptcha/VERSION @@ -1 +1 @@ -1.8 \ No newline at end of file +2.0 \ No newline at end of file diff --git a/plugins/recaptcha/index.php b/plugins/recaptcha/index.php index 2875839f7..4d1ab96ea 100644 --- a/plugins/recaptcha/index.php +++ b/plugins/recaptcha/index.php @@ -8,27 +8,31 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin public function Init() { $this->UseLangs(true); - + $this->addJs('js/recaptcha.js'); - + $this->addHook('ajax.action-pre-call', 'AjaxActionPreCall'); $this->addHook('filter.ajax-response', 'FilterAjaxResponse'); $this->addTemplate('templates/PluginLoginReCaptchaGroup.html'); $this->addTemplateHook('Login', 'BottomControlGroup', 'PluginLoginReCaptchaGroup'); } - + /** * @return array */ public function configMapping() { return array( - \RainLoop\Plugins\Property::NewInstance('public_key')->SetLabel('Public Key') + \RainLoop\Plugins\Property::NewInstance('public_key')->SetLabel('Site key') ->SetAllowedInJs(true) ->SetDefaultValue(''), - \RainLoop\Plugins\Property::NewInstance('private_key')->SetLabel('Private Key') + \RainLoop\Plugins\Property::NewInstance('private_key')->SetLabel('Secret key') ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('theme')->SetLabel('Theme') + ->SetAllowedInJs(true) + ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) + ->SetDefaultValue(array('light', 'dark')), \RainLoop\Plugins\Property::NewInstance('error_limit')->SetLabel('Limit') ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) ->SetDefaultValue(array(0, 1, 2, 3, 4, 5)) @@ -41,7 +45,7 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin */ private function getCaptchaCacherKey() { - return 'Captcha/Login/'.\RainLoop\Utils::GetConnectionToken(); + return 'CaptchaNew/Login/'.\RainLoop\Utils::GetConnectionToken(); } /** @@ -54,8 +58,8 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin { $oCacher = $this->Manager()->Actions()->Cacher(); $sLimit = $oCacher && $oCacher->IsInited() ? $oCacher->Get($this->getCaptchaCacherKey()) : '0'; - - if (0 < strlen($sLimit) && is_numeric($sLimit)) + + if (0 < \strlen($sLimit) && \is_numeric($sLimit)) { $iConfigLimit -= (int) $sLimit; } @@ -69,7 +73,7 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin */ public function FilterAppDataPluginSection($bAdmin, $bAuth, &$aData) { - if (!$bAdmin && !$bAuth && is_array($aData)) + if (!$bAdmin && !$bAuth && \is_array($aData)) { $aData['show_captcha_on_login'] = 1 > $this->getLimit(); } @@ -82,18 +86,25 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin { if ('Login' === $sAction && 0 >= $this->getLimit()) { - require_once __DIR__.'/recaptchalib.php'; + $bResult = false; - $oResp = recaptcha_check_answer( - $this->Config()->Get('plugin', 'private_key', ''), - isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '', - $this->Manager()->Actions()->GetActionParam('RecaptchaChallenge', ''), - $this->Manager()->Actions()->GetActionParam('RecaptchaResponse', '') - ); + $sResult = $this->Manager()->Actions()->Http()->GetUrlAsString( + 'https://www.google.com/recaptcha/api/siteverify?secret='. + \urlencode($this->Config()->Get('plugin', 'private_key', '')).'&response='. + \urlencode($this->Manager()->Actions()->GetActionParam('RecaptchaResponse', ''))); - if (!$oResp || !isset($oResp->is_valid) || !$oResp->is_valid) + if ($sResult) { - $this->Manager()->Actions()->Logger()->WriteDump($oResp); + $aResp = @\json_decode($sResult, true); + if (\is_array($aResp) && isset($aResp['success']) && $aResp['success']) + { + $bResult = true; + } + } + + if (!$bResult) + { + $this->Manager()->Actions()->Logger()->Write('RecaptchaResponse:'.$sResult); throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CaptchaError); } } @@ -109,6 +120,7 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin { $oCacher = $this->Manager()->Actions()->Cacher(); $iConfigLimit = (int) $this->Config()->Get('plugin', 'error_limit', 0); + $sKey = $this->getCaptchaCacherKey(); if (0 < $iConfigLimit && $oCacher && $oCacher->IsInited()) @@ -117,7 +129,7 @@ class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin { $iLimit = 0; $sLimut = $oCacher->Get($sKey); - if (0 < strlen($sLimut) && is_numeric($sLimut)) + if (0 < \strlen($sLimut) && \is_numeric($sLimut)) { $iLimit = (int) $sLimut; } diff --git a/plugins/recaptcha/js/recaptcha.js b/plugins/recaptcha/js/recaptcha.js index cbda2d216..7b1802d31 100644 --- a/plugins/recaptcha/js/recaptcha.js +++ b/plugins/recaptcha/js/recaptcha.js @@ -1,78 +1,89 @@ +(function ($, window) { -$(function () { + $(function () { - var - bStarted = false, - bShown = false - ; + var + nId = null, + bStarted = false + ; - function ShowRecaptcha() - { - if (window.Recaptcha) + function ShowRecaptcha() { - if (bShown) + if (window.grecaptcha) { - window.Recaptcha.reload(); - } - else - { - window.Recaptcha.create(window.rl.pluginSettingsGet('recaptcha', 'public_key'), 'recaptcha-place', { - 'theme': 'custom', - 'lang': window.rl.settingsGet('Language') - }); - } - - bShown = true; - } - } - - function StartRecaptcha() - { - if (!window.Recaptcha) - { - $.getScript('//www.google.com/recaptcha/api/js/recaptcha_ajax.js', ShowRecaptcha); - } - else - { - ShowRecaptcha(); - } - } - - if (window.rl) - { - window.rl.addHook('view-model-on-show', function (sName, oViewModel) { - if (!bStarted && oViewModel && - ('View:RainLoop:Login' === sName || 'View/App/Login' === sName || 'LoginViewModel' === sName || 'LoginAppView' === sName) && - window.rl.pluginSettingsGet('recaptcha', 'show_captcha_on_login')) - { - bStarted = true; - StartRecaptcha(); - } - }); - - window.rl.addHook('ajax-default-request', function (sAction, oParameters) { - if ('Login' === sAction && oParameters && bShown && window.Recaptcha) - { - oParameters['RecaptchaChallenge'] = window.Recaptcha.get_challenge(); - oParameters['RecaptchaResponse'] = window.Recaptcha.get_response(); - } - }); - - window.rl.addHook('ajax-default-response', function (sAction, oData, sType) { - if ('Login' === sAction) - { - if (!oData || 'success' !== sType || !oData['Result']) + if (null === nId) { - if (bShown && window.Recaptcha) + var oEl = $('#recaptcha-div'); + if (oEl && oEl[0]) { - window.Recaptcha.reload(); - } - else if (oData && oData['Captcha']) - { - StartRecaptcha(); + oEl.show(); + nId = window.grecaptcha.render(oEl[0], { + 'sitekey': window.rl.pluginSettingsGet('recaptcha', 'public_key'), + 'theme': window.rl.pluginSettingsGet('recaptcha', 'theme') + }); } } } - }); - } -}); \ No newline at end of file + } + + window.__globalShowRecaptcha = ShowRecaptcha; + + function StartRecaptcha() + { + if (!window.grecaptcha && window.rl) + { + $.getScript('https://www.google.com/recaptcha/api.js?onload=__globalShowRecaptcha&render=explicit&hl=' + window.rl.settingsGet('Language')); + } + else + { + ShowRecaptcha(); + } + } + + if (window.rl) + { + window.rl.addHook('user-login-submit', function (fSubmitResult) { + if (null !== nId && !window.grecaptcha.getResponse(nId)) + { + fSubmitResult(105); + } + }); + + window.rl.addHook('view-model-on-show', function (sName, oViewModel) { + if (!bStarted && oViewModel && + ('View:RainLoop:Login' === sName || 'View/App/Login' === sName || 'LoginViewModel' === sName || 'LoginAppView' === sName) && + window.rl.pluginSettingsGet('recaptcha', 'show_captcha_on_login')) + { + bStarted = true; + StartRecaptcha(); + } + }); + + window.rl.addHook('ajax-default-request', function (sAction, oParameters) { + if ('Login' === sAction && oParameters && null !== nId && window.grecaptcha) + { + oParameters['RecaptchaResponse'] = window.grecaptcha.getResponse(nId); + } + }); + + window.rl.addHook('ajax-default-response', function (sAction, oData, sType) { + if ('Login' === sAction) + { + if (!oData || 'success' !== sType || !oData['Result']) + { + if (null !== nId && window.grecaptcha) + { + window.grecaptcha.reset(nId); + } + else if (oData && oData['Captcha']) + { + StartRecaptcha(); + } + + } + } + }); + } + }); + +}($, window)); \ No newline at end of file diff --git a/plugins/recaptcha/templates/PluginLoginReCaptchaGroup.html b/plugins/recaptcha/templates/PluginLoginReCaptchaGroup.html index e6595a774..9749c8164 100644 --- a/plugins/recaptcha/templates/PluginLoginReCaptchaGroup.html +++ b/plugins/recaptcha/templates/PluginLoginReCaptchaGroup.html @@ -1,14 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/plugins/vesta-change-password/LICENSE b/plugins/vesta-change-password/LICENSE new file mode 100644 index 000000000..271342337 --- /dev/null +++ b/plugins/vesta-change-password/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 RainLoop Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/vesta-change-password/README b/plugins/vesta-change-password/README new file mode 100644 index 000000000..4f2f06281 --- /dev/null +++ b/plugins/vesta-change-password/README @@ -0,0 +1 @@ +Plugin that adds functionality to change the email account password (Vesta Control Panel). \ No newline at end of file diff --git a/plugins/vesta-change-password/VERSION b/plugins/vesta-change-password/VERSION new file mode 100644 index 000000000..9f8e9b69a --- /dev/null +++ b/plugins/vesta-change-password/VERSION @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/plugins/vesta-change-password/VestaChangePasswordDriver.php b/plugins/vesta-change-password/VestaChangePasswordDriver.php new file mode 100644 index 000000000..cbb8be5c1 --- /dev/null +++ b/plugins/vesta-change-password/VestaChangePasswordDriver.php @@ -0,0 +1,146 @@ +sHost = $sHost; + $this->iPort = $iPort; + + return $this; + } + + /** + * @param string $sAllowedEmails + * + * @return \VestaChangePasswordDriver + */ + public function SetAllowedEmails($sAllowedEmails) + { + $this->sAllowedEmails = $sAllowedEmails; + return $this; + } + + /** + * @param \MailSo\Log\Logger $oLogger + * + * @return \VestaChangePasswordDriver + */ + public function SetLogger($oLogger) + { + if ($oLogger instanceof \MailSo\Log\Logger) + { + $this->oLogger = $oLogger; + } + + return $this; + } + + /** + * @param \RainLoop\Account $oAccount + * + * @return bool + */ + public function PasswordChangePossibility($oAccount) + { + return $oAccount && $oAccount->Email() && + \RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails); + } + + /** + * @param \RainLoop\Account $oAccount + * @param string $sPrevPassword + * @param string $sNewPassword + * + * @return bool + */ + public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword) + { + if ($this->oLogger) + { + $this->oLogger->Write('Vesta: Try to change password for '.$oAccount->Email()); + } + + $bResult = false; + if (!empty($this->sHost) && 0 < $this->iPort && $oAccount) + { + $sEmail = \trim(\strtolower($oAccount->Email())); + + $sHost = \trim($this->sHost); + $sHost = \str_replace('{user:host-imap}', $oAccount->Domain()->IncHost(), $sHost); + $sHost = \str_replace('{user:host-smtp}', $oAccount->Domain()->OutHost(), $sHost); + $sHost = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sHost); + $sHost = \rtrim($this->sHost, '/\\'); + $sHost = 'https://'.$sHost; + + $sUrl = $sHost.':'.$this->iPort.'/reset/mail/'; + + $iCode = 0; + $oHttp = \MailSo\Base\Http::SingletonInstance(); + + if ($this->oLogger) + { + $this->oLogger->Write('Vesta[Api Request]:'.$sUrl); + } + + $mResult = $oHttp->SendPostRequest($sUrl, + array( + 'email' => $sEmail, + 'password' => $sPrevPassword, + 'new' => $sNewPassword, + ), 'MailSo Http User Agent (v1)', $iCode, $this->oLogger); + + if (false !== $mResult && 200 === $iCode) + { + $aRes = null; + @\parse_str($mResult, $aRes); + if (is_array($aRes) && (!isset($aRes['error']) || (int) $aRes['error'] !== 1)) + { + $bResult = true; + } + else + { + if ($this->oLogger) + { + $this->oLogger->Write('Vesta[Error]: Response: '.$mResult); + } + } + } + else + { + if ($this->oLogger) + { + $this->oLogger->Write('Vesta[Error]: Empty Response: Code:'.$iCode); + } + } + } + + return $bResult; + } +} diff --git a/plugins/vesta-change-password/index.php b/plugins/vesta-change-password/index.php new file mode 100644 index 000000000..f79c8711a --- /dev/null +++ b/plugins/vesta-change-password/index.php @@ -0,0 +1,55 @@ +addHook('main.fabrica', 'MainFabrica'); + } + + /** + * @param string $sName + * @param mixed $oProvider + */ + public function MainFabrica($sName, &$oProvider) + { + switch ($sName) + { + case 'change-password': + + $sHost = \trim($this->Config()->Get('plugin', 'vesta_host', '')); + $iPort = (int) $this->Config()->Get('plugin', 'vesta_port', 8083); + + if (!empty($sHost) && 0 < $iPort) + { + include_once __DIR__.'/VestaChangePasswordDriver.php'; + + $oProvider = new VestaChangePasswordDriver(); + $oProvider->SetLogger($this->Manager()->Actions()->Logger()); + $oProvider->SetConfig($sHost, $iPort); + $oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', '')))); + } + + break; + } + } + + /** + * @return array + */ + public function configMapping() + { + return array( + \RainLoop\Plugins\Property::NewInstance('vesta_host')->SetLabel('Vesta Host') + ->SetDefaultValue('') + ->SetDescription('Allowed patterns: {user:host-imap}, {user:host-smtp}, {user:domain}'), + \RainLoop\Plugins\Property::NewInstance('Vesta_port')->SetLabel('Vesta Port') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::INT) + ->SetDefaultValue(8083), + \RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT) + ->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net') + ->SetDefaultValue('*') + ); + } +} diff --git a/plugins/video-on-login-screen/LICENSE b/plugins/video-on-login-screen/LICENSE new file mode 100644 index 000000000..4aed64b3a --- /dev/null +++ b/plugins/video-on-login-screen/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015 RainLoop Team + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/video-on-login-screen/README b/plugins/video-on-login-screen/README new file mode 100644 index 000000000..ec8c5bbdb --- /dev/null +++ b/plugins/video-on-login-screen/README @@ -0,0 +1 @@ +Fullscreen background video on login screen. \ No newline at end of file diff --git a/plugins/video-on-login-screen/VERSION b/plugins/video-on-login-screen/VERSION new file mode 100644 index 000000000..b123147e2 --- /dev/null +++ b/plugins/video-on-login-screen/VERSION @@ -0,0 +1 @@ +1.1 \ No newline at end of file diff --git a/plugins/video-on-login-screen/index.php b/plugins/video-on-login-screen/index.php new file mode 100644 index 000000000..173d4e040 --- /dev/null +++ b/plugins/video-on-login-screen/index.php @@ -0,0 +1,42 @@ +addJs('js/vide/jquery.vide.js'); + $this->addJs('js/video-on-login.js'); + } + + /** + * @return array + */ + public function configMapping() + { + return array( + \RainLoop\Plugins\Property::NewInstance('mp4_file')->SetLabel('Url to a mp4 file') + ->SetPlaceholder('http://') + ->SetAllowedInJs(true) + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('webm_file')->SetLabel('Url to a webm file') + ->SetPlaceholder('http://') + ->SetAllowedInJs(true) + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('ogv_file')->SetLabel('Url to a ogv file') + ->SetPlaceholder('http://') + ->SetAllowedInJs(true) + ->SetDefaultValue(''), + \RainLoop\Plugins\Property::NewInstance('playback_rate')->SetLabel('Playback rate') + ->SetAllowedInJs(true) + ->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION) + ->SetDefaultValue(array('100%', '25%', '50%', '75%', '125%', '150%', '200%')), + \RainLoop\Plugins\Property::NewInstance('muted')->SetLabel('Muted') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) + ->SetAllowedInJs(true) + ->SetDefaultValue(true), + ); + } +} diff --git a/plugins/video-on-login-screen/js/vide/CHANGELOG.md b/plugins/video-on-login-screen/js/vide/CHANGELOG.md new file mode 100644 index 000000000..c922ebbd8 --- /dev/null +++ b/plugins/video-on-login-screen/js/vide/CHANGELOG.md @@ -0,0 +1,41 @@ +### 0.3.0 +* Added the `resizing` option. +* Updated tests. + +### 0.2.1 +* Code refactoring. +* Updated devDependencies. + +### 0.2.0 +* Lots of updates since 0.1.0. +* Improved code linting. +* Cleaned up the repository. +* Added bower dependencies. +* Updated devDependencies. + +### 0.1.4 +* Fixed wrong URL parsing. +* Changed main files in bower.json. + +### 0.1.3 +* Path argument can receive list of sources. +* Strings with options and pathes can be passed to the constructor directly. +* Added `posterType: none` value. +* Updated README. +* Updated JSDoc. +* Updated tests. +* Updated examples. +* Added CONTRIBUTING.md +* Other small fixes and optimizations. + +### 0.1.2 +* Restored `posterType` option to specify poster image type. + +### 0.1.1 +* Support of absolute URLs (#10). +* Fixed the CORS issue (#11). +* Fixed the destroy method. +* Fixed parsing of empty options. +* Poster and video positions are the same now. +* Syntax and behavior of the position option are similar to the CSS `background-position` property. +* Add support of the `.jpeg` extension. diff --git a/plugins/video-on-login-screen/js/vide/CONTRIBUTING.md b/plugins/video-on-login-screen/js/vide/CONTRIBUTING.md new file mode 100644 index 000000000..3900d1fe7 --- /dev/null +++ b/plugins/video-on-login-screen/js/vide/CONTRIBUTING.md @@ -0,0 +1,11 @@ +CONTRIBUTING +==== + +1. Fork. +2. Run `npm install`. +3. Make your changes on the `src` folder. +4. Update tests. +5. Run `npm test`, make sure everything is okay. +6. Submit a pull request to the master branch. + +Thanks. diff --git a/plugins/video-on-login-screen/js/vide/MIT-LICENSE b/plugins/video-on-login-screen/js/vide/MIT-LICENSE new file mode 100644 index 000000000..f03343d6d --- /dev/null +++ b/plugins/video-on-login-screen/js/vide/MIT-LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Ilya Makarov, http://vodkabears.github.io + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/plugins/video-on-login-screen/js/vide/README.md b/plugins/video-on-login-screen/js/vide/README.md new file mode 100644 index 000000000..546d81975 --- /dev/null +++ b/plugins/video-on-login-screen/js/vide/README.md @@ -0,0 +1,140 @@ +[![Bower version](https://badge.fury.io/bo/vide.svg)](http://badge.fury.io/bo/vide) +[![Travis](https://travis-ci.org/VodkaBears/Vide.svg?branch=master)](https://travis-ci.org/VodkaBears/Vide) +[![devDependency Status](https://david-dm.org/vodkabears/vide/dev-status.svg)](https://david-dm.org/vodkabears/vide#info=devDependencies) +Vide +==== + +Easy as hell jQuery plugin for video backgrounds. + +## Notes + +* All modern desktop browsers are supported. +* IE9+ +* iOS plays video from a browser only in the native player. So video for iOS is disabled, only fullscreen poster will be used. +* Some android devices play video, some not — go figure. So video for android is disabled, only fullscreen poster will be used. + +## Instructions + +Download it from [GitHub](https://github.com/VodkaBears/Vide/releases/latest) or via Bower: +`bower install vide` + +Include plugin: `` + +Prepare your video in several formats like '.webm', '.mp4' for cross browser compatability, also add a poster with `.jpg`, `.png` or `.gif` extension: +``` +path/ +├── to/ +│ ├── video.mp4 +│ ├── video.ogv +│ ├── video.webm +│ └── video.jpg +``` + +Add `data-vide-bg` attribute with a path to the video and poster without extension, video and poster must have the same name. Add `data-vide-options` to pass vide options, if you need it. By default video is muted, looped and starts automatically. +```html +
+
+``` + +Also you can set extended path: +```html +
+
+``` + +In some situations it can be helpful to initialize it with JS because Vide doesn't have mutation observers: +```js +$("#myBlock").vide("path/to/video"); +$("#myBlock").vide("path/to/video", { +...options... +}); +$("#myBlock").vide({ + mp4: path/to/video1, + webm: path/to/video2, + ogv: path/to/video3, + poster: path/to/poster +}, { +...options... +}); +$("#myBlock").vide("extended path as a string", "options as a string"); +``` + +Easy as hell. + +## Options + +Below is a complete list of options and matching default values: + +```js +$("#yourElement").vide({ + volume: 1, + playbackRate: 1, + muted: true, + loop: true, + autoplay: true, + position: "50% 50%", // Similar to the CSS `background-position` property. + posterType: "detect", // Poster image type. "detect" — auto-detection; "none" — no poster; "jpg", "png", "gif",... - extensions. + resizing: true // Auto-resizing, read: https://github.com/VodkaBears/Vide#resizing +}); +``` + +## Methods + +Below is a complete list of methods: + +```js +// Get instance of the plugin +var instance = $("#yourElement").data("vide"); + +// Get video element of the background. Do what you want. +instance.getVideoObject(); + +// Resize video background. +// It calls automatically, if window resize (or element, if you will use something like https://github.com/cowboy/jquery-resize). +instance.resize(); + +// Destroy plugin instance +instance.destroy(); +``` + +## Resizing + +The Vide plugin resizes if the window resizes. If you will use something like https://github.com/cowboy/jquery-resize, it will resize automatically when the container resizes. Or simply use `resize()` method whenever you need. + +Set the `resizing` option to false to disable auto-resizing. + +## Encoding video + +http://diveintohtml5.info/video.html#miro + +## Ruby Gem + +[Vider](https://github.com/wazery/vider) by Islam Wazery. + +## License + +``` +The MIT License (MIT) + +Copyright (c) 2014 Ilya Makarov, http://vodkabears.github.io + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` diff --git a/plugins/video-on-login-screen/js/vide/bower.json b/plugins/video-on-login-screen/js/vide/bower.json new file mode 100644 index 000000000..7d4c47379 --- /dev/null +++ b/plugins/video-on-login-screen/js/vide/bower.json @@ -0,0 +1,40 @@ +{ + "name": "vide", + "version": "0.3.0", + "homepage": "http://vodkabears.github.io/vide/", + "authors": [ + "Ilya Makarov " + ], + "description": "Easy as hell jQuery plugin for video backgrounds.", + "main": "dist/jquery.vide.js", + "ignore": [ + "**/.*", + "examples/", + "libs/", + "src/", + "test/", + "*.md", + "Gruntfile.js", + "package.json", + "vide.jquery.json" + ], + "keywords": [ + "jquery", + "plugin", + "jquery-plugin", + "video", + "background", + "ui", + "responsive", + "declarative" + ], + "license": "MIT", + "dependencies": { + "jquery": "*" + }, + "devDependencies": { + "qunit": "~1.15.0", + "jquery": "jquery#^1.11.1", + "jquery2": "jquery#^2.1.1" + } +} diff --git a/plugins/video-on-login-screen/js/vide/jquery.vide.js b/plugins/video-on-login-screen/js/vide/jquery.vide.js new file mode 100644 index 000000000..cc5f29dfc --- /dev/null +++ b/plugins/video-on-login-screen/js/vide/jquery.vide.js @@ -0,0 +1,442 @@ +/* + * Vide - v0.3.0 + * Easy as hell jQuery plugin for video backgrounds. + * http://vodkabears.github.io/vide/ + * + * Made by Ilya Makarov + * Under MIT License + */ +!(function($, window, document, navigator) { + "use strict"; + + /** + * Vide settings + * @private + */ + var pluginName = "vide", + defaults = { + volume: 1, + playbackRate: 1, + muted: true, + loop: true, + autoplay: true, + position: "50% 50%", + posterType: "detect", + resizing: true + }, + + // is iOs? + isIOS = /iPad|iPhone|iPod/i.test(navigator.userAgent), + + // is Android? + isAndroid = /Android/i.test(navigator.userAgent); + + /** + * Parse string with options + * @param {String} str + * @returns {Object|String} + * @private + */ + function parseOptions(str) { + var obj = {}, + delimiterIndex, + option, + prop, val, + arr, len, + i; + + // remove spaces around delimiters and split + arr = str.replace(/\s*:\s*/g, ":").replace(/\s*,\s*/g, ",").split(","); + + // parse string + for (i = 0, len = arr.length; i < len; i++) { + option = arr[i]; + + // Ignore urls and string without colon delimiters + if (option.search(/^(http|https|ftp):\/\//) !== -1 || + option.search(":") === -1) + { + break; + } + + delimiterIndex = option.indexOf(":"); + prop = option.substring(0, delimiterIndex); + val = option.substring(delimiterIndex + 1); + + // if val is an empty string, make it undefined + if (!val) { + val = undefined; + } + + // convert string value if it is like a boolean + if (typeof val === "string") { + val = val === "true" || (val === "false" ? false : val); + } + + // convert string value if it is like a number + if (typeof val === "string") { + val = !isNaN(val) ? +val : val; + } + + obj[prop] = val; + } + + // if nothing is parsed + if (prop == null && val == null) { + return str; + } + + return obj; + } + + /** + * Parse position option + * @param {String} str + * @returns {Object} + * @private + */ + function parsePosition(str) { + str = "" + str; + + // default value is a center + var args = str.split(/\s+/), + x = "50%", y = "50%", + len, arg, + i; + + for (i = 0, len = args.length; i < len; i++) { + arg = args[i]; + + // convert values + if (arg === "left") { + x = "0%"; + } else if (arg === "right") { + x = "100%"; + } else if (arg === "top") { + y = "0%"; + } else if (arg === "bottom") { + y = "100%"; + } else if (arg === "center") { + if (i === 0) { + x = "50%"; + } else { + y = "50%"; + } + } else { + if (i === 0) { + x = arg; + } else { + y = arg; + } + } + } + + return { x: x, y: y }; + } + + /** + * Search poster + * @param {String} path + * @param {Function} callback + * @private + */ + function findPoster(path, callback) { + var onLoad = function() { + callback(this.src); + }; + + $("").load(onLoad); + $("").load(onLoad); + $("").load(onLoad); + $("").load(onLoad); + } + + /** + * Vide constructor + * @param {HTMLElement} element + * @param {Object|String} path + * @param {Object|String} options + * @constructor + */ + function Vide(element, path, options) { + this.$element = $(element); + + // parse path + if (typeof path === "string") { + path = parseOptions(path); + } + + // parse options + if (!options) { + options = {}; + } else if (typeof options === "string") { + options = parseOptions(options); + } + + // remove extension + if (typeof path === "string") { + path = path.replace(/\.\w*$/, ""); + } else if (typeof path === "object") { + for (var i in path) { + if (path.hasOwnProperty(i)) { + path[i] = path[i].replace(/\.\w*$/, ""); + } + } + } + + this.settings = $.extend({}, defaults, options); + this.path = path; + + this.init(); + } + + /** + * Initialization + * @public + */ + Vide.prototype.init = function() { + var vide = this, + position = parsePosition(vide.settings.position), + sources, + poster; + + // Set video wrapper styles + vide.$wrapper = $("
").css({ + "position": "absolute", + "z-index": -1, + "top": 0, + "left": 0, + "bottom": 0, + "right": 0, + "overflow": "hidden", + "-webkit-background-size": "cover", + "-moz-background-size": "cover", + "-o-background-size": "cover", + "background-size": "cover", + "background-repeat": "no-repeat", + "background-position": position.x + " " + position.y + }); + + // Get poster path + poster = vide.path; + if (typeof vide.path === "object") { + if (vide.path.poster) { + poster = vide.path.poster; + } else { + if (vide.path.mp4) { + poster = vide.path.mp4; + } else if (vide.path.webm) { + poster = vide.path.webm; + } else if (vide.path.ogv) { + poster = vide.path.ogv; + } + } + } + + // Set video poster +// if (vide.settings.posterType === "detect") { +// findPoster(poster, function(url) { +// vide.$wrapper.css("background-image", "url(" + url + ")"); +// }); +// } else if (vide.settings.posterType !== "none") { +// vide.$wrapper +// .css("background-image", "url(" + poster + "." + vide.settings.posterType + ")"); +// } + + // if parent element has a static position, make it relative + if (vide.$element.css("position") === "static") { + vide.$element.css("position", "relative"); + } + + vide.$element.prepend(vide.$wrapper); + + if (!isIOS && !isAndroid) { + sources = ""; + + if (typeof vide.path === "object") { + if (vide.path.mp4) { + sources += ""; + } + if (vide.path.webm) { + sources += ""; + } + if (vide.path.ogv) { + sources += ""; + } + + vide.$video = $(""); + } else { + vide.$video = $(""); + } + + // Disable visibility, while loading + vide.$video.css("visibility", "hidden"); + + // Set video properties + vide.$video.prop({ + autoplay: vide.settings.autoplay, + loop: vide.settings.loop, + volume: vide.settings.volume, + muted: vide.settings.muted, + playbackRate: vide.settings.playbackRate + }); + + // Append video + vide.$wrapper.append(vide.$video); + + // Video alignment + vide.$video.css({ + "margin": "auto", + "position": "absolute", + "z-index": -1, + "top": position.y, + "left": position.x, + "-webkit-transform": "translate(-" + position.x + ", -" + position.y + ")", + "-ms-transform": "translate(-" + position.x + ", -" + position.y + ")", + "transform": "translate(-" + position.x + ", -" + position.y + ")" + }); + + // resize video, when it's loaded + vide.$video.bind("loadedmetadata." + pluginName, function() { + vide.$video.css("visibility", "visible"); + vide.resize(); + vide.$wrapper.css("background-image", "none"); + }); + + // resize event is available only for 'window', + // use another code solutions to detect DOM elements resizing + vide.$element.bind("resize." + pluginName, function() { + if (vide.settings.resizing) { + vide.resize(); + } + }); + } + }; + + /** + * Get video element of the background + * @returns {HTMLVideoElement|null} + * @public + */ + Vide.prototype.getVideoObject = function() { + return this.$video ? this.$video[0] : null; + }; + + /** + * Resize video background + * @public + */ + Vide.prototype.resize = function() { + if (!this.$video) { + return; + } + + var + // get native video size + videoHeight = this.$video[0].videoHeight, + videoWidth = this.$video[0].videoWidth, + + // get wrapper size + wrapperHeight = this.$wrapper.height(), + wrapperWidth = this.$wrapper.width(); + + if (wrapperWidth / videoWidth > wrapperHeight / videoHeight) { + this.$video.css({ + "width": wrapperWidth + 2, + + // +2 pixels to prevent empty space after transformation + "height": "auto" + }); + } else { + this.$video.css({ + "width": "auto", + + // +2 pixels to prevent empty space after transformation + "height": wrapperHeight + 2 + }); + } + }; + + /** + * Destroy video background + * @public + */ + Vide.prototype.destroy = function() { + this.$element.unbind(pluginName); + + if (this.$video) { + this.$video.unbind(pluginName); + } + + delete $[pluginName].lookup[this.index]; + this.$element.removeData(pluginName); + this.$wrapper.remove(); + }; + + /** + * Special plugin object for instances. + * @type {Object} + * @public + */ + $[pluginName] = { + lookup: [] + }; + + /** + * Plugin constructor + * @param {Object|String} path + * @param {Object|String} options + * @returns {JQuery} + * @constructor + */ + $.fn[pluginName] = function(path, options) { + var instance; + + this.each(function() { + instance = $.data(this, pluginName); + + if (instance) { + + // destroy plugin instance if exists + instance.destroy(); + } + + // create plugin instance + instance = new Vide(this, path, options); + instance.index = $[pluginName].lookup.push(instance) - 1; + $.data(this, pluginName, instance); + }); + + return this; + }; + + $(document).ready(function() { + + // window resize event listener + $(window).bind("resize." + pluginName, function() { + for (var len = $[pluginName].lookup.length, i = 0, instance; i < len; i++) { + instance = $[pluginName].lookup[i]; + + if (instance && instance.settings.resizing) { + instance.resize(); + } + } + }); + + // Auto initialization. + // Add 'data-vide-bg' attribute with a path to the video without extension. + // Also you can pass options throw the 'data-vide-options' attribute. + // 'data-vide-options' must be like "muted: false, volume: 0.5". + $(document).find("[data-" + pluginName + "-bg]").each(function(i, element) { + var $element = $(element), + options = $element.data(pluginName + "-options"), + path = $element.data(pluginName + "-bg"); + + $element[pluginName](path, options); + }); + }); +})(window.jQuery, window, document, navigator); diff --git a/plugins/video-on-login-screen/js/vide/jquery.vide.min.js b/plugins/video-on-login-screen/js/vide/jquery.vide.min.js new file mode 100644 index 000000000..20f3182cb --- /dev/null +++ b/plugins/video-on-login-screen/js/vide/jquery.vide.min.js @@ -0,0 +1,9 @@ +/* + * Vide - v0.3.0 + * Easy as hell jQuery plugin for video backgrounds. + * http://vodkabears.github.io/vide/ + * + * Made by Ilya Makarov + * Under MIT License + */ +!function(a,b,c,d){"use strict";function e(a){var b,c,d,e,f,g,h,i={};for(f=a.replace(/\s*:\s*/g,":").replace(/\s*,\s*/g,",").split(","),h=0,g=f.length;g>h&&(c=f[h],-1===c.search(/^(http|https|ftp):\/\//)&&-1!==c.search(":"));h++)b=c.indexOf(":"),d=c.substring(0,b),e=c.substring(b+1),e||(e=void 0),"string"==typeof e&&(e="true"===e||("false"===e?!1:e)),"string"==typeof e&&(e=isNaN(e)?e:+e),i[d]=e;return null==d&&null==e?a:i}function f(a){a=""+a;var b,c,d,e=a.split(/\s+/),f="50%",g="50%";for(d=0,b=e.length;b>d;d++)c=e[d],"left"===c?f="0%":"right"===c?f="100%":"top"===c?g="0%":"bottom"===c?g="100%":"center"===c?0===d?f="50%":g="50%":0===d?f=c:g=c;return{x:f,y:g}}function g(b,c){var d=function(){c(this.src)};a("").load(d),a("").load(d),a("").load(d),a("").load(d)}function h(b,c,d){if(this.$element=a(b),"string"==typeof c&&(c=e(c)),d?"string"==typeof d&&(d=e(d)):d={},"string"==typeof c)c=c.replace(/\.\w*$/,"");else if("object"==typeof c)for(var f in c)c.hasOwnProperty(f)&&(c[f]=c[f].replace(/\.\w*$/,""));this.settings=a.extend({},j,d),this.path=c,this.init()}var i="vide",j={volume:1,playbackRate:1,muted:!0,loop:!0,autoplay:!0,position:"50% 50%",posterType:"detect",resizing:!0},k=/iPad|iPhone|iPod/i.test(d.userAgent),l=/Android/i.test(d.userAgent);h.prototype.init=function(){var b,c,d=this,e=f(d.settings.position);d.$wrapper=a("
").css({position:"absolute","z-index":-1,top:0,left:0,bottom:0,right:0,overflow:"hidden","-webkit-background-size":"cover","-moz-background-size":"cover","-o-background-size":"cover","background-size":"cover","background-repeat":"no-repeat","background-position":e.x+" "+e.y}),c=d.path,"object"==typeof d.path&&(d.path.poster?c=d.path.poster:d.path.mp4?c=d.path.mp4:d.path.webm?c=d.path.webm:d.path.ogv&&(c=d.path.ogv)),"detect"===d.settings.posterType?g(c,function(a){d.$wrapper.css("background-image","url("+a+")")}):"none"!==d.settings.posterType&&d.$wrapper.css("background-image","url("+c+"."+d.settings.posterType+")"),"static"===d.$element.css("position")&&d.$element.css("position","relative"),d.$element.prepend(d.$wrapper),k||l||(b="","object"==typeof d.path?(d.path.mp4&&(b+=""),d.path.webm&&(b+=""),d.path.ogv&&(b+=""),d.$video=a("")):d.$video=a(""),d.$video.css("visibility","hidden"),d.$video.prop({autoplay:d.settings.autoplay,loop:d.settings.loop,volume:d.settings.volume,muted:d.settings.muted,playbackRate:d.settings.playbackRate}),d.$wrapper.append(d.$video),d.$video.css({margin:"auto",position:"absolute","z-index":-1,top:e.y,left:e.x,"-webkit-transform":"translate(-"+e.x+", -"+e.y+")","-ms-transform":"translate(-"+e.x+", -"+e.y+")",transform:"translate(-"+e.x+", -"+e.y+")"}),d.$video.bind("loadedmetadata."+i,function(){d.$video.css("visibility","visible"),d.resize(),d.$wrapper.css("background-image","none")}),d.$element.bind("resize."+i,function(){d.settings.resizing&&d.resize()}))},h.prototype.getVideoObject=function(){return this.$video?this.$video[0]:null},h.prototype.resize=function(){if(this.$video){var a=this.$video[0].videoHeight,b=this.$video[0].videoWidth,c=this.$wrapper.height(),d=this.$wrapper.width();this.$video.css(d/b>c/a?{width:d+2,height:"auto"}:{width:"auto",height:c+2})}},h.prototype.destroy=function(){this.$element.unbind(i),this.$video&&this.$video.unbind(i),delete a[i].lookup[this.index],this.$element.removeData(i),this.$wrapper.remove()},a[i]={lookup:[]},a.fn[i]=function(b,c){var d;return this.each(function(){d=a.data(this,i),d&&d.destroy(),d=new h(this,b,c),d.index=a[i].lookup.push(d)-1,a.data(this,i,d)}),this},a(c).ready(function(){a(b).bind("resize."+i,function(){for(var b,c=a[i].lookup.length,d=0;c>d;d++)b=a[i].lookup[d],b&&b.settings.resizing&&b.resize()}),a(c).find("[data-"+i+"-bg]").each(function(b,c){var d=a(c),e=d.data(i+"-options"),f=d.data(i+"-bg");d[i](f,e)})})}(window.jQuery,window,document,navigator); \ No newline at end of file diff --git a/plugins/video-on-login-screen/js/video-on-login.js b/plugins/video-on-login-screen/js/video-on-login.js new file mode 100644 index 000000000..487128998 --- /dev/null +++ b/plugins/video-on-login-screen/js/video-on-login.js @@ -0,0 +1,47 @@ +(function ($, window) { + + $(function () { + + if (window.rl && window.rl && !window.rl.settingsGet('Auth')) + { + var + iRate = 1, + sRate = window.rl.pluginSettingsGet('video-on-login-screen', 'playback_rate') + ; + + switch (sRate) + { + case '25%': + iRate = 0.25; + break; + case '50%': + iRate = 0.5; + break; + case '75%': + iRate = 0.75; + break; + case '125%': + iRate = 1.25; + break; + case '150%': + iRate = 1.5; + break; + case '200%': + iRate = 2; + break; + } + + $('#rl-bg').vide({ + 'mp4': window.rl.pluginSettingsGet('video-on-login-screen', 'mp4_file') || '', + 'webm': window.rl.pluginSettingsGet('video-on-login-screen', 'webm_file') || '', + 'ogv': window.rl.pluginSettingsGet('video-on-login-screen', 'ogv_file') || '' + }, { + playbackRate: iRate, + muted: !!window.rl.pluginSettingsGet('video-on-login-screen', 'muted') + }); + } + + }); + +}($, window)); + diff --git a/rainloop/v/0.0.0/app/domains/default.ini.dist b/rainloop/v/0.0.0/app/domains/default.ini.dist new file mode 100644 index 000000000..d0e283504 --- /dev/null +++ b/rainloop/v/0.0.0/app/domains/default.ini.dist @@ -0,0 +1,7 @@ +imap_host = "IMAP_HOST" +imap_port = IMAP_PORT +imap_secure = "SSL" +smtp_host = "SMTP_HOST" +smtp_port = SMTP_PORT +smtp_secure = "SSL" +smtp_auth = On diff --git a/rainloop/v/0.0.0/app/domains/disabled b/rainloop/v/0.0.0/app/domains/disabled index e69de29bb..0370511f7 100644 --- a/rainloop/v/0.0.0/app/domains/disabled +++ b/rainloop/v/0.0.0/app/domains/disabled @@ -0,0 +1 @@ +outlook.com,qq.com,yahoo.com \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/handle.php b/rainloop/v/0.0.0/app/handle.php index fb4483642..ebb703d10 100644 --- a/rainloop/v/0.0.0/app/handle.php +++ b/rainloop/v/0.0.0/app/handle.php @@ -2,63 +2,91 @@ if (!\defined('RAINLOOP_APP_LIBRARIES_PATH')) { - \define('RAINLOOP_APP_LIBRARIES_PATH', \rtrim(\realpath(__DIR__), '\\/').'/libraries/'); + \define('RAINLOOP_APP_PATH', \rtrim(\realpath(__DIR__), '\\/').'/'); + \define('RAINLOOP_APP_LIBRARIES_PATH', RAINLOOP_APP_PATH.'libraries/'); \define('RAINLOOP_MB_SUPPORTED', \function_exists('mb_strtoupper')); - \spl_autoload_register(function ($sClassName) { + \define('RAINLOOP_INCLUDE_AS_API_DEF', isset($_ENV['RAINLOOP_INCLUDE_AS_API']) && $_ENV['RAINLOOP_INCLUDE_AS_API']); - if (0 === \strpos($sClassName, 'RainLoop') && false !== \strpos($sClassName, '\\')) + if (!defined('RL_BACKWARD_CAPABILITY')) + { + \define('RL_BACKWARD_CAPABILITY', true); + include_once RAINLOOP_APP_LIBRARIES_PATH.'RainLoop/Common/BackwardCapability/Account.php'; + } + + /** + * @param string $sClassName + * + * @return mixed + */ + function rainLoopSplAutoloadNamespaces() + { + return RAINLOOP_INCLUDE_AS_API_DEF ? array('RainLoop') : + array('RainLoop', 'Facebook', 'GuzzleHttp', 'Symfony', 'PHPThumb', 'Sabre'); + } + + /** + * @param string $sClassName + * + * @return mixed + */ + function rainLoopSplAutoloadRegisterFunction($sClassName) + { + if ($sClassName && '\\' === $sClassName[0]) { - return include RAINLOOP_APP_LIBRARIES_PATH.'RainLoop/'.\str_replace('\\', '/', \substr($sClassName, 9)).'.php'; + $sClassName = \substr($sClassName, 1); } - else if (0 === \strpos($sClassName, 'Facebook') && false !== \strpos($sClassName, '\\')) + + foreach (rainLoopSplAutoloadNamespaces() as $sNamespaceName) { - return include RAINLOOP_APP_LIBRARIES_PATH.'Facebook/'.\str_replace('\\', '/', \substr($sClassName, 9)).'.php'; - } - else if (0 === \strpos($sClassName, 'GuzzleHttp') && false !== \strpos($sClassName, '\\')) - { - return include RAINLOOP_APP_LIBRARIES_PATH.'GuzzleHttp/'.\str_replace('\\', '/', \substr($sClassName, 11)).'.php'; - } - else if (0 === \strpos($sClassName, 'Sabre') && false !== \strpos($sClassName, '\\')) - { - if (!RAINLOOP_MB_SUPPORTED && !defined('RL_MB_FIXED')) + if (0 === \strpos($sClassName, $sNamespaceName.'\\')) { - \define('RL_MB_FIXED', true); - include_once RAINLOOP_APP_LIBRARIES_PATH.'RainLoop/Common/MbStringFix.php'; - } + if ('Sabre' === $sNamespaceName && !RAINLOOP_MB_SUPPORTED && !defined('RL_MB_FIXED')) + { + \define('RL_MB_FIXED', true); + include_once RAINLOOP_APP_LIBRARIES_PATH.'RainLoop/Common/MbStringFix.php'; + } - return include RAINLOOP_APP_LIBRARIES_PATH.'Sabre/'.\str_replace('\\', '/', \substr($sClassName, 6)).'.php'; + return include RAINLOOP_APP_LIBRARIES_PATH.\strtr($sClassName, '\\', '/').'.php'; + } } return false; - }); -} - -if (\class_exists('RainLoop\Service')) -{ - $oException = null; - if (!\class_exists('MailSo\Version')) - { - try - { - include APP_VERSION_ROOT_PATH.'app/libraries/MailSo/MailSo.php'; - } - catch (\Exception $oException) {} } - if (!$oException) + \spl_autoload_register('rainLoopSplAutoloadRegisterFunction', false); +} + +if (\class_exists('RainLoop\Api')) +{ + if (!\class_exists('MailSo\Version')) { - if (isset($_ENV['RAINLOOP_INCLUDE_AS_API']) && $_ENV['RAINLOOP_INCLUDE_AS_API']) + include APP_VERSION_ROOT_PATH.'app/libraries/MailSo/MailSo.php'; + } + + if (\class_exists('MailSo\Version')) + { + if (RAINLOOP_INCLUDE_AS_API_DEF) { - $_ENV['RAINLOOP_INCLUDE_AS_API'] = false; - \RainLoop\Api::Handle(); + if (!\defined('APP_API_STARTED')) + { + \define('APP_API_STARTED', true); + + \RainLoop\Api::Handle(); + } } else if (!\defined('APP_STARTED')) { \define('APP_STARTED', true); - \RainLoop\Service::NewInstance()->Handle(); + + \RainLoop\Api::Handle(); + \RainLoop\Service::Handle(); + + \RainLoop\Api::ExitOnEnd(); } } } - -return ''; \ No newline at end of file +else if (\function_exists('rainLoopSplAutoloadRegisterFunction')) +{ + \spl_autoload_unregister('rainLoopSplAutoloadRegisterFunction'); +} diff --git a/rainloop/v/0.0.0/app/i18n/langs.ini b/rainloop/v/0.0.0/app/i18n/langs.ini index c74aad33e..9f19d3d74 100644 --- a/rainloop/v/0.0.0/app/i18n/langs.ini +++ b/rainloop/v/0.0.0/app/i18n/langs.ini @@ -79,6 +79,31 @@ LANG_ZH_HK = "中文(香港)" LANG_KO_KR = "한국어" +LANG_BG = "Български" +LANG_BG_BG = "Български" + +LANG_VI = "Tiếng Việt" +LANG_VI_VI = "Tiếng Việt" +LANG_VI_VN = "Tiếng Việt" + +LANG_CZ = "Čeština" +LANG_CZ_CZ = "Čeština" +LANG_CS = "Čeština" +LANG_CS_CS = "Čeština" +LANG_CS_CZ = "Čeština" +LANG_CZ_CS = "Čeština" + +LANG_HR = "Hrvatski" +LANG_HR_HR = "Hrvatski" + +LANG_SR = "Српски" +LANG_SR_SR = "Српски" +LANG_SR_RS = "Српски" + +LANG_BS = "Bosanski" +LANG_BS_BS = "Bosanski" +LANG_BS_BA = "Bosanski" + [LANGS_NAMES_EN] LANG_EN = "English" LANG_EN_US = "English (US)" @@ -160,3 +185,28 @@ LANG_ZH_CN = "Simplified Chinese (China)" LANG_ZH_HK = "Traditional Chinese (Hong Kong)" LANG_KO_KR = "Korean" + +LANG_BG = "Bulgarian" +LANG_BG_BG = "Bulgarian" + +LANG_VI = "Vietnamese" +LANG_VI_VI = "Vietnamese" +LANG_VI_VN = "Vietnamese" + +LANG_CZ = "Czech" +LANG_CZ_CZ = "Czech" +LANG_CS = "Czech" +LANG_CS_CS = "Czech" +LANG_CS_CZ = "Czech" +LANG_CZ_CS = "Czech" + +LANG_HR = "Croatian" +LANG_HR_HR = "Croatian" + +LANG_SR = "Serbian" +LANG_SR_SR = "Serbian" +LANG_SR_RS = "Serbian" + +LANG_BS = "Bosnian" +LANG_BS_BS = "Bosnian" +LANG_BS_BA = "Bosnian" diff --git a/rainloop/v/0.0.0/app/i18n/moment/pt.js b/rainloop/v/0.0.0/app/i18n/moment/pt.js index 77e1cad31..a96297e4d 100644 --- a/rainloop/v/0.0.0/app/i18n/moment/pt.js +++ b/rainloop/v/0.0.0/app/i18n/moment/pt.js @@ -12,17 +12,17 @@ } }(function (moment) { return moment.lang('pt', { - months : "janeiro_fevereiro_março_abril_maio_junho_julho_agosto_setembro_outubro_novembro_dezembro".split("_"), - monthsShort : "jan_fev_mar_abr_mai_jun_jul_ago_set_out_nov_dez".split("_"), - weekdays : "domingo_segunda-feira_terça-feira_quarta-feira_quinta-feira_sexta-feira_sábado".split("_"), - weekdaysShort : "dom_seg_ter_qua_qui_sex_sáb".split("_"), - weekdaysMin : "dom_2ª_3ª_4ª_5ª_6ª_sáb".split("_"), + months : "Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro".split("_"), + monthsShort : "Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez".split("_"), + weekdays : "Domingo_Segunda-feira_Terça-feira_Quarta-feira_Quinta-feira_Sexta-feira_Sábado".split("_"), + weekdaysShort : "Dom_Seg_Ter_Qua_Qui_Sex_Sáb".split("_"), + weekdaysMin : "Dom_2ª_3ª_4ª_5ª_6ª_Sáb".split("_"), longDateFormat : { LT : "HH:mm", L : "DD/MM/YYYY", LL : "D [de] MMMM [de] YYYY", - LLL : "D [de] MMMM [de] YYYY LT", - LLLL : "dddd, D [de] MMMM [de] YYYY LT" + LLL : "D [de] MMMM [de] YYYY [às] LT", + LLLL : "dddd, D [de] MMMM [de] YYYY [às] LT" }, calendar : { sameDay: '[Hoje às] LT', @@ -37,7 +37,7 @@ sameElse: 'L' }, relativeTime : { - future : "em %s", + future : "daqui a %s", past : "%s atrás", s : "segundos", m : "um minuto", diff --git a/rainloop/v/0.0.0/app/libraries/Facebook/FacebookRedirectLoginHelper.php b/rainloop/v/0.0.0/app/libraries/Facebook/FacebookRedirectLoginHelper.php index 5511ea647..4897a7acd 100644 --- a/rainloop/v/0.0.0/app/libraries/Facebook/FacebookRedirectLoginHelper.php +++ b/rainloop/v/0.0.0/app/libraries/Facebook/FacebookRedirectLoginHelper.php @@ -241,7 +241,8 @@ class FacebookRedirectLoginHelper } $buf = ''; // http://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ - if (is_readable('/dev/urandom')) { + if (false && is_readable('/dev/urandom')) { +// if (is_readable('/dev/urandom')) { $fp = fopen('/dev/urandom', 'rb'); if ($fp !== FALSE) { $buf = fread($fp, $bytes); diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Collection.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Collection.php index 2ddac3c4e..61dcbeb19 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Collection.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Collection.php @@ -1,5 +1,14 @@ tagName), array('svg', 'head', 'link', - 'base', 'meta', 'title', 'style', 'script', 'bgsound', 'keygen', 'source', + 'base', 'meta', 'title', 'style', 'x-script', 'script', 'bgsound', 'keygen', 'source', 'object', 'embed', 'applet', 'mocha', 'iframe', 'frame', 'frameset', 'video', 'audio')) && isset($oElement->parentNode)) { @$oElement->parentNode->removeChild($oElement); @@ -537,8 +546,8 @@ class HtmlUtils // } foreach (array( - 'id', 'class', 'contenteditable', 'designmode', 'formaction', 'data-bind', 'xmlns', - 'srcset' + 'id', 'class', 'contenteditable', 'designmode', 'formaction', + 'data-bind', 'xmlns', 'srcset' ) as $sAttr) { @$oElement->removeAttribute($sAttr); @@ -557,14 +566,15 @@ class HtmlUtils if ($oElement->hasAttribute('href')) { $sHref = \trim($oElement->getAttribute('href')); - if (!\preg_match('/^(http[s]?|ftp|skype|mailto):/i', $sHref)) + if (!\preg_match('/^(http[s]?|ftp|skype|mailto):/i', $sHref) && '//' !== \substr($sHref, 0, 2)) { $oElement->setAttribute('data-x-broken-href', $sHref); $oElement->setAttribute('href', 'javascript:false'); } - else if ('a' === $sTagNameLower) + + if ('a' === $sTagNameLower) { - $oElement->setAttribute('rel', 'external'); + $oElement->setAttribute('rel', 'external nofollow'); } } @@ -585,7 +595,7 @@ class HtmlUtils } else { - if (\preg_match('/http[s]?:\/\//i', $sSrc)) + if (\preg_match('/^http[s]?:\/\//i', $sSrc) || '//' === \substr($sSrc, 0, 2)) { if ($bDoNotReplaceExternalUrl) { @@ -606,7 +616,7 @@ class HtmlUtils $bHasExternals = true; } - else if ('data:image/' === \strtolower(\substr(\trim($sSrc), 0, 11))) + else if ('data:image/' === \strtolower(\substr($sSrc, 0, 11))) { $oElement->setAttribute('src', $sSrc); } @@ -736,7 +746,7 @@ class HtmlUtils { $oElement->removeAttribute('data-x-additional-src'); } - + if ($oElement->hasAttribute('data-x-additional-style-url')) { $oElement->removeAttribute('data-x-additional-style-url'); diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Http.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Http.php index 6e9c5d311..4294908da 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Http.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Http.php @@ -1,5 +1,14 @@ GetServer($sServerKey, ''); @@ -272,20 +280,37 @@ class Http } /** + * @param bool $bCheckProxy = true + * * @return string */ - public function GetScheme() + public function GetScheme($bCheckProxy = true) { - $sHttps = \strtolower($this->GetServer('HTTPS', '')); - return ('on' === $sHttps || ('' === $sHttps && '443' === (string) $this->GetServer('SERVER_PORT', ''))) ? 'https' : 'http'; + return $this->IsSecure($bCheckProxy) ? 'https' : 'http'; } /** + * @param bool $bCheckProxy = true + * * @return bool */ - public function IsSecure() + public function IsSecure($bCheckProxy = true) { - return ('https' === $this->GetScheme()); + $sHttps = \strtolower($this->GetServer('HTTPS', '')); + if ('on' === $sHttps || ('' === $sHttps && '443' === (string) $this->GetServer('SERVER_PORT', ''))) + { + return true; + } + + if ($bCheckProxy && ( + ('https' === \strtolower($this->GetServer('HTTP_X_FORWARDED_PROTO', ''))) || + ('on' === \strtolower($this->GetServer('HTTP_X_FORWARDED_SSL', ''))) + )) + { + return true; + } + + return false; } /** @@ -300,12 +325,10 @@ class Http $sHost = $this->GetServer('HTTP_HOST', ''); if (0 === \strlen($sHost)) { - $sScheme = $this->GetScheme(); $sName = $this->GetServer('SERVER_NAME'); - $iPort = (int) $this->GetServer('SERVER_PORT'); + $iPort = (int) $this->GetServer('SERVER_PORT', 80); - $sHost = (('http' === $sScheme && 80 === $iPort) || ('https' === $sScheme && 443 === $iPort)) - ? $sName : $sName.':'.$iPort; + $sHost = (\in_array($iPort, array(80, 433))) ? $sName : $sName.':'.$iPort; } if ($bWithoutWWW) @@ -328,11 +351,11 @@ class Http } /** - * @param bool $bCheckProxy = true + * @param bool $bCheckProxy = false * * @return string */ - public function GetClientIp($bCheckProxy = true) + public function GetClientIp($bCheckProxy = false) { $sIp = ''; if ($bCheckProxy && null !== $this->GetServer('HTTP_CLIENT_IP', null)) @@ -466,7 +489,7 @@ class Http { $aAddOptions[CURLOPT_HTTPHEADER] = $aOptions[CURLOPT_HTTPHEADER]; } - + \curl_setopt_array($oCurl, $aAddOptions); do @@ -510,7 +533,7 @@ class Http return null === $sNewUrl ? $sUrl : $sNewUrl; } - + /** * @param string $sUrl * @param resource $rFile @@ -540,7 +563,7 @@ class Http { $oLogger->Write('cURL: input resource invalid.', \MailSo\Log\Enumerations\Type::WARNING); } - + return false; } @@ -578,10 +601,10 @@ class Http if ($oLogger) { $oLogger->Write('cUrl: URL: '.$sUrl); - if (isset($aOptions[CURLOPT_HTTPHEADER]) && \is_array($aOptions[CURLOPT_HTTPHEADER]) && 0 < \count($aOptions[CURLOPT_HTTPHEADER])) - { - $oLogger->Write('cUrl: Headers: '.\print_r($aOptions[CURLOPT_HTTPHEADER], true)); - } +// if (isset($aOptions[CURLOPT_HTTPHEADER]) && \is_array($aOptions[CURLOPT_HTTPHEADER]) && 0 < \count($aOptions[CURLOPT_HTTPHEADER])) +// { +// $oLogger->Write('cUrl: Headers: '.\print_r($aOptions[CURLOPT_HTTPHEADER], true)); +// } } \MailSo\Base\Http::DetectAndHackFollowLocationUrl($sUrl, $aOptions, $oLogger); @@ -590,10 +613,10 @@ class Http \curl_setopt_array($oCurl, $aOptions); $bResult = \curl_exec($oCurl); - + $iCode = (int) \curl_getinfo($oCurl, CURLINFO_HTTP_CODE); $sContentType = (string) \curl_getinfo($oCurl, CURLINFO_CONTENT_TYPE); - + if ($oLogger) { $oLogger->Write('cUrl: Request result: '.($bResult ? 'true' : 'false').' (Status: '.$iCode.', ContentType: '.$sContentType.')'); @@ -607,7 +630,7 @@ class Http { \curl_close($oCurl); } - + return $bResult; } @@ -634,7 +657,7 @@ class Http \rewind($rMemFile); return \stream_get_contents($rMemFile); } - + return false; } @@ -677,13 +700,40 @@ class Http return $bResult; } + /** + * @staticvar bool $bCache + */ public function ServerNoCache() { - @\header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); - @\header('Last-Modified: '.\gmdate('D, d M Y H:i:s').' GMT'); - @\header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); - @\header('Cache-Control: post-check=0, pre-check=0', false); - @\header('Pragma: no-cache'); + static $bCache = false; + if (false === $bCache) + { + $bCache = true; + @\header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); + @\header('Last-Modified: '.\gmdate('D, d M Y H:i:s').' GMT'); + @\header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); + @\header('Cache-Control: post-check=0, pre-check=0', false); + @\header('Pragma: no-cache'); + } + } + + /** + * @staticvar bool $bCache + * @param string $sEtag + * @param int $iLastModified + * @param int $iExpires + */ + public function ServerUseCache($sEtag, $iLastModified, $iExpires) + { + static $bCache = false; + if (false === $bCache) + { + $bCache = true; + @\header('Cache-Control: private', true); + @\header('ETag: '.$sEtag, true); + @\header('Last-Modified: '.\gmdate('D, d M Y H:i:s', $iLastModified).' UTC', true); + @\header('Expires: '.\gmdate('D, j M Y H:i:s', $iExpires).' UTC', true); + } } /** diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/LinkFinder.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/LinkFinder.php index 27a99989e..73f927872 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/LinkFinder.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/LinkFinder.php @@ -1,5 +1,14 @@ aSubStreams[$this->iIndex]; } - + return $nNull; } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/StreamWrappers/TempFile.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/StreamWrappers/TempFile.php new file mode 100644 index 000000000..dfe6ff609 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/StreamWrappers/TempFile.php @@ -0,0 +1,161 @@ +rSream = self::$aStreams[$sHashName]; + $bResult = true; + } + else + { + $this->rSream = fopen('php://memory', 'r+b'); + self::$aStreams[$sHashName] = $this->rSream; + + $bResult = true; + + \MailSo\Base\Loader::IncStatistic('CreateStream/TempFile'); + } + } + + return $bResult; + } + + /** + * @return bool + */ + public function stream_close() + { + return -1 !== fseek($this->rSream, 0); + } + + /** + * @return bool + */ + public function stream_flush() + { + return fflush($this->rSream); + } + + /** + * @param int $iLen + * + * @return string + */ + public function stream_read($iLen) + { + return fread($this->rSream, $iLen); + } + + /** + * @param string $sInputString + * + * @return int + */ + public function stream_write($sInputString) + { + return fwrite($this->rSream, $sInputString); + } + + /** + * @return int + */ + public function stream_tell() + { + return ftell($this->rSream); + } + + /** + * @return bool + */ + public function stream_eof() + { + return feof($this->rSream); + } + + /** + * @return array + */ + public function stream_stat() + { + return fstat($this->rSream); + } + + /** + * @param int $iOffset + * @param int $iWhence = SEEK_SET + * + * @return int + */ + public function stream_seek($iOffset, $iWhence = SEEK_SET) + { + return fseek($this->rSream, $iOffset, $iWhence); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/StreamWrappers/Test.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/StreamWrappers/Test.php index 103f49a85..6e8c46e4a 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/StreamWrappers/Test.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/StreamWrappers/Test.php @@ -1,5 +1,14 @@ WriteDump(array( + \MailSo\Log\Logger::SystemLog(array( 'inc' => \strtoupper($sInputFromEncoding), 'out' => \strtoupper($sInputToEncoding).$sIconvOptions, - 'val' => $sInputString, - 'hex' => $sHex + 'val' => 500 < \strlen($sInputString) ? \substr($sInputString, 0, 500) : $sInputString, + 'hex' => 500 < \strlen($sInputString) ? '-- to long --' : \bin2hex($sInputString) ), \MailSo\Log\Enumerations\Type::NOTICE); } @@ -430,7 +476,7 @@ class Utils { return true; } - + return !\preg_match('/[^\x09\x10\x13\x0A\x0D\x20-\x7E]/', $sValue); } @@ -461,7 +507,7 @@ class Utils */ public static function IsUtf8($sValue) { - return (bool) (\function_exists('mb_check_encoding') ? + return (bool) ( \function_exists('mb_check_encoding') ? \mb_check_encoding($sValue, 'UTF-8') : \preg_match('//u', $sValue)); } @@ -517,11 +563,8 @@ class Utils $sValue = $sEncodedValue; if (0 < \strlen($sIncomingCharset)) { - if (\MailSo\Base\Enumerations\Charset::UTF_8 !== $sIncomingCharset && \MailSo\Base\Utils::IsUtf8($sValue)) - { - $sIncomingCharset = \MailSo\Base\Enumerations\Charset::UTF_8; - } - + $sIncomingCharset = \MailSo\Base\Utils::NormalizeCharsetByValue($sIncomingCharset, $sValue); + $sValue = \MailSo\Base\Utils::ConvertEncoding($sValue, $sIncomingCharset, \MailSo\Base\Enumerations\Charset::UTF_8); } @@ -610,11 +653,8 @@ class Utils } else { - if (\MailSo\Base\Enumerations\Charset::UTF_8 !== $aParts[$iIndex][2] && \MailSo\Base\Utils::IsUtf8($aParts[$iIndex][1])) - { - $aParts[$iIndex][2] = \MailSo\Base\Enumerations\Charset::UTF_8; - } - + $aParts[$iIndex][2] = \MailSo\Base\Utils::NormalizeCharsetByValue($aParts[$iIndex][2], $aParts[$iIndex][1]); + $sValue = \str_replace($aParts[$iIndex][0], \MailSo\Base\Utils::ConvertEncoding($aParts[$iIndex][1], $aParts[$iIndex][2], \MailSo\Base\Enumerations\Charset::UTF_8), $sValue); @@ -623,11 +663,7 @@ class Utils if ($bOneCharset && 0 < \strlen($sMainCharset)) { - if (\MailSo\Base\Enumerations\Charset::UTF_8 !== $sMainCharset && \MailSo\Base\Utils::IsUtf8($sValue)) - { - $sMainCharset = \MailSo\Base\Enumerations\Charset::UTF_8; - } - + $sMainCharset = \MailSo\Base\Utils::NormalizeCharsetByValue($sMainCharset, $sValue); $sValue = \MailSo\Base\Utils::ConvertEncoding($sValue, $sMainCharset, \MailSo\Base\Enumerations\Charset::UTF_8); } @@ -712,7 +748,7 @@ class Utils $iNlen = \strlen($sAttrName); $iVlen = \strlen($sValue); - + if (\strlen($sAttrName) + $iVlen > $iLen - 3) { $sections = array(); @@ -728,7 +764,7 @@ class Utils { $sections[$i] = ' '.$sAttrName.'*'.$i.'*='.$sections[$i]; } - + return \implode(";\r\n", $sections); } else @@ -746,7 +782,7 @@ class Utils { $sAttrName = \trim($sAttrName); $sValue = \trim($sValue); - + if (0 < \strlen($sValue) && !\MailSo\Base\Utils::IsAscii($sValue)) { if (!empty($_SERVER['HTTP_USER_AGENT']) && 0 < \strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) @@ -811,7 +847,7 @@ class Utils public static function GetFileExtension($sFileName) { $iLast = \strrpos($sFileName, '.'); - return false === $iLast ? '' : \substr($sFileName, $iLast + 1); + return false === $iLast ? '' : \strtolower(\substr($sFileName, $iLast + 1)); } /** @@ -862,7 +898,6 @@ class Utils 'p7c' => 'application/pkcs7-mime', 'p7m' => 'application/pkcs7-mime', 'p7s' => 'application/pkcs7-signature', - 'ttf' => 'application/x-ttf', 'torrent' => 'application/x-bittorrent', // scripts @@ -943,7 +978,6 @@ class Utils 'flv' => 'video/x-flv', 'qt' => 'video/quicktime', 'mov' => 'video/quicktime', - 'wmv' => 'video/windows-media', 'avi' => 'video/x-msvideo', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', @@ -956,6 +990,7 @@ class Utils 'h263' => 'video/h263', 'h264' => 'video/h264', 'jpgv' => 'video/jpgv', + 'mp4' => 'video/mp4', 'mp4v' => 'video/mp4', 'mpg4' => 'video/mp4', 'ogv' => 'video/ogg', @@ -1153,12 +1188,17 @@ class Utils $sResult = @\json_encode($mInput, $iOpt); if (!\is_string($sResult) || '' === $sResult) { - if (!$oLogger && \MailSo\Config::$SystemLogger instanceof \MailSo\Log\Logger) + if (!$oLogger && \MailSo\Log\Logger::IsSystemEnabled()) { $oLogger = \MailSo\Config::$SystemLogger; } - if ($oLogger instanceof \MailSo\Log\Logger) + if (!($oLogger instanceof \MailSo\Log\Logger)) + { + $oLogger = null; + } + + if ($oLogger) { $oLogger->Write('json_encode: '.\trim( (\MailSo\Base\Utils::FunctionExistsAndEnabled('json_last_error') ? ' [Error Code: '.\json_last_error().']' : ''). @@ -1169,7 +1209,7 @@ class Utils if (\is_array($mInput)) { - if ($oLogger instanceof \MailSo\Log\Logger) + if ($oLogger) { $oLogger->WriteDump($mInput, \MailSo\Log\Enumerations\Type::INFO, 'JSON'); $oLogger->Write('Trying to clear Utf8 before json_encode', \MailSo\Log\Enumerations\Type::INFO, 'JSON'); @@ -1190,7 +1230,7 @@ class Utils */ public static function ClearFileName($sFileName) { - return \MailSo\Base\Utils::ClearNullBite(\preg_replace('/[\s]+/', ' ', + return \MailSo\Base\Utils::ClearNullBite(\preg_replace('/[\s]+/u', ' ', \str_replace(array('"', '/', '\\', '*', '?', '<', '>', '|', ':'), ' ', $sFileName))); } @@ -1205,6 +1245,17 @@ class Utils \str_replace(array('"', '/', '\\', '*', '?', '<', '>', '|', ':'), ' ', $sValue)); } + /** + * @param string $sValue + * + * @return string + */ + public static function Trim($sValue) + { + return \trim(\preg_replace('/^[\x00-\x1F]+/u', '', + \preg_replace('/[\x00-\x1F]+$/u', '', \trim($sValue)))); + } + /** * @param string $sDir * @@ -1408,15 +1459,7 @@ class Utils return $sUtfString; } - $sUtfString = \preg_replace( - '/[\x00-\x08\x10\x0B\x0C\x0E-\x1F\x7F]'. - '|[\x00-\x7F][\x80-\xBF]+'. - '|([\xC0\xC1]|[\xF0-\xFF])[\x80-\xBF]*'. - '|[\xC2-\xDF]((?![\x80-\xBF])|[\x80-\xBF]{2,})'. - '|[\xE0-\xEF](([\x80-\xBF](?![\x80-\xBF]))|(?![\x80-\xBF]{2})|[\x80-\xBF]{3,})/S', - $sReplaceOn, - $sUtfString - ); + $sUtfString = \preg_replace(\MailSo\Base\Utils::$sValidUtf8Regexp, '$1', $sUtfString); $sUtfString = \preg_replace( '/\xE0[\x80-\x9F][\x80-\xBF]'. @@ -1426,15 +1469,16 @@ class Utils $sUtfString = \preg_replace('/\xEF\xBF\xBD/', '?', $sUtfString); $sNewUtfString = false; - if (\MailSo\Base\Utils::IsIconvSupported()) - { - $sNewUtfString = \MailSo\Base\Utils::IconvConvertEncoding($sUtfString, 'UTF-8', 'UTF-8'); - } - else if (\MailSo\Base\Utils::IsMbStringSupported()) + if (false === $sNewUtfString && \MailSo\Base\Utils::IsMbStringSupported()) { $sNewUtfString = \MailSo\Base\Utils::MbConvertEncoding($sUtfString, 'UTF-8', 'UTF-8'); } + if (false === $sNewUtfString && \MailSo\Base\Utils::IsIconvSupported()) + { + $sNewUtfString = \MailSo\Base\Utils::IconvConvertEncoding($sUtfString, 'UTF-8', 'UTF-8'); + } + if (false !== $sNewUtfString) { $sUtfString = $sNewUtfString; @@ -1998,14 +2042,28 @@ class Utils } /** - * @param string $sFunctionName + * @param string|array $mFunctionNameOrNames * * @return bool */ - public static function FunctionExistsAndEnabled($sFunctionName) + public static function FunctionExistsAndEnabled($mFunctionNameOrNames) { static $aCache = null; - if (empty($sFunctionName) || !\function_exists($sFunctionName) || !\is_callable($sFunctionName)) + + if (\is_array($mFunctionNameOrNames)) + { + foreach ($mFunctionNameOrNames as $sFunctionName) + { + if (!\MailSo\Base\Utils::FunctionExistsAndEnabled($sFunctionName)) + { + return false; + } + } + + return true; + } + + if (empty($mFunctionNameOrNames) || !\function_exists($mFunctionNameOrNames) || !\is_callable($mFunctionNameOrNames)) { return false; } @@ -2016,7 +2074,7 @@ class Utils $sDisableFunctions = \is_string($sDisableFunctions) && 0 < \strlen($sDisableFunctions) ? $sDisableFunctions : ''; $aCache = \explode(',', $sDisableFunctions); - $aCache = is_array($aCache) && 0 < count($aCache) ? $aCache : array(); + $aCache = \is_array($aCache) && 0 < \count($aCache) ? $aCache : array(); if (\extension_loaded('suhosin')) { @@ -2024,17 +2082,17 @@ class Utils $sSuhosin = \is_string($sSuhosin) && 0 < \strlen($sSuhosin) ? $sSuhosin : ''; $aSuhosinCache = \explode(',', $sSuhosin); - $aSuhosinCache = is_array($aSuhosinCache) && 0 < count($aSuhosinCache) ? $aSuhosinCache : array(); + $aSuhosinCache = \is_array($aSuhosinCache) && 0 < \count($aSuhosinCache) ? $aSuhosinCache : array(); if (0 < \count($aSuhosinCache)) { - $aCache = array_merge($aCache, $aSuhosinCache); - $aCache = array_unique($aCache); + $aCache = \array_merge($aCache, $aSuhosinCache); + $aCache = \array_unique($aCache); } } } - return !\in_array($sFunctionName, $aCache); + return !\in_array($mFunctionNameOrNames, $aCache); } /** @@ -2109,6 +2167,28 @@ class Utils return \is_string($mResult) && 0 < \strlen($mResult) ? $mResult : ''; } + /** + * @param string $sAdditionalSalt = '' + * + * @return string + */ + public static function Md5Rand($sAdditionalSalt = '') + { + return \md5(\microtime(true).\rand(10000, 99999). + \md5($sAdditionalSalt).\rand(10000, 99999).\microtime(true)); + } + + /** + * @param string $sAdditionalSalt = '' + * + * @return string + */ + public static function Sha1Rand($sAdditionalSalt = '') + { + return \sha1(\microtime(true).\rand(10000, 99999). + \sha1($sAdditionalSalt).\rand(10000, 99999).\microtime(true)); + } + /** * @param string $sData * @param string $sKey @@ -2148,6 +2228,16 @@ class Utils )); } + /** + * @param string $sIp + * + * @return bool + */ + public static function ValidateIP($sIp) + { + return !empty($sIp) && $sIp === @\filter_var($sIp, FILTER_VALIDATE_IP); + } + /** * @return \Net_IDNA2 */ @@ -2212,4 +2302,21 @@ class Utils return ('' === $sUser ? '' : $sUser.'@').$sDomain; } + + /** + * @param string $sPassword + * + * @return bool + */ + public static function PasswordWeaknessCheck($sPassword) + { + $sPassword = \trim($sPassword); + if (6 > \strlen($sPassword)) + { + return false; + } + + $sLine = 'password 123.456 12345678 abc123 qwerty monkey letmein dragon 111.111 baseball iloveyou trustno1 1234567 sunshine master 123.123 welcome shadow ashley football jesus michael ninja mustang password1 123456 123456789 qwerty 111111 1234567 666666 12345678 7777777 123321 654321 1234567890 123123 555555 vkontakte gfhjkm 159753 777777 temppassword qazwsx 1q2w3e 1234 112233 121212 qwertyuiop qq18ww899 987654321 12345 zxcvbn zxcvbnm 999999 samsung ghbdtn 1q2w3e4r 1111111 123654 159357 131313 qazwsxedc 123qwe 222222 asdfgh 333333 9379992 asdfghjkl 4815162342 12344321 88888888 11111111 knopka 789456 qwertyu 1q2w3e4r5t iloveyou vfhbyf marina password qweasdzxc 10203 987654 yfnfif cjkysirj nikita 888888 vfrcbv k.,jdm qwertyuiop[] qwe123 qweasd natasha 123123123 fylhtq q1w2e3 stalker 1111111111 q1w2e3r4 nastya 147258369 147258 fyfcnfcbz 1234554321 1qaz2wsx andrey 111222 147852 genius sergey 7654321 232323 123789 fktrcfylh spartak admin test 123 azerty abc123 lol123 easytocrack1 hello saravn holysh!t test123 tundra_cool2 456 dragon thomas killer root 1111 pass master aaaaaa a monkey daniel asdasd e10adc3949ba59abbe56e057f20f883e changeme computer jessica letmein mirage loulou lol superman shadow admin123 secret administrator sophie kikugalanetroot doudou liverpool hallo sunshine charlie parola 100827092 michael andrew password1 fuckyou matrix cjmasterinf internet hallo123 eminem demo gewinner pokemon abcd1234 guest ngockhoa martin sandra asdf hejsan george qweqwe lollipop lovers q1q1q1 tecktonik naruto 12 password12 password123 password1234 password12345 password123456 password1234567 password12345678 password123456789 000000 maximius 123abc baseball1 football1 soccer princess slipknot 11111 nokia super star 666999 12341234 1234321 135790 159951 212121 zzzzzz 121314 134679 142536 19921992 753951 7007 1111114 124578 19951995 258456 qwaszx zaqwsx 55555 77777 54321 qwert 22222 33333 99999 88888 66666'; + return false === \strpos($sLine, \strtolower($sPassword)); + } } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Validator.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Validator.php index c260565f4..dbaf2ffdb 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Validator.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Validator.php @@ -1,5 +1,14 @@ oDriver = null; - $this->sCacheIndex = ''; - } - - /** - * @return \MailSo\Cache\CacheClient - */ - public static function NewInstance() - { - return new self(); - } - - /** - * @param string $sKey - * @param string $sValue - * - * @return bool - */ - public function Set($sKey, $sValue) - { - return $this->oDriver ? $this->oDriver->Set($sKey.$this->sCacheIndex, $sValue) : false; - } - - /** - * @param string $sKey - * - * @return bool - */ - public function SetTimer($sKey) - { - return $this->Set($sKey.'/TIMER', time()); - } - - /** - * @param string $sKey - * @param string $bClearAfterGet = false - * - * @return string - */ - public function Get($sKey, $bClearAfterGet = false) - { - $sValue = ''; - - if ($this->oDriver) - { - $sValue = $this->oDriver->Get($sKey.$this->sCacheIndex); - } - - if ($bClearAfterGet) - { - $this->Delete($sKey); - } - - return $sValue; - } - - /** - * @param string $sKey - * - * @return int - */ - public function GetTimer($sKey) - { - $iTimer = 0; - $sValue = $this->Get($sKey.'/TIMER'); - if (0 < strlen($sValue) && is_numeric($sValue)) - { - $iTimer = (int) $sValue; - } - - return $iTimer; - } - - /** - * @param string $sKey - * - * @return \MailSo\Cache\CacheClient - */ - public function Delete($sKey) - { - if ($this->oDriver) - { - $this->oDriver->Delete($sKey.$this->sCacheIndex); - } - - return $this; - } - - /** - * @param \MailSo\Cache\DriverInterface $oDriver - * - * @return \MailSo\Cache\CacheClient - */ - public function SetDriver(\MailSo\Cache\DriverInterface $oDriver) - { - $this->oDriver = $oDriver; - - return $this; - } - - /** - * @param int $iTimeToClearInHours = 24 - * - * @return bool - */ - public function GC($iTimeToClearInHours = 24) - { - return $this->oDriver ? $this->oDriver->GC($iTimeToClearInHours) : false; - } - - /** - * @return bool - */ - public function IsInited() - { - return $this->oDriver instanceof \MailSo\Cache\DriverInterface; - } - - /** - * @param string $sCacheIndex - * - * @return \MailSo\Cache\CacheClient - */ - public function SetCacheIndex($sCacheIndex) - { - $this->sCacheIndex = 0 < \strlen($sCacheIndex) ? "\x0".$sCacheIndex : ''; - - return $this; - } -} +oDriver = null; + $this->sCacheIndex = ''; + } + + /** + * @return \MailSo\Cache\CacheClient + */ + public static function NewInstance() + { + return new self(); + } + + /** + * @param string $sKey + * @param string $sValue + * + * @return bool + */ + public function Set($sKey, $sValue) + { + return $this->oDriver ? $this->oDriver->Set($sKey.$this->sCacheIndex, $sValue) : false; + } + + /** + * @param string $sKey + * + * @return bool + */ + public function SetTimer($sKey) + { + return $this->Set($sKey.'/TIMER', time()); + } + + /** + * @param string $sKey + * + * @return bool + */ + public function SetLock($sKey) + { + return $this->Set($sKey.'/LOCK', '1'); + } + + /** + * @param string $sKey + * + * @return bool + */ + public function RemoveLock($sKey) + { + return $this->Set($sKey.'/LOCK', '0'); + } + + /** + * @param string $sKey + * + * @return bool + */ + public function GetLock($sKey) + { + return '1' === $this->Get($sKey.'/LOCK'); + } + + /** + * @param string $sKey + * @param string $bClearAfterGet = false + * + * @return string + */ + public function Get($sKey, $bClearAfterGet = false) + { + $sValue = ''; + + if ($this->oDriver) + { + $sValue = $this->oDriver->Get($sKey.$this->sCacheIndex); + } + + if ($bClearAfterGet) + { + $this->Delete($sKey); + } + + return $sValue; + } + + /** + * @param string $sKey + * + * @return int + */ + public function GetTimer($sKey) + { + $iTimer = 0; + $sValue = $this->Get($sKey.'/TIMER'); + if (0 < strlen($sValue) && is_numeric($sValue)) + { + $iTimer = (int) $sValue; + } + + return $iTimer; + } + + /** + * @param string $sKey + * + * @return \MailSo\Cache\CacheClient + */ + public function Delete($sKey) + { + if ($this->oDriver) + { + $this->oDriver->Delete($sKey.$this->sCacheIndex); + } + + return $this; + } + + /** + * @param \MailSo\Cache\DriverInterface $oDriver + * + * @return \MailSo\Cache\CacheClient + */ + public function SetDriver(\MailSo\Cache\DriverInterface $oDriver) + { + $this->oDriver = $oDriver; + + return $this; + } + + /** + * @param int $iTimeToClearInHours = 24 + * + * @return bool + */ + public function GC($iTimeToClearInHours = 24) + { + return $this->oDriver ? $this->oDriver->GC($iTimeToClearInHours) : false; + } + + /** + * @return bool + */ + public function IsInited() + { + return $this->oDriver instanceof \MailSo\Cache\DriverInterface; + } + + /** + * @param string $sCacheIndex + * + * @return \MailSo\Cache\CacheClient + */ + public function SetCacheIndex($sCacheIndex) + { + $this->sCacheIndex = 0 < \strlen($sCacheIndex) ? "\x0".$sCacheIndex : ''; + + return $this; + } + + /** + * @param bool $bCache = false + * + * @return bool + */ + public function Verify($bCache = false) + { + if ($this->oDriver) + { + $sCacheData = \gmdate('Y-m-d-H'); + if ($bCache && $sCacheData === $this->Get('__verify_key__')) + { + return true; + } + + return $this->Set('__verify_key__', $sCacheData); + } + + return false; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Cache/DriverInterface.php b/rainloop/v/0.0.0/app/libraries/MailSo/Cache/DriverInterface.php index ce80133f0..4789cd6c1 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Cache/DriverInterface.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Cache/DriverInterface.php @@ -1,5 +1,14 @@ generateCachedKey($sKey), (string) $sValue); - } - - /** - * @param string $sKey - * - * @return string - */ - public function Get($sKey) - { - $sValue = \apc_fetch($this->generateCachedKey($sKey)); - return \is_string($sValue) ? $sValue : ''; - } - - /** - * @param string $sKey - * - * @return void - */ - public function Delete($sKey) - { - \apc_delete($this->generateCachedKey($sKey)); - } - - /** - * @param int $iTimeToClearInHours = 24 - * - * @return bool - */ - public function GC($iTimeToClearInHours = 24) - { - if (0 === $iTimeToClearInHours) - { - return \apc_clear_cache('user'); - } - - return false; - } - - /** - * @param string $sKey - * - * @return string - */ - private function generateCachedKey($sKey) - { - return \sha1($sKey); - } -} +sKeyPrefix = $sKeyPrefix; + if (!empty($this->sKeyPrefix)) + { + $this->sKeyPrefix = + \preg_replace('/[^a-zA-Z0-9_]/', '_', rtrim(trim($this->sKeyPrefix), '\\/')).'/'; + } + } + + /** + * @param string $sKeyPrefix = '' + * + * @return \MailSo\Cache\Drivers\APC + */ + public static function NewInstance($sKeyPrefix = '') + { + return new self($sKeyPrefix); + } + + /** + * @param string $sKey + * @param string $sValue + * + * @return bool + */ + public function Set($sKey, $sValue) + { + return \apc_store($this->generateCachedKey($sKey), (string) $sValue); + } + + /** + * @param string $sKey + * + * @return string + */ + public function Get($sKey) + { + $sValue = \apc_fetch($this->generateCachedKey($sKey)); + return \is_string($sValue) ? $sValue : ''; + } + + /** + * @param string $sKey + * + * @return void + */ + public function Delete($sKey) + { + \apc_delete($this->generateCachedKey($sKey)); + } + + /** + * @param int $iTimeToClearInHours = 24 + * + * @return bool + */ + public function GC($iTimeToClearInHours = 24) + { + if (0 === $iTimeToClearInHours) + { + return \apc_clear_cache('user'); + } + + return false; + } + + /** + * @param string $sKey + * + * @return string + */ + private function generateCachedKey($sKey) + { + return $this->sKeyPrefix.\sha1($sKey); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php b/rainloop/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php index 4d15c72fc..abdb125c1 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Cache/Drivers/File.php @@ -1,5 +1,14 @@ sCacheFolder = $sCacheFolder; $this->sCacheFolder = rtrim(trim($this->sCacheFolder), '\\/').'/'; - if (!\is_dir($this->sCacheFolder)) + + $this->sKeyPrefix = $sKeyPrefix; + if (!empty($this->sKeyPrefix)) { - @\mkdir($this->sCacheFolder, 0755); + $this->sKeyPrefix = \str_pad(\preg_replace('/[^a-zA-Z0-9_]/', '_', + rtrim(trim($this->sKeyPrefix), '\\/')), 5, '_'); + + $this->sKeyPrefix = '__/'. + \substr($this->sKeyPrefix, 0, 2).'/'.\substr($this->sKeyPrefix, 2, 2).'/'. + $this->sKeyPrefix.'/'; } } /** * @param string $sCacheFolder + * @param string $sKeyPrefix = '' * * @return \MailSo\Cache\Drivers\File */ - public static function NewInstance($sCacheFolder) + public static function NewInstance($sCacheFolder, $sKeyPrefix = '') { - return new self($sCacheFolder); + return new self($sCacheFolder, $sKeyPrefix); } /** @@ -47,7 +70,8 @@ class File implements \MailSo\Cache\DriverInterface */ public function Set($sKey, $sValue) { - return false !== \file_put_contents($sPath = $this->generateCachedFileName($sKey, true), $sValue); + $sPath = $this->generateCachedFileName($sKey, true); + return '' === $sPath ? false : false !== \file_put_contents($sPath, $sValue); } /** @@ -59,7 +83,7 @@ class File implements \MailSo\Cache\DriverInterface { $sValue = ''; $sPath = $this->generateCachedFileName($sKey); - if (\file_exists($sPath)) + if ('' !== $sPath && \file_exists($sPath)) { $sValue = \file_get_contents($sPath); } @@ -75,7 +99,7 @@ class File implements \MailSo\Cache\DriverInterface public function Delete($sKey) { $sPath = $this->generateCachedFileName($sKey); - if (\file_exists($sPath)) + if ('' !== $sPath && \file_exists($sPath)) { \unlink($sPath); } @@ -83,7 +107,7 @@ class File implements \MailSo\Cache\DriverInterface /** * @param int $iTimeToClearInHours = 24 - * + * * @return bool */ public function GC($iTimeToClearInHours = 24) @@ -93,7 +117,7 @@ class File implements \MailSo\Cache\DriverInterface \MailSo\Base\Utils::RecTimeDirRemove($this->sCacheFolder, 60 * 60 * $iTimeToClearInHours, \time()); return true; } - + return false; } @@ -111,7 +135,7 @@ class File implements \MailSo\Cache\DriverInterface $sKeyPath = \sha1($sKey); $sKeyPath = \substr($sKeyPath, 0, 2).'/'.\substr($sKeyPath, 2, 2).'/'.$sKeyPath; - $sFilePath = $this->sCacheFolder.'/'.$sKeyPath; + $sFilePath = $this->sCacheFolder.$this->sKeyPrefix.$sKeyPath; if ($bMkDir && !\is_dir(\dirname($sFilePath))) { if (!\mkdir(\dirname($sFilePath), 0755, true)) diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Cache/Drivers/Memcache.php b/rainloop/v/0.0.0/app/libraries/MailSo/Cache/Drivers/Memcache.php index e8f32e5c4..35094b488 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Cache/Drivers/Memcache.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Cache/Drivers/Memcache.php @@ -1,120 +1,144 @@ -sHost = $sHost; - $this->iPost = $iPost; - $this->iExpire = 0 < $iExpire ? $iExpire : 43200; - - $this->oMem = new \Memcache(); - if (!$this->oMem->connect($this->sHost, $this->iPost)) - { - $this->oMem = null; - } - } - - /** - * @param string $sHost = '127.0.0.1' - * @param int $iPost = 11211 - * - * @return \MailSo\Cache\Drivers\APC - */ - public static function NewInstance($sHost = '127.0.0.1', $iPost = 11211) - { - return new self($sHost, $iPost); - } - - /** - * @param string $sKey - * @param string $sValue - * - * @return bool - */ - public function Set($sKey, $sValue) - { - return $this->oMem ? $this->oMem->set($this->generateCachedKey($sKey), $sValue, 0, $this->iExpire) : false; - } - - /** - * @param string $sKey - * - * @return string - */ - public function Get($sKey) - { - $sValue = $this->oMem ? $this->oMem->get($this->generateCachedKey($sKey)) : ''; - return \is_string($sValue) ? $sValue : ''; - } - - /** - * @param string $sKey - * - * @return void - */ - public function Delete($sKey) - { - if ($this->oMem) - { - $this->oMem->delete($this->generateCachedKey($sKey)); - } - } - - /** - * @param int $iTimeToClearInHours = 24 - * - * @return bool - */ - public function GC($iTimeToClearInHours = 24) - { - if (0 === $iTimeToClearInHours && $this->oMem) - { - return $this->oMem->flush(); - } - - return false; - } - - /** - * @param string $sKey - * - * @return string - */ - private function generateCachedKey($sKey) - { - return \sha1($sKey); - } -} +sHost = $sHost; + $this->iPost = $iPost; + $this->iExpire = 0 < $iExpire ? $iExpire : 43200; + + $this->oMem = new \Memcache(); + if (!$this->oMem->connect($this->sHost, $this->iPost)) + { + $this->oMem = null; + } + + $this->sKeyPrefix = $sKeyPrefix; + if (!empty($this->sKeyPrefix)) + { + $this->sKeyPrefix = + \preg_replace('/[^a-zA-Z0-9_]/', '_', rtrim(trim($this->sKeyPrefix), '\\/')).'/'; + } + } + + /** + * @param string $sHost = '127.0.0.1' + * @param int $iPost = 11211 + * @param int $iExpire = 43200 + * @param string $sKeyPrefix = '' + * + * @return \MailSo\Cache\Drivers\APC + */ + public static function NewInstance($sHost = '127.0.0.1', $iPost = 11211, $iExpire = 43200, $sKeyPrefix = '') + { + return new self($sHost, $iPost, $iExpire, $sKeyPrefix); + } + + /** + * @param string $sKey + * @param string $sValue + * + * @return bool + */ + public function Set($sKey, $sValue) + { + return $this->oMem ? $this->oMem->set($this->generateCachedKey($sKey), $sValue, 0, $this->iExpire) : false; + } + + /** + * @param string $sKey + * + * @return string + */ + public function Get($sKey) + { + $sValue = $this->oMem ? $this->oMem->get($this->generateCachedKey($sKey)) : ''; + return \is_string($sValue) ? $sValue : ''; + } + + /** + * @param string $sKey + * + * @return void + */ + public function Delete($sKey) + { + if ($this->oMem) + { + $this->oMem->delete($this->generateCachedKey($sKey)); + } + } + + /** + * @param int $iTimeToClearInHours = 24 + * + * @return bool + */ + public function GC($iTimeToClearInHours = 24) + { + if (0 === $iTimeToClearInHours && $this->oMem) + { + return $this->oMem->flush(); + } + + return false; + } + + /** + * @param string $sKey + * + * @return string + */ + private function generateCachedKey($sKey) + { + return $this->sKeyPrefix.\sha1($sKey); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Config.php b/rainloop/v/0.0.0/app/libraries/MailSo/Config.php index dd35cac57..682b0439b 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Config.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Config.php @@ -1,60 +1,68 @@ -ContentType()); + return \in_array(\strtolower($this->ContentType()), + array('application/pgp-signature', 'application/pkcs7-signature')); } /** @@ -321,7 +331,7 @@ class BodyStructure { $mResult = $this->SearchPlainParts(); } - + return $mResult; } @@ -456,7 +466,7 @@ class BodyStructure * @param array $aParams * @param string $sParamName * @param string $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8 - * + * * @return string */ private static function decodeAttrParamenter($aParams, $sParamName, $sCharset = \MailSo\Base\Enumerations\Charset::UTF_8) @@ -468,7 +478,7 @@ class BodyStructure } else if (isset($aParams[$sParamName.'*'])) { - $aValueParts = \explode('\'\'', $aParams[$sParamName.'*'], 2); + $aValueParts = \explode("''", $aParams[$sParamName.'*'], 2); if (\is_array($aValueParts) && 2 === \count($aValueParts)) { $sCharset = isset($aValueParts[0]) ? $aValueParts[0] : \MailSo\Base\Enumerations\Charset::UTF_8; @@ -481,30 +491,30 @@ class BodyStructure $sResult = \urldecode($aParams[$sParamName.'*']); } } - else if (isset($aParams[$sParamName.'*0*'])) + else { $sCharset = ''; + $sCharsetIndex = -1; + $aFileNames = array(); foreach ($aParams as $sName => $sValue) { $aMatches = array(); - if ($sParamName.'*0*' === $sName) + if (\preg_match('/^'.\preg_quote($sParamName, '/').'\*([0-9]+)\*$/i', $sName, $aMatches)) { - if (0 === \strlen($sCharset)) + $iIndex = (int) $aMatches[1]; + if ($sCharsetIndex < $iIndex && false !== \strpos($sValue, "''")) { - $aValueParts = \explode('\'\'', $sValue, 2); + $aValueParts = \explode("''", $sValue, 2); if (\is_array($aValueParts) && 2 === \count($aValueParts) && 0 < \strlen($aValueParts[0])) { + $sCharsetIndex = $iIndex; $sCharset = $aValueParts[0]; $sValue = $aValueParts[1]; } } - - $aFileNames[0] = $sValue; - } - else if ($sParamName.'*0*' !== $sName && \preg_match('/^'.\preg_quote($sParamName, '/').'\*([0-9]+)\*$/i', $sName, $aMatches) && 0 < \strlen($aMatches[1])) - { - $aFileNames[(int) $aMatches[1]] = $sValue; + + $aFileNames[$iIndex] = $sValue; } } @@ -653,7 +663,7 @@ class BodyStructure { $sCharset = $aBodyParams['charset']; } - + if (\is_array($aBodyParams)) { $sName = self::decodeAttrParamenter($aBodyParams, 'name', $sContentType); @@ -666,7 +676,7 @@ class BodyStructure { return null; } - + $sContentID = $aBodyStructure[3]; } @@ -676,7 +686,7 @@ class BodyStructure { return null; } - + $sDescription = $aBodyStructure[4]; } @@ -858,7 +868,7 @@ class BodyStructure /** * @param array $aList * @param string $sPartID - * + * * @return array|null */ private static function findPartByIndexInArray(array $aList, $sPartID) diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php index 6a846f8bd..fe6a36e18 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/Enumerations/FetchType.php @@ -1,5 +1,14 @@ GetLastResponse(); + if ($oResponse && $oResponse->IsStatusResponse && !empty($oResponse->HumanReadable) && + isset($oResponse->OptionalResponse[0]) && 'ALERT' === $oResponse->OptionalResponse[0]) + { + $sResult = $oResponse->HumanReadable; + } + + return $sResult; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/Exceptions/ResponseException.php b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/Exceptions/ResponseException.php index 5b443813f..24f0d8ca3 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/Exceptions/ResponseException.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/Exceptions/ResponseException.php @@ -1,5 +1,14 @@ PermanentFlags) || in_array($sFlag, $this->PermanentFlags); + return \in_array('\\*', $this->PermanentFlags) || \in_array($sFlag, $this->PermanentFlags); } } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php index 1bb007352..8abaea90a 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php @@ -1,5 +1,14 @@ aTagTimeouts['*'] = \microtime(true); - parent::Connect($sServerName, $iPort, $iSecurityType); + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); $this->parseResponseWithValidation('*', true); @@ -146,12 +158,7 @@ class ImapClient extends \MailSo\Net\NetClient $this->IsSupported('STARTTLS'), $this->iSecurityType)) { $this->SendRequestWithCheck('STARTTLS'); - if (!@\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) - { - $this->writeLogException( - new \MailSo\Imap\Exceptions\RuntimeException('Cannot enable STARTTLS'), - \MailSo\Log\Enumerations\Type::ERROR, true); - } + $this->EnableCrypto(); $this->aCapabilityItems = null; } @@ -187,8 +194,8 @@ class ImapClient extends \MailSo\Net\NetClient \MailSo\Log\Enumerations\Type::ERROR, true); } - $sLogin = \trim($sLogin); - $sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin); + $sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin)); + $sPassword = $sPassword; $this->sLogginedUser = $sLogin; @@ -319,7 +326,7 @@ class ImapClient extends \MailSo\Net\NetClient try { - $this->SendRequestWithCheck('AUTHENTICATE', array('XOAUTH2', trim($sXOAuth2Token))); + $this->SendRequestWithCheck('AUTHENTICATE', array('XOAUTH2', \trim($sXOAuth2Token))); } catch (\MailSo\Imap\Exceptions\NegativeResponseException $oException) { @@ -601,7 +608,8 @@ class ImapClient extends \MailSo\Net\NetClient /** * @param array $aResult - * @param bool $bIsSubscribeList + * @param string $sStatus + * @param bool $bUseListStatus = false * * @return array */ @@ -609,6 +617,9 @@ class ImapClient extends \MailSo\Net\NetClient { $aReturn = array(); + $sDelimiter = ''; + $bInbox = false; + $oImapResponse = null; foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) { @@ -620,6 +631,16 @@ class ImapClient extends \MailSo\Net\NetClient $oFolder = Folder::NewInstance($oImapResponse->ResponseList[4], $oImapResponse->ResponseList[3], $oImapResponse->ResponseList[2]); + if ($oFolder->IsInbox()) + { + $bInbox = true; + } + + if (empty($sDelimiter)) + { + $sDelimiter = $oFolder->Delimiter(); + } + $aReturn[] = $oFolder; } catch (\MailSo\Base\Exceptions\InvalidArgumentException $oException) @@ -628,19 +649,24 @@ class ImapClient extends \MailSo\Net\NetClient } } } - + + if (!$bInbox && !empty($sDelimiter)) + { + $aReturn[] = Folder::NewInstance('INBOX', $sDelimiter); + } + if ($bUseListStatus) { foreach ($aResult as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) { if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType && - 'STATUS' === $oImapResponse->StatusOrIndex && + 'STATUS' === $oImapResponse->StatusOrIndex && isset($oImapResponse->ResponseList[2]) && isset($oImapResponse->ResponseList[3]) && \is_array($oImapResponse->ResponseList[3])) { $sFolderNameRaw = $oImapResponse->ResponseList[2]; - + $oCurrentFolder = null; foreach ($aReturn as &$oFolder) { @@ -650,7 +676,7 @@ class ImapClient extends \MailSo\Net\NetClient break; } } - + if (null !== $oCurrentFolder) { $sName = null; @@ -667,13 +693,13 @@ class ImapClient extends \MailSo\Net\NetClient $sName = null; } } - + if (0 < count($aStatus)) { $oCurrentFolder->SetExtended('STATUS', $aStatus); } } - + unset($oCurrentFolder); } } @@ -700,9 +726,9 @@ class ImapClient extends \MailSo\Net\NetClient { $sCmd = 'LIST'; } - + $sListPattern = 0 === strlen(trim($sListPattern)) ? '*' : $sListPattern; - + $aParameters = array( $this->EscapeString($sParentFolderName), $this->EscapeString($sListPattern) @@ -724,7 +750,7 @@ class ImapClient extends \MailSo\Net\NetClient { $bUseListStatus = false; } - + $this->SendRequest($sCmd, $aParameters); return $this->getFoldersFromResult( @@ -1085,7 +1111,7 @@ class ImapClient extends \MailSo\Net\NetClient { $iStart = 3; } - + for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) { $aReturn[] = (int) $oImapResponse->ResponseList[$iIndex]; @@ -1142,7 +1168,7 @@ class ImapClient extends \MailSo\Net\NetClient { $aRequest[] = 'RETURN'; $aRequest[] = $aSearchOrSortReturn; - + $aRequest[] = $aSortTypes; $aRequest[] = \MailSo\Base\Utils::IsAscii($sSearchCriterias) ? 'US-ASCII' : 'UTF-8'; } @@ -1177,7 +1203,7 @@ class ImapClient extends \MailSo\Net\NetClient foreach ($aResponse as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) { if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType - && 'ESEARCH' === $oImapResponse->StatusOrIndex + && ('ESEARCH' === $oImapResponse->StatusOrIndex || 'ESORT' === $oImapResponse->StatusOrIndex) && \is_array($oImapResponse->ResponseList) && isset($oImapResponse->ResponseList[2], $oImapResponse->ResponseList[2][0], $oImapResponse->ResponseList[2][1]) && 'TAG' === $oImapResponse->ResponseList[2][0] && $sRequestTag === $oImapResponse->ResponseList[2][1] @@ -1415,7 +1441,7 @@ class ImapClient extends \MailSo\Net\NetClient { $iStart = 3; } - + for ($iIndex = $iStart, $iLen = \count($oImapResponse->ResponseList); $iIndex < $iLen; $iIndex++) { $aNewValue = $this->validateThreadItem($oImapResponse->ResponseList[$iIndex]); @@ -1454,7 +1480,7 @@ class ImapClient extends \MailSo\Net\NetClient return $this->SendRequestWithCheck($sCommandPrefix.'COPY', array($sIndexRange, $this->EscapeString($sToFolder))); } - + /** * @param string $sToFolder * @param string $sIndexRange @@ -1488,21 +1514,23 @@ class ImapClient extends \MailSo\Net\NetClient } /** - * @param string $sUidRangeIfSupported + * @param string $sUidRangeIfSupported = '' + * @param bool $bForceUidExpunge = false + * @param bool $bExpungeAll = false * * @return \MailSo\Imap\ImapClient * * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function MessageExpunge($sUidRangeIfSupported = '', $bForceUidExpunge = false) + public function MessageExpunge($sUidRangeIfSupported = '', $bForceUidExpunge = false, $bExpungeAll = false) { $sUidRangeIfSupported = \trim($sUidRangeIfSupported); $sCmd = 'EXPUNGE'; $aArguments = array(); - if ($bForceUidExpunge && 0 < \strlen($sUidRangeIfSupported) && $this->IsSupported('UIDPLUS')) + if (!$bExpungeAll && $bForceUidExpunge && 0 < \strlen($sUidRangeIfSupported) && $this->IsSupported('UIDPLUS')) { $sCmd = 'UID '.$sCmd; $aArguments = array($sUidRangeIfSupported); @@ -1737,7 +1765,7 @@ class ImapClient extends \MailSo\Net\NetClient { $oImapResponse = null; $sEndTag = (null === $sEndTag) ? $this->getCurrentTag() : $sEndTag; - + while (true) { $oImapResponse = Response::NewInstance(); @@ -1929,11 +1957,11 @@ class ImapClient extends \MailSo\Net\NetClient $this->writeLog('Literal stream read warning "read '.$iLiteralSize.' of '. $iLiteralLen.'" bytes', \MailSo\Log\Enumerations\Type::WARNING); } - + if (!$bTreatAsAtom) { $aList[] = $sLiteral; - + if (\MailSo\Config::$LogSimpleLiterals) { $this->writeLog('{'.\strlen($sLiteral).'} '.$sLiteral, \MailSo\Log\Enumerations\Type::INFO); @@ -1965,7 +1993,7 @@ class ImapClient extends \MailSo\Net\NetClient { $sAtomBlock = $this->partialParseResponseBranch($mNull, $iStackIndex, true, null === $sPreviousAtomUpperCase ? '' : \strtoupper($sPreviousAtomUpperCase)); - + $sAtomBuilder .= $sAtomBlock; $iPos = $this->iResponseBufParsedPos; $sAtomBuilder .= ($bIsClosingBracketSquare) ? ']' : ')'; @@ -1987,7 +2015,7 @@ class ImapClient extends \MailSo\Net\NetClient if (null !== $oImapResponse && $oImapResponse->IsStatusResponse) { $oImapResponse->OptionalResponse = $aSubItems; - + $bIsGotoDefault = true; $bIsGotoNotAtomBracket = false; continue; @@ -2350,7 +2378,7 @@ class ImapClient extends \MailSo\Net\NetClient if (\is_resource($rImapLiteralStream)) { $iNotReadLiteralLen = 0; - + $bFeof = \feof($rImapLiteralStream); $this->writeLog('End Callback for '.$sParent.' / '.$sLiteralAtomUpperCase. ' - feof = '.($bFeof ? 'good' : 'BAD'), $bFeof ? diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/NamespaceResult.php b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/NamespaceResult.php index a19970c17..f53bd74ff 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/NamespaceResult.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/NamespaceResult.php @@ -1,5 +1,14 @@ bShowSecter = false; $this->bHideErrorNotices = false; - \set_error_handler(array(&$this, '__phpErrorHandler')); + if ($bRegPhpErrorHandler) + { + \set_error_handler(array(&$this, '__phpErrorHandler')); + } + \register_shutdown_function(array(&$this, '__loggerShutDown')); } /** + * @param bool $bRegPhpErrorHandler = false + * * @return \MailSo\Log\Logger */ - public static function NewInstance() + public static function NewInstance($bRegPhpErrorHandler = false) { - return new self(); + return new self($bRegPhpErrorHandler); } /** @@ -74,6 +91,26 @@ class Logger extends \MailSo\Base\Collection return $oInstance; } + /** + * @return bool + */ + public static function IsSystemEnabled() + { + return !!(\MailSo\Config::$SystemLogger instanceof \MailSo\Log\Logger); + } + + /** + * @param mixed $mData + * @param int $iType = \MailSo\Log\Enumerations\Type::INFO + */ + public static function SystemLog($mData, $iType = \MailSo\Log\Enumerations\Type::INFO) + { + if (\MailSo\Config::$SystemLogger instanceof \MailSo\Log\Logger) + { + \MailSo\Config::$SystemLogger->WriteMixed($mData, $iType); + } + } + /** * @staticvar string $sCache; * @@ -84,7 +121,7 @@ class Logger extends \MailSo\Base\Collection static $sCache = null; if (null === $sCache) { - $sCache = \substr(\md5(\microtime(true).\rand(10000, 99999)), -8); + $sCache = \substr(\MailSo\Base\Utils::Md5Rand(), -8); } return $sCache; diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Attachment.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Attachment.php index 60d0ab8cd..51def4efc 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Attachment.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Attachment.php @@ -1,5 +1,14 @@ oBodyStructure ? $this->oBodyStructure->IsDoc() : false; } + /** + * @return bool + */ + public function IsPgpSignature() + { + return $this->oBodyStructure ? $this->oBodyStructure->IsPgpSignature() : false; + } + /** * @return \MailSo\Mail\Attachment */ diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/AttachmentCollection.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/AttachmentCollection.php index 69648e5af..5cc64b576 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/AttachmentCollection.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/AttachmentCollection.php @@ -1,5 +1,14 @@ FilterList(function ($oAttachment) { - return $oAttachment && $oAttachment->IsImage(); + return $this->MapList(function ($oAttachment) { + if ($oAttachment) + { + return array($oAttachment->FileName(true), $oAttachment->MimeType()); + } + + return null; }); - - return \is_array($aList) ? \count($aList) : 0; - } - - /** - * @return int - */ - public function ArchiveCount() - { - $aList = $this->FilterList(function ($oAttachment) { - return $oAttachment && $oAttachment->IsArchive(); - }); - - return \is_array($aList) ? \count($aList) : 0; - } - - /** - * @return int - */ - public function PdfCount() - { - $aList = $this->FilterList(function ($oAttachment) { - return $oAttachment && $oAttachment->IsPdf(); - }); - - return \is_array($aList) ? \count($aList) : 0; - } - - /** - * @return int - */ - public function DocCount() - { - $aList = $this->FilterList(function ($oAttachment) { - return $oAttachment && $oAttachment->IsDoc(); - }); - - return \is_array($aList) ? \count($aList) : 0; } } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Exceptions/Exception.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Exceptions/Exception.php index de488c51e..9bf1c4212 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Exceptions/Exception.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Exceptions/Exception.php @@ -1,5 +1,14 @@ FoldersHash = ''; $this->SystemFolders = array(); $this->IsThreadsSupported = false; + $this->Optimized = false; } /** @@ -54,7 +69,7 @@ class FolderCollection extends \MailSo\Base\Collection * * @return \MailSo\Mail\Folder|null */ - public function &GetByFullNameRaw($sFullNameRaw) + public function GetByFullNameRaw($sFullNameRaw) { $mResult = null; foreach ($this->aItems as /* @var $oFolder \MailSo\Mail\Folder */ $oFolder) @@ -64,6 +79,18 @@ class FolderCollection extends \MailSo\Base\Collection $mResult = $oFolder; break; } + else if ($oFolder->HasSubFolders()) + { + $mResult = $oFolder->SubFolders(true)->GetByFullNameRaw($sFullNameRaw); + if ($mResult) + { + break; + } + else + { + $mResult = null; + } + } } return $mResult; @@ -77,6 +104,27 @@ class FolderCollection extends \MailSo\Base\Collection return $this->sNamespace; } + /** + * @return string + */ + public function FindDelimiter() + { + $sDelimiter = '/'; + + $oFolder = $this->GetByFullNameRaw('INBOX'); + if (!$oFolder) + { + $oFolder = $this->GetByIndex(0); + } + + if ($oFolder) + { + $sDelimiter = $oFolder->Delimiter(); + } + + return $sDelimiter; + } + /** * @param string $sNamespace * @@ -88,7 +136,7 @@ class FolderCollection extends \MailSo\Base\Collection return $this; } - + /** * @param array $aUnsortedMailFolders * diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php index 0561ed092..ce428e586 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php @@ -1,5 +1,14 @@ oImapClient->Connect($sServerName, $iPort, $iSecurityType); + $this->oImapClient->Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl); return $this; } @@ -144,6 +154,26 @@ class MailClient return $this->oImapClient->IsLoggined(); } + /** + * @return string + */ + private function getEnvelopeOrHeadersRequestStringForSimpleList() + { + return \MailSo\Imap\Enumerations\FetchType::BuildBodyCustomHeaderRequest(array( + \MailSo\Mime\Enumerations\Header::RETURN_PATH, + \MailSo\Mime\Enumerations\Header::RECEIVED, + \MailSo\Mime\Enumerations\Header::MIME_VERSION, + \MailSo\Mime\Enumerations\Header::FROM_, + \MailSo\Mime\Enumerations\Header::TO_, + \MailSo\Mime\Enumerations\Header::CC, + \MailSo\Mime\Enumerations\Header::SENDER, + \MailSo\Mime\Enumerations\Header::REPLY_TO, + \MailSo\Mime\Enumerations\Header::DATE, + \MailSo\Mime\Enumerations\Header::SUBJECT, + \MailSo\Mime\Enumerations\Header::CONTENT_TYPE + ), true); + } + /** * @return string */ @@ -182,23 +212,27 @@ class MailClient * @param string $sFolderName * @param string $sMessageFlag * @param bool $bSetAction = true + * @param bool $sSkipUnsupportedFlag = false * * @throws \MailSo\Base\Exceptions\InvalidArgumentException * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception * @throws \MailSo\Mail\Exceptions\Exception */ - public function MessageSetFlagToAll($sFolderName, $sMessageFlag, $bSetAction = true) + public function MessageSetFlagToAll($sFolderName, $sMessageFlag, $bSetAction = true, $sSkipUnsupportedFlag = false) { $this->oImapClient->FolderSelect($sFolderName); $oFolderInfo = $this->oImapClient->FolderCurrentInformation(); if (!$oFolderInfo || !$oFolderInfo->IsFlagSupported($sMessageFlag)) { - throw new \MailSo\Mail\Exceptions\RuntimeException('Message flag is not supported.'); + if (!$sSkipUnsupportedFlag) + { + throw new \MailSo\Mail\Exceptions\RuntimeException('Message flag "'.$sMessageFlag.'" is not supported.'); + } } - if (0 < $oFolderInfo->Exists) + if ($oFolderInfo && 0 < $oFolderInfo->Exists) { $sStoreAction = $bSetAction ? \MailSo\Imap\Enumerations\StoreAction::ADD_FLAGS_SILENT @@ -231,7 +265,7 @@ class MailClient { if (!$sSkipUnsupportedFlag) { - throw new \MailSo\Mail\Exceptions\RuntimeException('Message flag is not supported.'); + throw new \MailSo\Mail\Exceptions\RuntimeException('Message flag "'.$sMessageFlag.'" is not supported.'); } } else @@ -251,15 +285,16 @@ class MailClient * @param array $aIndexRange * @param bool $bIndexIsUid * @param bool $bSetAction = true + * @param bool $sSkipUnsupportedFlag = false * * @throws \MailSo\Base\Exceptions\InvalidArgumentException * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function MessageSetFlagged($sFolderName, $aIndexRange, $bIndexIsUid, $bSetAction = true) + public function MessageSetFlagged($sFolderName, $aIndexRange, $bIndexIsUid, $bSetAction = true, $sSkipUnsupportedFlag = false) { $this->MessageSetFlag($sFolderName, $aIndexRange, $bIndexIsUid, - \MailSo\Imap\Enumerations\MessageFlag::FLAGGED, $bSetAction); + \MailSo\Imap\Enumerations\MessageFlag::FLAGGED, $bSetAction, $sSkipUnsupportedFlag); } /** @@ -272,7 +307,7 @@ class MailClient */ public function MessageSetSeenToAll($sFolderName, $bSetAction = true) { - $this->MessageSetFlagToAll($sFolderName, \MailSo\Imap\Enumerations\MessageFlag::SEEN, $bSetAction); + $this->MessageSetFlagToAll($sFolderName, \MailSo\Imap\Enumerations\MessageFlag::SEEN, $bSetAction, true); } /** @@ -288,7 +323,7 @@ class MailClient public function MessageSetSeen($sFolderName, $aIndexRange, $bIndexIsUid, $bSetAction = true) { $this->MessageSetFlag($sFolderName, $aIndexRange, $bIndexIsUid, - \MailSo\Imap\Enumerations\MessageFlag::SEEN, $bSetAction); + \MailSo\Imap\Enumerations\MessageFlag::SEEN, $bSetAction, true); } /** @@ -311,7 +346,7 @@ class MailClient throw new \MailSo\Base\Exceptions\InvalidArgumentException(); } - $this->oImapClient->FolderExamine($sFolderName); + $this->oImapClient->FolderSelect($sFolderName); $oBodyStructure = null; $oMessage = false; @@ -411,7 +446,7 @@ class MailClient throw new \MailSo\Base\Exceptions\InvalidArgumentException(); } - $this->oImapClient->FolderExamine($sFolderName); + $this->oImapClient->FolderSelect($sFolderName); $sFileName = ''; $sContentType = ''; @@ -495,6 +530,7 @@ class MailClient * @param array $aIndexRange * @param bool $bIndexIsUid * @param bool $bUseExpunge = true + * @param bool $bExpungeAll = false * * @return \MailSo\Mail\MailClient * @@ -502,7 +538,7 @@ class MailClient * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function MessageDelete($sFolder, $aIndexRange, $bIndexIsUid, $bUseExpunge = true) + public function MessageDelete($sFolder, $aIndexRange, $bIndexIsUid, $bUseExpunge = true, $bExpungeAll = false) { if (0 === \strlen($sFolder) || !\is_array($aIndexRange) || 0 === \count($aIndexRange)) { @@ -520,7 +556,7 @@ class MailClient if ($bUseExpunge) { - $this->oImapClient->MessageExpunge($bIndexIsUid ? $sIndexRange : '', $bIndexIsUid); + $this->oImapClient->MessageExpunge($bIndexIsUid ? $sIndexRange : '', $bIndexIsUid, $bExpungeAll); } return $this; @@ -532,6 +568,7 @@ class MailClient * @param array $aIndexRange * @param bool $bIndexIsUid * @param bool $bUseMoveSupported = false + * @param bool $bExpungeAll = false * * @return \MailSo\Mail\MailClient * @@ -539,7 +576,7 @@ class MailClient * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function MessageMove($sFromFolder, $sToFolder, $aIndexRange, $bIndexIsUid, $bUseMoveSupported = false) + public function MessageMove($sFromFolder, $sToFolder, $aIndexRange, $bIndexIsUid, $bUseMoveSupported = false, $bExpungeAll = false) { if (0 === \strlen($sFromFolder) || 0 === \strlen($sToFolder) || !\is_array($aIndexRange) || 0 === \count($aIndexRange)) @@ -559,7 +596,7 @@ class MailClient $this->oImapClient->MessageCopy($sToFolder, \MailSo\Base\Utils::PrepearFetchSequence($aIndexRange), $bIndexIsUid); - $this->MessageDelete($sFromFolder, $aIndexRange, $bIndexIsUid, true); + $this->MessageDelete($sFromFolder, $aIndexRange, $bIndexIsUid, true, $bExpungeAll); } return $this; @@ -647,11 +684,10 @@ class MailClient * @param int $iCount * @param int $iUnseenCount * @param string $sUidNext - * @param \MailSo\Cache\CacheClient|null $oCacher = null * * @return void */ - protected function initFolderValues($sFolderName, &$iCount, &$iUnseenCount, &$sUidNext, $oCacher = null) + protected function initFolderValues($sFolderName, &$iCount, &$iUnseenCount, &$sUidNext) { $aFolderStatus = $this->oImapClient->FolderStatus($sFolderName, array( \MailSo\Imap\Enumerations\FolderResponseStatus::MESSAGES, @@ -668,36 +704,7 @@ class MailClient $sUidNext = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UIDNEXT]) ? (string) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UIDNEXT] : '0'; - if (\MailSo\Config::$MessageListUndeletedFilter) - { - $oFolder = $this->oImapClient->FolderCurrentInformation(); - if (!$oFolder || $oFolder->FolderName !== $sFolderName) - { - $this->oImapClient->FolderExamine($sFolderName); - } - - $aUids = $this->getSearchUidsResult('', - $sFolderName, false, false, false, $oCacher); - - $iNewCount = \count($aUids); - if (0 < $iNewCount && $iNewCount !== $iCount) - { - $iCount = $iNewCount; - - $aUids = $this->getSearchUidsResult('is:unread', - $sFolderName, false, false, false, $oCacher); - - $iUnseenCount = \count($aUids); - } - else if (0 === $iNewCount) - { - $iCount = 0; - $iUnseenCount = 0; - } - - unset($aUids); - } - else if ($this->IsGmail()) + if ($this->IsGmail()) { $oFolder = $this->oImapClient->FolderCurrentInformation(); if ($oFolder && null !== $oFolder->Exists && $oFolder->FolderName === $sFolderName) @@ -711,6 +718,18 @@ class MailClient } } + /** + * @return string + */ + public function GenerateImapClientHash() + { + return \md5('ImapClientHash/'. + $this->oImapClient->GetLogginedUser().'@'. + $this->oImapClient->GetConnectedHost().':'. + $this->oImapClient->GetConnectedPort() + ); + } + /** * @param string $sFolder * @param int $iCount @@ -719,10 +738,12 @@ class MailClient * * @return string */ - public static function GenerateHash($sFolder, $iCount, $iUnseenCount, $sUidNext) + public function GenerateFolderHash($sFolder, $iCount, $iUnseenCount, $sUidNext) { $iUnseenCount = 0; // unneccessery - return \md5($sFolder.'-'.$iCount.'-'.$iUnseenCount.'-'.$sUidNext); + return \md5('FolderHash/'.$sFolder.'-'.$iCount.'-'.$iUnseenCount.'-'.$sUidNext.'-'. + $this->GenerateImapClientHash() + ); } /** @@ -738,7 +759,7 @@ class MailClient if (0 < \strlen($sPrevUidNext) && (string) $sPrevUidNext !== (string) $sCurrentUidNext) { - $this->oImapClient->FolderExamine($sFolderName); + $this->oImapClient->FolderSelect($sFolderName); $aFetchResponse = $this->oImapClient->Fetch(array( \MailSo\Imap\Enumerations\FetchType::INDEX, @@ -800,7 +821,6 @@ class MailClient * @param string $sFolderName * @param string $sPrevUidNext = '' * @param array $aUids = '' - * @param \MailSo\Cache\CacheClient|null $oCacher = null * * @return string * @@ -808,14 +828,14 @@ class MailClient * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function FolderInformation($sFolderName, $sPrevUidNext = '', $aUids = array(), $oCacher = null) + public function FolderInformation($sFolderName, $sPrevUidNext = '', $aUids = array()) { $aFlags = array(); $bSelect = false; if ($this->IsGmail()) { - $this->oImapClient->FolderExamine($sFolderName); + $this->oImapClient->FolderSelect($sFolderName); $bSelect = true; } @@ -823,7 +843,7 @@ class MailClient { if (!$bSelect) { - $this->oImapClient->FolderExamine($sFolderName); + $this->oImapClient->FolderSelect($sFolderName); } $aFetchResponse = $this->oImapClient->Fetch(array( @@ -847,11 +867,11 @@ class MailClient $iUnseenCount = 0; $sUidNext = '0'; - $this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $sUidNext, $oCacher); + $this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $sUidNext); $aResult = array( 'Folder' => $sFolderName, - 'Hash' => self::GenerateHash($sFolderName, $iCount, $iUnseenCount, $sUidNext), + 'Hash' => $this->GenerateFolderHash($sFolderName, $iCount, $iUnseenCount, $sUidNext), 'MessageCount' => $iCount, 'MessageUnseenCount' => $iUnseenCount, 'UidNext' => $sUidNext, @@ -865,7 +885,6 @@ class MailClient /** * @param string $sFolderName - * @param \MailSo\Cache\CacheClient|null $oCacher = null * * @return string * @@ -873,15 +892,33 @@ class MailClient * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function FolderHash($sFolderName, $oCacher = null) + public function FolderHash($sFolderName) { $iCount = 0; $iUnseenCount = 0; $sUidNext = '0'; - $this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $sUidNext, $oCacher); + $this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $sUidNext); - return self::GenerateHash($sFolderName, $iCount, $iUnseenCount, $sUidNext); + return $this->GenerateFolderHash($sFolderName, $iCount, $iUnseenCount, $sUidNext); + } + + /** + * @return int + * + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function InboxUnreadCount() + { + $aFolderStatus = $this->oImapClient->FolderStatus('INBOX', array( + \MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN + )); + + $iResult = isset($aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN]) ? + (int) $aFolderStatus[\MailSo\Imap\Enumerations\FolderResponseStatus::UNSEEN] : 0; + + return 0 < $iResult ? $iResult : 0; } /** @@ -987,7 +1024,7 @@ class MailClient { do { - $sKey = \md5(\rand(10000, 90000).\microtime(true)); + $sKey = \MailSo\Base\Utils::Md5Rand(); } while (isset($aCache[$sKey])); @@ -1003,7 +1040,7 @@ class MailClient { do { - $sKey = \md5(\rand(10000, 90000).\microtime(true)); + $sKey = \MailSo\Base\Utils::Md5Rand(); } while (isset($aCache[$sKey])); @@ -1327,10 +1364,6 @@ class MailClient } $sCriteriasResult = \trim(\implode(' ', $aCriteriasResult)); - if (\MailSo\Config::$MessageListUndeletedFilter) - { - $sCriteriasResult .= ' UNDELETED'; - } if (0 < $iTimeFilter) { @@ -1409,7 +1442,9 @@ class MailClient /** * @param string $sFolderName * @param string $sFolderHash + * @param array $aIndexOrUids * @param \MailSo\Cache\CacheClient $oCacher + * @param bool $bCacheOnly = false * * @return array * @@ -1417,14 +1452,11 @@ class MailClient * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function MessageListThreadsMap($sFolderName, $sFolderHash, $oCacher) + public function MessageListThreadsMap($sFolderName, $sFolderHash, $aIndexOrUids, $oCacher, $bCacheOnly = false) { - $sSearchHash = ''; - if (\MailSo\Config::$MessageListUndeletedFilter) - { - $sSearchHash .= ' UNDELETED'; - } + $iThreadLimit = \MailSo\Config::$LargeThreadLimit; + $sSearchHash = ''; if (0 < \MailSo\Config::$MessageListDateFilter) { $iD = \time() - 3600 * 24 * 30 * \MailSo\Config::$MessageListDateFilter; @@ -1440,29 +1472,36 @@ class MailClient if ($oCacher && $oCacher->IsInited()) { - $sSerializedHash = - 'ThreadsMap/'.$sSearchHash.'/'. - $this->oImapClient->GetLogginedUser().'@'. - $this->oImapClient->GetConnectedHost().':'. - $this->oImapClient->GetConnectedPort().'/'. - $sFolderName.'/'.$sFolderHash; + $sSerializedHashKey = + 'ThreadsMapSorted/'.$sSearchHash.'/'. + 'Limit='.$iThreadLimit.'/'.$sFolderName.'/'.$sFolderHash; - $sSerializedUids = $oCacher->Get($sSerializedHash); + if ($this->oLogger) + { + $this->oLogger->Write($sSerializedHashKey); + } + + $sSerializedUids = $oCacher->Get($sSerializedHashKey); if (!empty($sSerializedUids)) { - $aSerializedUids = @\unserialize($sSerializedUids); - if (is_array($aSerializedUids)) + $aSerializedUids = @\json_decode($sSerializedUids, true); + if (isset($aSerializedUids['ThreadsUids']) && \is_array($aSerializedUids['ThreadsUids'])) { if ($this->oLogger) { - $this->oLogger->Write('Get Serialized Thread UIDS from cache ("'.$sFolderName.'" / '.$sSearchHash.') [count:'.\count($aSerializedUids).']'); + $this->oLogger->Write('Get Serialized Thread UIDS from cache ("'.$sFolderName.'" / '.$sSearchHash.') [count:'.\count($aSerializedUids['ThreadsUids']).']'); } - return $aSerializedUids; + return $aSerializedUids['ThreadsUids']; } } } + if ($bCacheOnly) + { + return null; + } + $this->oImapClient->FolderExamine($sFolderName); $aThreadUids = array(); @@ -1472,6 +1511,7 @@ class MailClient } catch (\MailSo\Imap\Exceptions\RuntimeException $oException) { + unset($oException); $aThreadUids = array(); } @@ -1494,9 +1534,100 @@ class MailClient } } - if ($oCacher && $oCacher->IsInited() && !empty($sSerializedHash)) + $aParentsMap = array(); + foreach ($aIndexOrUids as $iUid) { - $oCacher->Set($sSerializedHash, serialize($aResult)); + if (isset($aResult[$iUid]) && \is_array($aResult[$iUid])) + { + foreach ($aResult[$iUid] as $iTempUid) + { + $aParentsMap[$iTempUid] = $iUid; + if (isset($aResult[$iTempUid])) + { + unset($aResult[$iTempUid]); + } + } + } + } + + $aSortedThreads = array(); + foreach ($aIndexOrUids as $iUid) + { + if (isset($aResult[$iUid])) + { + $aSortedThreads[$iUid] = $iUid; + } + } + + foreach ($aIndexOrUids as $iUid) + { + if (!isset($aSortedThreads[$iUid]) && + isset($aParentsMap[$iUid]) && + isset($aSortedThreads[$aParentsMap[$iUid]])) + { + if (!\is_array($aSortedThreads[$aParentsMap[$iUid]])) + { + $aSortedThreads[$aParentsMap[$iUid]] = array(); + } + + $aSortedThreads[$aParentsMap[$iUid]][] = $iUid; + } + } + + $aResult = $aSortedThreads; + unset($aParentsMap, $aSortedThreads); + + $aTemp = array(); + foreach ($aResult as $iUid => $mValue) + { + if (0 < $iThreadLimit && \is_array($mValue) && $iThreadLimit < \count($mValue)) + { + $aParts = \array_chunk($mValue, $iThreadLimit); + if (0 < count($aParts)) + { + foreach ($aParts as $iIndex => $aItem) + { + if (0 === $iIndex) + { + $aResult[$iUid] = $aItem; + } + else if (0 < $iIndex && \is_array($aItem)) + { + $mFirst = \array_shift($aItem); + if (!empty($mFirst)) + { + $aTemp[$mFirst] = 0 < \count($aItem) ? $aItem : $mFirst; + } + } + } + } + } + } + + foreach ($aTemp as $iUid => $mValue) + { + $aResult[$iUid] = $mValue; + } + + unset($aTemp); + + $aLastResult = array(); + foreach ($aIndexOrUids as $iUid) + { + if (isset($aResult[$iUid])) + { + $aLastResult[$iUid] = $aResult[$iUid]; + } + } + + $aResult = $aLastResult; + unset($aLastResult); + + if ($oCacher && $oCacher->IsInited() && !empty($sSerializedHashKey)) + { + $oCacher->Set($sSerializedHashKey, @\json_encode(array( + 'ThreadsUids' => $aResult + ))); if ($this->oLogger) { @@ -1511,11 +1642,12 @@ class MailClient * @param \MailSo\Mail\MessageCollection &$oMessageCollection * @param array $aRequestIndexOrUids * @param bool $bIndexAsUid + * @param bool $bSimple = false * * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function MessageListByRequestIndexOrUids(&$oMessageCollection, $aRequestIndexOrUids, $bIndexAsUid) + public function MessageListByRequestIndexOrUids(&$oMessageCollection, $aRequestIndexOrUids, $bIndexAsUid, $bSimple = false) { if (\is_array($aRequestIndexOrUids) && 0 < \count($aRequestIndexOrUids)) { @@ -1526,7 +1658,8 @@ class MailClient \MailSo\Imap\Enumerations\FetchType::INTERNALDATE, \MailSo\Imap\Enumerations\FetchType::FLAGS, \MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE, - $this->getEnvelopeOrHeadersRequestString() + $bSimple ? $this->getEnvelopeOrHeadersRequestStringForSimpleList() : + $this->getEnvelopeOrHeadersRequestString() ), \MailSo\Base\Utils::PrepearFetchSequence($aRequestIndexOrUids), $bIndexAsUid); if (\is_array($aFetchResponse) && 0 < \count($aFetchResponse)) @@ -1580,8 +1713,8 @@ class MailClient private function getSearchUidsResult($sSearch, $sFolderName, $sFolderHash, $bUseSortIfSupported = true, $bUseESearchOrESortRequest = false, $oCacher = null) { - $bUidsFromCacher = false; $aResultUids = false; + $bUidsFromCacher = false; $bUseCacheAfterSearch = true; $sSerializedHash = ''; @@ -1592,23 +1725,21 @@ class MailClient $sSearchCriterias = $this->getImapSearchCriterias($sSearch, 0, $bUseCacheAfterSearch); if ($bUseCacheAfterSearch && $oCacher && $oCacher->IsInited()) { - $sSerializedHash = + $sSerializedHash = 'SearchSortUids/'. ($bUseSortIfSupported ? 'S': 'N').'/'. - $this->oImapClient->GetLogginedUser().'@'. - $this->oImapClient->GetConnectedHost().':'. - $this->oImapClient->GetConnectedPort().'/'. - $sFolderName.'/'. - $sSearchCriterias; + $this->GenerateImapClientHash().'/'. + $sFolderName.'/'.$sSearchCriterias; $sSerializedLog = '"'.$sFolderName.'" / '.$sSearchCriterias.''; $sSerialized = $oCacher->Get($sSerializedHash); if (!empty($sSerialized)) { - $aSerialized = @\unserialize($sSerialized); + $aSerialized = @\json_decode($sSerialized, true); if (\is_array($aSerialized) && isset($aSerialized['FolderHash'], $aSerialized['Uids']) && - \is_array($aSerialized['Uids']) && - ($sFolderHash === $aSerialized['FolderHash'] || false === $sFolderHash)) + $sFolderHash === $aSerialized['FolderHash'] && + \is_array($aSerialized['Uids']) + ) { if ($this->oLogger) { @@ -1691,7 +1822,7 @@ class MailClient if (!$bUidsFromCacher && $bUseCacheAfterSearch && \is_array($aResultUids) && $oCacher && $oCacher->IsInited() && 0 < \strlen($sSerializedHash)) { - $oCacher->Set($sSerializedHash, \serialize(array( + $oCacher->Set($sSerializedHash, @\json_encode(array( 'FolderHash' => $sFolderHash, 'Uids' => $aResultUids ))); @@ -1706,6 +1837,80 @@ class MailClient return $aResultUids; } + /** + * @param string $sFolderName + * @param string $sFolderHash + * @param string $sUid + * @param \MailSo\Cache\CacheClient|null $oCacher = null + * + * @return array + */ + public function MessageThreadUidsFromCache($sFolderName, $sFolderHash, $sUid, $oCacher = null) + { + $aResult = array(); + if (0 < \strlen($sFolderName) && 0 < \strlen($sFolderHash) && 0 < \strlen($sUid) && $oCacher && $oCacher->IsInited()) + { + $mThreads = $this->MessageListThreadsMap($sFolderName, $sFolderHash, array(), $oCacher, true); + + if (\is_array($mThreads)) + { + $iUid = (int) $sUid; + foreach ($mThreads as $iSubUid => $aSubUids) + { + if ($iUid === $iSubUid) + { + $aResult = $aSubUids; + if (!\is_array($aResult)) + { + $aResult = array(); + } + + \array_unshift($aResult, $iSubUid); + break; + } + else if (\is_array($aSubUids)) + { + if (\in_array($iUid, $aSubUids)) + { + $aResult = $aSubUids; + \array_unshift($aResult, $iSubUid); + break; + } + } + } + } + } + + return \array_map('trim', $aResult); + } + + /** + * @param string $sFolderName + * @param array $aUids + * + * @return \MailSo\Mail\MessageCollection + * + * @throws \MailSo\Base\Exceptions\InvalidArgumentException + * @throws \MailSo\Net\Exceptions\Exception + * @throws \MailSo\Imap\Exceptions\Exception + */ + public function MessageListSimple($sFolderName, $aUids) + { + if (0 === \strlen($sFolderName) || !\MailSo\Base\Validator::NotEmptyArray($aUids)) + { + throw new \MailSo\Base\Exceptions\InvalidArgumentException(); + } + + $this->oImapClient->FolderExamine($sFolderName); + + $oMessageCollection = \MailSo\Mail\MessageCollection::NewInstance(); + $oMessageCollection->FolderName = $sFolderName; + + $this->MessageListByRequestIndexOrUids($oMessageCollection, $aUids, true, true); + + return $oMessageCollection->GetAsArray(); + } + /** * @param string $sFolderName * @param int $iOffset = 0 @@ -1715,7 +1920,6 @@ class MailClient * @param \MailSo\Cache\CacheClient|null $oCacher = null * @param bool $bUseSortIfSupported = false * @param bool $bUseThreadSortIfSupported = false - * @param array $aExpandedThreadsUids = array() * @param bool $bUseESearchOrESortRequest = false * * @return \MailSo\Mail\MessageCollection @@ -1724,9 +1928,8 @@ class MailClient * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception */ - public function MessageList($sFolderName, $iOffset = 0, $iLimit = 10, $sSearch = '', $sPrevUidNext = '', - $oCacher = null, $bUseSortIfSupported = false, $bUseThreadSortIfSupported = false, - $aExpandedThreadsUids = array(), $bUseESearchOrESortRequest = false) + public function MessageList($sFolderName, $iOffset = 0, $iLimit = 10, $sSearch = '', $sPrevUidNext = '', $oCacher = null, + $bUseSortIfSupported = false, $bUseThreadSortIfSupported = false, $bUseESearchOrESortRequest = false) { $sSearch = \trim($sSearch); if (!\MailSo\Base\Validator::RangeInt($iOffset, 0) || @@ -1735,7 +1938,7 @@ class MailClient throw new \MailSo\Base\Exceptions\InvalidArgumentException(); } - $this->oImapClient->FolderExamine($sFolderName); + $this->oImapClient->FolderSelect($sFolderName); $oMessageCollection = MessageCollection::NewInstance(); $oMessageCollection->FolderName = $sFolderName; @@ -1743,8 +1946,6 @@ class MailClient $oMessageCollection->Limit = $iLimit; $oMessageCollection->Search = $sSearch; - $aLastCollapsedThreadUids = array(); - $aThreads = array(); $iMessageCount = 0; @@ -1762,10 +1963,10 @@ class MailClient $oCacher = null; } - $this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext, $oCacher); + $this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext); $iMessageCount = $iMessageRealCount; - $oMessageCollection->FolderHash = self::GenerateHash($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext); + $oMessageCollection->FolderHash = $this->GenerateFolderHash($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext); $oMessageCollection->UidNext = $sUidNext; $oMessageCollection->NewMessages = $this->getFolderNextMessageInformation($sFolderName, $sPrevUidNext, $sUidNext); @@ -1801,24 +2002,9 @@ class MailClient $oMessageCollection->FolderName, $oMessageCollection->FolderHash, $bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher); - $aThreads = $this->MessageListThreadsMap($oMessageCollection->FolderName, $oMessageCollection->FolderHash, $oCacher); - - $aExpandedThreadsUids = \is_array($aExpandedThreadsUids) ? $aExpandedThreadsUids : array(); - $bWatchExpanded = 0 < \count($aExpandedThreadsUids); - - foreach ($aIndexOrUids as $iUid) - { - if (isset($aThreads[$iUid]) && \is_array($aThreads[$iUid])) - { - foreach ($aThreads[$iUid] as $iTempUid) - { - if (isset($aThreads[$iTempUid])) - { - unset($aThreads[$iTempUid]); - } - } - } - } + $aThreads = $this->MessageListThreadsMap( + $oMessageCollection->FolderName, $oMessageCollection->FolderHash, + $aIndexOrUids, $oCacher); $aNewIndexOrUids = array(); foreach ($aIndexOrUids as $iUid) @@ -1826,24 +2012,6 @@ class MailClient if (isset($aThreads[$iUid])) { $aNewIndexOrUids[] = $iUid; - if (\is_array($aThreads[$iUid])) - { - if ($bWatchExpanded && \in_array($iUid, $aExpandedThreadsUids)) - { - $aSubArray = $aThreads[$iUid]; - foreach ($aIndexOrUids as $iSubRootUid) - { - if (\in_array($iSubRootUid, $aSubArray)) - { - $aNewIndexOrUids[] = $iSubRootUid; - } - } - } - else - { - $aLastCollapsedThreadUids[] = $iUid; - } - } } } @@ -1860,8 +2028,8 @@ class MailClient if (1 < $iMessageCount) { - if ($bMessageListOptimization || - (!\MailSo\Config::$MessageListUndeletedFilter && 0 === \MailSo\Config::$MessageListDateFilter)) + if (0 === \MailSo\Config::$MessageListDateFilter && + ($bMessageListOptimization || !$bUseSortIfSupported)) { $aIndexOrUids = \array_reverse(\range(1, $iMessageCount)); } @@ -1894,38 +2062,20 @@ class MailClient } } - $aLastCollapsedThreadUidsForPage = array(); - if (!$bSearch && $bUseThreadSortIfSupported && 0 < \count($aThreads)) { - $oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use ( - $aThreads, $aLastCollapsedThreadUids, &$aLastCollapsedThreadUidsForPage) - { - $iUid = $oMessage->Uid(); - if (\in_array($iUid, $aLastCollapsedThreadUids)) - { - $aLastCollapsedThreadUidsForPage[] = $iUid; - } + $oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use ($aThreads) { + $iUid = $oMessage->Uid(); if (isset($aThreads[$iUid]) && \is_array($aThreads[$iUid]) && 0 < \count($aThreads[$iUid])) { - $oMessage->SetThreads($aThreads[$iUid]); - $oMessage->SetThreadsLen(\count($aThreads[$iUid])); - } - else if (!isset($aThreads[$iUid])) - { - foreach ($aThreads as $iKeyUid => $mSubItem) - { - if (\is_array($mSubItem) && \in_array($iUid, $mSubItem)) - { - $oMessage->SetParentThread($iKeyUid); - $oMessage->SetThreadsLen(\count($mSubItem)); - } - } + $aSubThreads = $aThreads[$iUid]; + \array_unshift($aSubThreads, $iUid); + + $oMessage->SetThreads(\array_map('trim', $aSubThreads)); + unset($aSubThreads); } }); - - $oMessageCollection->LastCollapsedThreadUids = $aLastCollapsedThreadUidsForPage; } return $oMessageCollection; @@ -1960,19 +2110,152 @@ class MailClient return \is_array($aUids) && 1 === \count($aUids) && \is_numeric($aUids[0]) ? (int) $aUids[0] : null; } + /** + * @param array $aMailFoldersHelper + * @param int $iOptimizationLimit = 0 + * + * @return array + */ + public function folderListOptimization($aMailFoldersHelper, $iOptimizationLimit = 0) + { + // optimization + if (10 < $iOptimizationLimit && \is_array($aMailFoldersHelper) && $iOptimizationLimit < \count($aMailFoldersHelper)) + { + if ($this->oLogger) + { + $this->oLogger->Write('Start optimization (limit:'.$iOptimizationLimit.') for '.\count($aMailFoldersHelper).' folders'); + } + + $iForeachLimit = 1; + + $aFilteredNames = array( + 'inbox', + 'sent', 'outbox', 'sentmail', + 'drafts', + 'junk', 'spam', + 'trash', 'bin', + 'archives', 'archive', 'allmail', 'all', + 'starred', 'flagged', 'important', + 'contacts', 'chats' + ); + + $aNewMailFoldersHelper = array(); + + $iCountLimit = $iForeachLimit; + + foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder) + { + // mandatory folders + if ($oFolder && \in_array(\strtolower($oFolder->NameRaw()), $aFilteredNames)) + { + $aNewMailFoldersHelper[] = $oFolder; + $aMailFoldersHelper[$iIndex] = null; + } + } + + foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder) + { + // subscribed folders + if ($oFolder && $oFolder->IsSubscribed()) + { + $aNewMailFoldersHelper[] = $oFolder; + + $aMailFoldersHelper[$iIndex] = null; + $iCountLimit--; + } + + if (0 > $iCountLimit) + { + if ($iOptimizationLimit < \count($aNewMailFoldersHelper)) + { + break; + } + else + { + $iCountLimit = $iForeachLimit; + } + } + } + + $iCountLimit = $iForeachLimit; + if ($iOptimizationLimit >= \count($aNewMailFoldersHelper)) + { + // name filter + foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder) + { + if ($oFolder && !\preg_match('/[{}\[\]]/', $oFolder->NameRaw())) + { + $aNewMailFoldersHelper[] = $oFolder; + + $aMailFoldersHelper[$iIndex] = null; + $iCountLimit--; + } + + if (0 > $iCountLimit) + { + if ($iOptimizationLimit < \count($aNewMailFoldersHelper)) + { + break; + } + else + { + $iCountLimit = $iForeachLimit; + } + } + } + } + + $iCountLimit = $iForeachLimit; + if ($iOptimizationLimit >= \count($aNewMailFoldersHelper)) + { + // other + foreach ($aMailFoldersHelper as $iIndex => /* @var $oImapFolder \MailSo\Mail\Folder */ $oFolder) + { + if ($oFolder) + { + $aNewMailFoldersHelper[] = $oFolder; + + $aMailFoldersHelper[$iIndex] = null; + $iCountLimit--; + } + + if (0 > $iCountLimit) + { + if ($iOptimizationLimit < \count($aNewMailFoldersHelper)) + { + break; + } + else + { + $iCountLimit = $iForeachLimit; + } + } + } + } + + $aMailFoldersHelper = $aNewMailFoldersHelper; + + if ($this->oLogger) + { + $this->oLogger->Write('Result optimization: '.\count($aMailFoldersHelper).' folders'); + } + } + + return $aMailFoldersHelper; + } + /** * @param string $sParent = '' * @param string $sListPattern = '*' * @param bool $bUseListSubscribeStatus = false + * @param int $iOptimizationLimit = 0 * * @return \MailSo\Mail\FolderCollection|false */ - public function Folders($sParent = '', $sListPattern = '*', $bUseListSubscribeStatus = true) + public function Folders($sParent = '', $sListPattern = '*', $bUseListSubscribeStatus = true, $iOptimizationLimit = 0) { $oFolderCollection = false; - $aFolders = $this->oImapClient->FolderList($sParent, $sListPattern); - $aSubscribedFolders = null; if ($bUseListSubscribeStatus) { @@ -1993,7 +2276,11 @@ class MailClient } } + $aFolders = $this->oImapClient->FolderList($sParent, $sListPattern); + + $bOptimized = false; $aMailFoldersHelper = null; + if (\is_array($aFolders)) { $aMailFoldersHelper = array(); @@ -2005,12 +2292,19 @@ class MailClient $oImapFolder->IsInbox() ); } + + $iCount = \count($aMailFoldersHelper); + $aMailFoldersHelper = $this->folderListOptimization($aMailFoldersHelper, $iOptimizationLimit); + + $bOptimized = $iCount !== \count($aMailFoldersHelper); } if (\is_array($aMailFoldersHelper)) { $oFolderCollection = FolderCollection::NewInstance(); $oFolderCollection->InitByUnsortedMailFolderArray($aMailFoldersHelper); + + $oFolderCollection->Optimized = $bOptimized; } if ($oFolderCollection) @@ -2047,41 +2341,45 @@ class MailClient } /** - * @param string $sFolderNameInUtf + * @param string $sFolderNameInUtf8 * @param string $sFolderParentFullNameRaw = '' * @param bool $bSubscribeOnCreation = true + * @param string $sDelimiter = '' * * @return \MailSo\Mail\MailClient * * @throws \MailSo\Base\Exceptions\InvalidArgumentException */ - public function FolderCreate($sFolderNameInUtf, $sFolderParentFullNameRaw = '', $bSubscribeOnCreation = true) + public function FolderCreate($sFolderNameInUtf8, $sFolderParentFullNameRaw = '', $bSubscribeOnCreation = true, $sDelimiter = '') { - if (!\MailSo\Base\Validator::NotEmptyString($sFolderNameInUtf, true) || + if (!\MailSo\Base\Validator::NotEmptyString($sFolderNameInUtf8, true) || !\is_string($sFolderParentFullNameRaw)) { throw new \MailSo\Base\Exceptions\InvalidArgumentException(); } - $sFolderNameInUtf = trim($sFolderNameInUtf); + $sFolderNameInUtf8 = \trim($sFolderNameInUtf8); - $aFolders = $this->oImapClient->FolderList('', 0 === \strlen(\trim($sFolderParentFullNameRaw)) ? 'INBOX' : $sFolderParentFullNameRaw); - if (!\is_array($aFolders) || !isset($aFolders[0])) + if (0 === \strlen($sDelimiter) || 0 < \strlen(\trim($sFolderParentFullNameRaw))) { - // TODO - throw new \MailSo\Mail\Exceptions\RuntimeException( - 0 === \strlen(trim($sFolderParentFullNameRaw)) - ? 'Cannot get folder delimiter' - : 'Cannot create folder in non-existen parent folder'); + $aFolders = $this->oImapClient->FolderList('', 0 === \strlen(\trim($sFolderParentFullNameRaw)) ? 'INBOX' : $sFolderParentFullNameRaw); + if (!\is_array($aFolders) || !isset($aFolders[0])) + { + // TODO + throw new \MailSo\Mail\Exceptions\RuntimeException( + 0 === \strlen(trim($sFolderParentFullNameRaw)) + ? 'Cannot get folder delimiter' + : 'Cannot create folder in non-existen parent folder'); + } + + $sDelimiter = $aFolders[0]->Delimiter(); + if (0 < \strlen($sDelimiter) && 0 < \strlen(\trim($sFolderParentFullNameRaw))) + { + $sFolderParentFullNameRaw .= $sDelimiter; + } } - $sDelimiter = $aFolders[0]->Delimiter(); - if (0 < \strlen($sDelimiter) && 0 < \strlen(\trim($sFolderParentFullNameRaw))) - { - $sFolderParentFullNameRaw .= $sDelimiter; - } - - $sFullNameRawToCreate = \MailSo\Base\Utils::ConvertEncoding($sFolderNameInUtf, + $sFullNameRawToCreate = \MailSo\Base\Utils::ConvertEncoding($sFolderNameInUtf8, \MailSo\Base\Enumerations\Charset::UTF_8, \MailSo\Base\Enumerations\Charset::UTF_7_IMAP); @@ -2113,9 +2411,9 @@ class MailClient * * @throws \MailSo\Base\Exceptions\InvalidArgumentException */ - public function FolderMove($sPrevFolderFullNameRaw, $sNextFolderFullNameInUtf, $bSubscribeOnRename = true) + public function FolderMove($sPrevFolderFullNameRaw, $sNextFolderFullNameInUtf, $bSubscribeOnMove = true) { - return $this->folderModify($sPrevFolderFullNameRaw, $sNextFolderFullNameInUtf, false, $bSubscribeOnRename); + return $this->folderModify($sPrevFolderFullNameRaw, $sNextFolderFullNameInUtf, false, $bSubscribeOnMove); } /** diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Message.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Message.php index 8fe93dbbb..7424f1bbf 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Message.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Message.php @@ -1,5 +1,14 @@ sReadReceipt = ''; $this->aThreads = array(); - $this->iThreadsLen = 0; - $this->iParentThread = 0; $this->bTextPartIsTrimmed = false; @@ -533,38 +530,6 @@ class Message $this->aThreads = \is_array($aThreads) ? $aThreads : array(); } - /** - * @return int - */ - public function ThreadsLen() - { - return $this->iThreadsLen; - } - - /** - * @param int $iThreadsLen - */ - public function SetThreadsLen($iThreadsLen) - { - $this->iThreadsLen = $iThreadsLen; - } - - /** - * @return int - */ - public function ParentThread() - { - return $this->iParentThread; - } - - /** - * @param int $iParentThread - */ - public function SetParentThread($iParentThread) - { - $this->iParentThread = $iParentThread; - } - /** * @return boole */ @@ -648,12 +613,15 @@ class Message $this->oCc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::CC, $bCharsetAutoDetect); $this->oBcc = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::BCC, $bCharsetAutoDetect); + $oHeaders->PopulateEmailColectionByDkim($this->oFrom); + $this->oSender = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::SENDER, $bCharsetAutoDetect); $this->oReplyTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::REPLY_TO, $bCharsetAutoDetect); $this->oDeliveredTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::DELIVERED_TO, $bCharsetAutoDetect); $this->sInReplyTo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IN_REPLY_TO); - $this->sReferences = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::REFERENCES); + $this->sReferences = \trim(\preg_replace('/[\s]+/', ' ', + $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::REFERENCES))); $sHeaderDate = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DATE); $this->sHeaderDate = $sHeaderDate; diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php index ad2b0a023..34c8cf884 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php @@ -1,5 +1,14 @@ sEmail = \MailSo\Base\Utils::IdnToAscii(\trim($sEmail), true); - $this->sDisplayName = \trim($sDisplayName); - $this->sRemark = \trim($sRemark); + $this->sEmail = \MailSo\Base\Utils::IdnToAscii( + \MailSo\Base\Utils::Trim($sEmail), true); + + $this->sDisplayName = \MailSo\Base\Utils::Trim($sDisplayName); + $this->sRemark = \MailSo\Base\Utils::Trim($sRemark); + + $this->sDkimStatus = \MailSo\Mime\Enumerations\DkimStatus::NONE; + $this->sDkimValue = ''; } /** @@ -66,6 +90,7 @@ class Email */ public static function Parse($sEmailAddress) { + $sEmailAddress = \MailSo\Base\Utils::Trim($sEmailAddress); if (!\MailSo\Base\Validator::NotEmptyString($sEmailAddress, true)) { throw new \MailSo\Base\Exceptions\InvalidArgumentException(); @@ -214,6 +239,22 @@ class Email return $this->sRemark; } + /** + * @return string + */ + public function GetDkimStatus() + { + return $this->sDkimStatus; + } + + /** + * @return string + */ + public function GetDkimValue() + { + return $this->sDkimValue; + } + /** * @return string */ @@ -232,14 +273,25 @@ class Email return \MailSo\Base\Utils::GetDomainFromEmail($this->GetEmail($bIdn)); } + /** + * @param string $sDkimStatus + * @param string $sDkimValue = '' + */ + public function SetDkimStatusAndValue($sDkimStatus, $sDkimValue = '') + { + $this->sDkimStatus = \MailSo\Mime\Enumerations\DkimStatus::normalizeValue($sDkimStatus); + $this->sDkimValue = $sDkimValue; + } + /** * @param bool $bIdn = false - * + * * @return array */ public function ToArray($bIdn = false) { - return array($this->sDisplayName, $this->GetEmail($bIdn), $this->sRemark); + return array($this->sDisplayName, $this->GetEmail($bIdn), $this->sRemark, + $this->sDkimStatus, $this->sDkimValue); } /** diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/EmailCollection.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/EmailCollection.php index b9cc1ee28..53e5db391 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/EmailCollection.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/EmailCollection.php @@ -1,5 +1,14 @@ parseEmailAddresses($sEmailAddresses); @@ -204,7 +214,7 @@ class EmailCollection extends \MailSo\Base\Collection $this->Add( \MailSo\Mime\Email::Parse(\substr($sWorkingRecipients, $iEmailStartPos, $iEmailEndPos - $iEmailStartPos)) ); - + $iEmailStartPos = $iCurrentPos + 1; } catch (\MailSo\Base\Exceptions\InvalidArgumentException $oException) diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Enumerations/Constants.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Enumerations/Constants.php index 5f58ba30b..7f52ee890 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Enumerations/Constants.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Enumerations/Constants.php @@ -1,5 +1,14 @@ Add($oHeader); } - + $sName = null; $sValue = null; } @@ -346,6 +356,113 @@ class HeaderCollection extends \MailSo\Base\Collection return $this; } + /** + * @return int + */ + public function DkimStatuses() + { + $aResult = array(); + + $aHeaders = $this->ValuesByName(\MailSo\Mime\Enumerations\Header::AUTHENTICATION_RESULTS); + if (\is_array($aHeaders) && 0 < \count($aHeaders)) + { + foreach ($aHeaders as $sHeaderValue) + { + $sStatus = ''; + $sHeader = ''; + $sDkimLine = ''; + + $aMatch = array(); + + $sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue); + + if (\preg_match('/dkim=[^;]+/i', $sHeaderValue, $aMatch) && !empty($aMatch[0])) + { + $sDkimLine = $aMatch[0]; + + $aMatch = array(); + if (\preg_match('/dkim=([a-zA-Z0-9]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[1])) + { + $sStatus = $aMatch[1]; + } + + $aMatch = array(); + if (\preg_match('/header\.(d|i|from)=([^\s;]+)/i', $sDkimLine, $aMatch) && !empty($aMatch[2])) + { + $sHeader = \trim($aMatch[2]); + } + + if (!empty($sStatus) && !empty($sHeader)) + { + $aResult[] = array($sStatus, $sHeader, $sDkimLine); + } + } + } + } + else + { + // X-DKIM-Authentication-Results: signer="hostinger.com" status="pass" + $aHeaders = $this->ValuesByName(\MailSo\Mime\Enumerations\Header::X_DKIM_AUTHENTICATION_RESULTS); + if (\is_array($aHeaders) && 0 < \count($aHeaders)) + { + foreach ($aHeaders as $sHeaderValue) + { + $sStatus = ''; + $sHeader = ''; + + $aMatch = array(); + + $sHeaderValue = \preg_replace('/[\r\n\t\s]+/', ' ', $sHeaderValue); + + if (\preg_match('/status[\s]?=[\s]?"([a-zA-Z0-9]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1])) + { + $sStatus = $aMatch[1]; + } + + if (\preg_match('/signer[\s]?=[\s]?"([^";]+)"/i', $sHeaderValue, $aMatch) && !empty($aMatch[1])) + { + $sHeader = \trim($aMatch[1]); + } + + if (!empty($sStatus) && !empty($sHeader)) + { + $aResult[] = array($sStatus, $sHeader, $sHeaderValue); + } + } + } + } + + return $aResult; + } + + /** + * @return int + */ + public function PopulateEmailColectionByDkim($oEmails) + { + if ($oEmails && $oEmails instanceof \MailSo\Mime\EmailCollection) + { + $aDkimStatuses = $this->DkimStatuses(); + if (\is_array($aDkimStatuses) && 0 < \count($aDkimStatuses)) + { + $oEmails->ForeachList(function (/* @var $oItem \MailSo\Mime\Email */ $oItem) use ($aDkimStatuses) { + if ($oItem && $oItem instanceof \MailSo\Mime\Email) + { + $sEmail = $oItem->GetEmail(); + foreach ($aDkimStatuses as $aDkimData) + { + if (isset($aDkimData[0], $aDkimData[1]) && + $aDkimData[1] === \strstr($sEmail, $aDkimData[1])) + { + $oItem->SetDkimStatusAndValue($aDkimData[0], empty($aDkimData[2]) ? '' : $aDkimData[2]); + } + } + } + }); + } + } + } + /** * @return string */ diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Message.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Message.php index 386970197..7bfaec046 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Message.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Message.php @@ -1,5 +1,14 @@ oAttachmentCollection; } + /** + * @return string + */ + public function GetSubject() + { + return isset($this->aHeadersValue[\MailSo\Mime\Enumerations\Header::SUBJECT]) ? + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::SUBJECT] : ''; + } + /** * @return \MailSo\Mime\Email|null */ @@ -238,7 +256,8 @@ class Message */ public function SetReferences($sReferences) { - $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::REFERENCES] = $sReferences; + $this->aHeadersValue[\MailSo\Mime\Enumerations\Header::REFERENCES] = + \trim(\preg_replace('/[\s]+/', ' ', $sReferences)); return $this; } @@ -517,8 +536,9 @@ class Message $sHostName = 'localhost'; } - return '<'.\md5(\rand(100000, 999999).\time().$sHostName. - (\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? @\getmypid() : '')).'@'.$sHostName.'>'; + return '<'. + \MailSo\Base\Utils::Md5Rand($sHostName. + (\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? @\getmypid() : '')).'@'.$sHostName.'>'; } /** diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Parameter.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Parameter.php index 1f6f28ed2..487a256e7 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Parameter.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Parameter.php @@ -1,5 +1,14 @@ Parse($sRawParams); } @@ -65,12 +74,12 @@ class ParameterCollection extends \MailSo\Base\Collection public function ParameterValueByName($sName) { $sResult = ''; - $sName = trim($sName); + $sName = \trim($sName); $aParams =& $this->GetAsArray(); foreach ($aParams as /* @var $oParam \MailSo\Mime\ParameterCollection */ $oParam) { - if (strtolower($sName) === strtolower($oParam->Name())) + if (\strtolower($sName) === \strtolower($oParam->Name())) { $sResult = $oParam->Value(); break; @@ -89,7 +98,7 @@ class ParameterCollection extends \MailSo\Base\Collection { $this->Clear(); - $aDataToParse = explode(';', $sRawParams); + $aDataToParse = \explode(';', $sRawParams); foreach ($aDataToParse as $sParam) { @@ -113,13 +122,13 @@ class ParameterCollection extends \MailSo\Base\Collection foreach ($aParams as /* @var $oParam \MailSo\Mime\Parameter */ $oParam) { $sLine = $oParam->ToString($bConvertSpecialsName); - if (0 < strlen($sLine)) + if (0 < \strlen($sLine)) { $aResult[] = $sLine; } } - return 0 < count($aResult) ? implode('; ', $aResult) : ''; + return 0 < \count($aResult) ? \implode('; ', $aResult) : ''; } /** @@ -138,8 +147,8 @@ class ParameterCollection extends \MailSo\Base\Collection $aMatch = array(); $sParamName = $oParam->Name(); - if (preg_match('/([^\*]+)\*([\d]{1,2})\*/', $sParamName, $aMatch) && isset($aMatch[1], $aMatch[2]) - && 0 < strlen($aMatch[1]) && is_numeric($aMatch[2])) + if (\preg_match('/([^\*]+)\*([\d]{1,2})\*/', $sParamName, $aMatch) && isset($aMatch[1], $aMatch[2]) + && 0 < \strlen($aMatch[1]) && \is_numeric($aMatch[2])) { if (!isset($aPreParams[$aMatch[1]])) { @@ -147,16 +156,20 @@ class ParameterCollection extends \MailSo\Base\Collection } $sValue = $oParam->Value(); - $aValueParts = explode('\'\'', $sValue, 2); - if (is_array($aValueParts) && 2 === count($aValueParts) && 0 < strlen($aValueParts[1])) + + if (false !== \strpos($sValue, "''")) { - $sCharset = $aValueParts[0]; - $sValue = $aValueParts[1]; + $aValueParts = \explode("''", $sValue, 2); + if (\is_array($aValueParts) && 2 === \count($aValueParts) && 0 < \strlen($aValueParts[1])) + { + $sCharset = $aValueParts[0]; + $sValue = $aValueParts[1]; + } } $aPreParams[$aMatch[1]][(int) $aMatch[2]] = $sValue; } - else if (preg_match('/([^\*]+)\*/', $sParamName, $aMatch) && isset($aMatch[1])) + else if (\preg_match('/([^\*]+)\*/', $sParamName, $aMatch) && isset($aMatch[1])) { if (!isset($aPreParams[$aMatch[1]])) { @@ -164,11 +177,14 @@ class ParameterCollection extends \MailSo\Base\Collection } $sValue = $oParam->Value(); - $aValueParts = explode('\'\'', $sValue, 2); - if (is_array($aValueParts) && 2 === count($aValueParts) && 0 < strlen($aValueParts[1])) + if (false !== \strpos($sValue, "''")) { - $sCharset = $aValueParts[0]; - $sValue = $aValueParts[1]; + $aValueParts = \explode("''", $sValue, 2); + if (\is_array($aValueParts) && 2 === \count($aValueParts) && 0 < \strlen($aValueParts[1])) + { + $sCharset = $aValueParts[0]; + $sValue = $aValueParts[1]; + } } $aPreParams[$aMatch[1]][0] = $sValue; @@ -182,10 +198,10 @@ class ParameterCollection extends \MailSo\Base\Collection foreach ($aPreParams as $sName => $aValues) { ksort($aValues); - $sResult = implode(array_values($aValues)); - $sResult = urldecode($sResult); + $sResult = \implode(\array_values($aValues)); + $sResult = \urldecode($sResult); - if (0 < strlen($sCharset)) + if (0 < \strlen($sCharset)) { $sResult = \MailSo\Base\Utils::ConvertEncoding($sResult, $sCharset, \MailSo\Base\Enumerations\Charset::UTF_8); diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Parser/ParserEmpty.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Parser/ParserEmpty.php index c13b6c8c6..2349ade75 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Parser/ParserEmpty.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Parser/ParserEmpty.php @@ -1,5 +1,14 @@ sSocketMessage; + return $this->iSocketCode; } } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Net/Exceptions/SocketConnectionDoesNotAvailableException.php b/rainloop/v/0.0.0/app/libraries/MailSo/Net/Exceptions/SocketConnectionDoesNotAvailableException.php index 765fee683..b406b3d2a 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Net/Exceptions/SocketConnectionDoesNotAvailableException.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Net/Exceptions/SocketConnectionDoesNotAvailableException.php @@ -1,5 +1,14 @@ rConnect; } + /** + * @param int $iErrNo + * @param string $sErrStr + * @param string $sErrFile + * @param int $iErrLine + * + * @return bool + */ + public function capturePhpErrorWithException($iErrNo, $sErrStr, $sErrFile, $iErrLine) + { + throw new \MailSo\Base\Exceptions\Exception($sErrStr, $iErrNo); + } + /** * @param string $sServerName * @param int $iPort * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = true * * @return void * @@ -180,7 +204,8 @@ abstract class NetClient * @throws \MailSo\Net\Exceptions\SocketCanNotConnectToHostException */ public function Connect($sServerName, $iPort, - $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT) + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = true) { if (!\MailSo\Base\Validator::NotEmptyString($sServerName, true) || !\MailSo\Base\Validator::PortInt($iPort)) { @@ -201,15 +226,22 @@ abstract class NetClient $sErrorStr = ''; $iErrorNo = 0; - $this->iSecurityType = $iSecurityType; + $this->sConnectedHost = $sServerName; $this->iConnectedPort = $iPort; - $this->bSecure = \MailSo\Net\Enumerations\ConnectionSecurityType::UseSSL($iPort, $iSecurityType); - $this->sConnectedHost = $this->bSecure ? 'ssl://'.$sServerName : $sServerName; + $this->iSecurityType = $iSecurityType; + $this->bSecure = \MailSo\Net\Enumerations\ConnectionSecurityType::UseSSL( + $this->iConnectedPort, $this->iSecurityType); + + if (!\preg_match('/^[a-z0-9._]{2,8}:\/\//i', $this->sConnectedHost)) + { + $this->sConnectedHost = ($this->bSecure ? 'ssl://' : 'tcp://').$this->sConnectedHost; +// $this->sConnectedHost = ($this->bSecure ? 'ssl://' : '').$this->sConnectedHost; + } if (!$this->bSecure && \MailSo\Net\Enumerations\ConnectionSecurityType::SSL === $this->iSecurityType) { $this->writeLogException( - new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('SSL isn\'t supported'), + new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('SSL isn\'t supported: ('.\implode(', ', \stream_get_transports()).')'), \MailSo\Log\Enumerations\Type::ERROR, true); } @@ -217,8 +249,42 @@ abstract class NetClient $this->writeLog('Start connection to "'.$this->sConnectedHost.':'.$this->iConnectedPort.'"', \MailSo\Log\Enumerations\Type::NOTE); - $this->rConnect = @\fsockopen($this->sConnectedHost, $this->iConnectedPort, - $iErrorNo, $sErrorStr, $this->iConnectTimeOut); +// $this->rConnect = @\fsockopen($this->sConnectedHost, $this->iConnectedPort, +// $iErrorNo, $sErrorStr, $this->iConnectTimeOut); + + $bVerifySsl = !!$bVerifySsl; + $bAllowSelfSigned = $bVerifySsl ? !!$bAllowSelfSigned : true; + + $aStreamContextSettings = array( + 'ssl' => array( + 'verify_host' => $bVerifySsl, + 'verify_peer' => $bVerifySsl, + 'verify_peer_name' => $bVerifySsl, + 'allow_self_signed' => $bAllowSelfSigned + ) + ); + + \MailSo\Hooks::Run('Net.NetClient.StreamContextSettings/Filter', array(&$aStreamContextSettings)); + + $rStreamContext = \stream_context_create($aStreamContextSettings); + + \set_error_handler(array(&$this, 'capturePhpErrorWithException')); + + try + { + $this->rConnect = \stream_socket_client($this->sConnectedHost.':'.$this->iConnectedPort, + $iErrorNo, $sErrorStr, $this->iConnectTimeOut, STREAM_CLIENT_CONNECT, $rStreamContext); + } + catch (\Exception $oExc) + { + $sErrorStr = $oExc->getMessage(); + $iErrorNo = $oExc->getCode(); + } + + \restore_error_handler(); + + $this->writeLog('Connected ('.(\is_resource($this->rConnect) ? 'success' : 'unsuccess').')', + \MailSo\Log\Enumerations\Type::NOTE); if (!\is_resource($this->rConnect)) { @@ -241,6 +307,31 @@ abstract class NetClient } } + public function EnableCrypto() + { + $bError = true; + if (\is_resource($this->rConnect) && + \MailSo\Base\Utils::FunctionExistsAndEnabled('stream_socket_enable_crypto')) + { + switch (true) + { + case defined('STREAM_CRYPTO_METHOD_ANY_CLIENT') && + @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_ANY_CLIENT): + case @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_TLS_CLIENT): + case @\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT): + $bError = false; + break; + } + } + + if ($bError) + { + $this->writeLogException( + new \MailSo\Net\Exceptions\Exception('Cannot enable STARTTLS.'), + \MailSo\Log\Enumerations\Type::ERROR, true); + } + } + /** * @return void */ @@ -308,6 +399,15 @@ abstract class NetClient $this->IsConnected(true); } + /** + * @return array|bool + */ + public function StreamContextParams() + { + return \is_resource($this->rConnect) && \MailSo\Base\Utils::FunctionExistsAndEnabled('stream_context_get_options') + ? \stream_context_get_params($this->rConnect) : false; + } + /** * @param string $sRaw * @param bool $bWriteToLog = true @@ -408,8 +508,9 @@ abstract class NetClient } else { -// $this->writeLog('Stream Meta: '. -// \print_r($aSocketStatus, true), \MailSo\Log\Enumerations\Type::ERROR); + $this->writeLog('Stream Meta: '. + \print_r($aSocketStatus, true), \MailSo\Log\Enumerations\Type::ERROR); + $this->writeLogException( new Exceptions\SocketReadException(), \MailSo\Log\Enumerations\Type::ERROR, true); @@ -420,10 +521,10 @@ abstract class NetClient $iReadedLen = \strlen($this->sResponseBuffer); if (null === $mReadLen || $bForceLogin) { - $iLimit = 5000; // 5kb + $iLimit = 5000; // 5KB if ($iLimit < $iReadedLen) { - $this->writeLogWithCrlf('[cutted:'.$iReadedLen.'b] < '.\substr($this->sResponseBuffer.'...', 0, $iLimit), + $this->writeLogWithCrlf('[cutted:'.$iReadedLen.'] < '.\substr($this->sResponseBuffer, 0, $iLimit).'...', \MailSo\Log\Enumerations\Type::INFO); } else @@ -487,6 +588,11 @@ abstract class NetClient { if ($this->oLogger) { + if ($oException instanceof Exceptions\SocketCanNotConnectToHostException) + { + $this->oLogger->Write('Socket: ['.$oException->getSocketCode().'] '.$oException->getSocketMessage(), $iDescType, $this->getLogName()); + } + $this->oLogger->WriteException($oException, $iDescType, $this->getLogName()); } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Pop3/Exceptions/Exception.php b/rainloop/v/0.0.0/app/libraries/MailSo/Pop3/Exceptions/Exception.php index b74cb4bcc..0cc582d57 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Pop3/Exceptions/Exception.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Pop3/Exceptions/Exception.php @@ -1,5 +1,14 @@ iRequestTime = microtime(true); - parent::Connect($sServerName, $iPort, $iSecurityType); + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + $this->validateResponse(); if (\MailSo\Net\Enumerations\ConnectionSecurityType::UseStartTLS( in_array('STLS', $this->Capa()), $this->iSecurityType)) { $this->sendRequestWithCheck('STLS'); - if (!@stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) - { - $this->writeLogException( - new \MailSo\Pop3\Exceptions\RuntimeException('Cannot enable STARTTLS'), - \MailSo\Log\Enumerations\Type::ERROR, true); - } + $this->EnableCrypto(); $this->aCapa = null; } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Poppassd/Exceptions/Exception.php b/rainloop/v/0.0.0/app/libraries/MailSo/Poppassd/Exceptions/Exception.php index 3cd260832..76097ffbc 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Poppassd/Exceptions/Exception.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Poppassd/Exceptions/Exception.php @@ -1,5 +1,14 @@ iRequestTime = \microtime(true); - parent::Connect($sServerName, $iPort, $iSecurityType); + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + $this->validateResponse(); return $this; diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/Exceptions/Exception.php b/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/Exceptions/Exception.php index ed345fc6a..0740485c4 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/Exceptions/Exception.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Sieve/Exceptions/Exception.php @@ -1,5 +1,14 @@ IsSupported('SIEVE') && \in_array(\strtoupper($sModule), $this->aModules); + return $this->IsSupported('SIEVE') && \in_array(\strtolower(\trim($sModule)), $this->aModules); } - + + /** + * @return array + */ + public function Modules() + { + return $this->aModules; + } + /** * @param string $sAuth * @@ -78,6 +95,8 @@ class ManageSieveClient extends \MailSo\Net\NetClient * @param string $sServerName * @param int $iPort * @param int $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT + * @param bool $bVerifySsl = false + * @param bool $bAllowSelfSigned = true * * @return \MailSo\Sieve\ManageSieveClient * @@ -86,12 +105,13 @@ class ManageSieveClient extends \MailSo\Net\NetClient * @throws \MailSo\Sieve\Exceptions\ResponseException */ public function Connect($sServerName, $iPort, - $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT) + $iSecurityType = \MailSo\Net\Enumerations\ConnectionSecurityType::AUTO_DETECT, + $bVerifySsl = false, $bAllowSelfSigned = true) { - $this->iRequestTime = microtime(true); + $this->iRequestTime = \microtime(true); + + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); - parent::Connect($sServerName, $iPort, $iSecurityType); - $mResponse = $this->parseResponse(); $this->validateResponse($mResponse); $this->parseStartupResponse($mResponse); @@ -100,12 +120,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient $this->IsSupported('STARTTLS'), $this->iSecurityType)) { $this->sendRequestWithCheck('STARTTLS'); - if (!@stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) - { - $this->writeLogException( - new \MailSo\Sieve\Exceptions\RuntimeException('Cannot enable STARTTLS'), - \MailSo\Log\Enumerations\Type::ERROR, true); - } + $this->EnableCrypto(); $mResponse = $this->parseResponse(); $this->validateResponse($mResponse); @@ -117,7 +132,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient new \MailSo\Net\Exceptions\SocketUnsuppoterdSecureConnectionException('STARTTLS is not supported'), \MailSo\Log\Enumerations\Type::ERROR, true); } - + return $this; } @@ -149,9 +164,9 @@ class ManageSieveClient extends \MailSo\Net\NetClient { if ($this->IsAuthSupported('PLAIN')) { - $sAuth = base64_encode($sLoginAuthKey."\0".$sLogin."\0".$sPassword); + $sAuth = \base64_encode($sLoginAuthKey."\0".$sLogin."\0".$sPassword); - $this->sendRequest('AUTHENTICATE "PLAIN" {'.strlen($sAuth).'+}'); + $this->sendRequest('AUTHENTICATE "PLAIN" {'.\strlen($sAuth).'+}'); $this->sendRequest($sAuth); $mResponse = $this->parseResponse(); @@ -161,13 +176,13 @@ class ManageSieveClient extends \MailSo\Net\NetClient } else if ($this->IsAuthSupported('LOGIN')) { - $sLogin = base64_encode($sLogin); - $sPassword = base64_encode($sPassword); + $sLogin = \base64_encode($sLogin); + $sPassword = \base64_encode($sPassword); $this->sendRequest('AUTHENTICATE "LOGIN"'); - $this->sendRequest('{'.strlen($sLogin).'+}'); + $this->sendRequest('{'.\strlen($sLogin).'+}'); $this->sendRequest($sLogin); - $this->sendRequest('{'.strlen($sPassword).'+}'); + $this->sendRequest('{'.\strlen($sPassword).'+}'); $this->sendRequest($sPassword); $mResponse = $this->parseResponse(); @@ -233,7 +248,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient $this->validateResponse($mResponse); $aResult = array(); - if (is_array($mResponse)) + if (\is_array($mResponse)) { foreach ($mResponse as $sLine) { @@ -249,7 +264,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient return $aResult; } - + /** * @return array * @@ -268,7 +283,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient /** * @return \MailSo\Sieve\ManageSieveClient - * + * * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Sieve\Exceptions\NegativeResponseException */ @@ -281,7 +296,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient /** * @param string $sScriptName - * + * * @return string * * @throws \MailSo\Net\Exceptions\Exception @@ -294,16 +309,16 @@ class ManageSieveClient extends \MailSo\Net\NetClient $this->validateResponse($mResponse); $sScript = ''; - if (is_array($mResponse) && 0 < count($mResponse)) + if (\is_array($mResponse) && 0 < \count($mResponse)) { if ('{' === $mResponse[0]{0}) { - array_shift($mResponse); + \array_shift($mResponse); } if (\in_array(\substr($mResponse[\count($mResponse) - 1], 0, 2), array('OK', 'NO'))) { - array_pop($mResponse); + \array_pop($mResponse); } $sScript = \implode("\n", $mResponse); @@ -323,9 +338,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient */ public function PutScript($sScriptName, $sScriptSource) { - $sScriptSource = stripslashes($sScriptSource); - - $this->sendRequest('PUTSCRIPT "'.$sScriptName.'" {'.strlen($sScriptSource).'+}'); + $this->sendRequest('PUTSCRIPT "'.$sScriptName.'" {'.\strlen($sScriptSource).'+}'); $this->sendRequestWithCheck($sScriptSource); return $this; @@ -341,9 +354,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient */ public function CheckScript($sScriptSource) { - $sScriptSource = stripslashes($sScriptSource); - - $this->sendRequest('CHECKSCRIPT {'.strlen($sScriptSource).'+}'); + $this->sendRequest('CHECKSCRIPT {'.\strlen($sScriptSource).'+}'); $this->sendRequestWithCheck($sScriptSource); return $this; @@ -360,10 +371,10 @@ class ManageSieveClient extends \MailSo\Net\NetClient public function SetActiveScript($sScriptName) { $this->sendRequestWithCheck('SETACTIVE "'.$sScriptName.'"'); - + return $this; } - + /** * @param string $sScriptName * @@ -388,7 +399,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient public function GetActiveScriptName() { $aList = $this->ListScripts(); - if (is_array($aList) && 0 < count($aList)) + if (\is_array($aList) && 0 < \count($aList)) { foreach ($aList as $sName => $bIsActive) { @@ -404,7 +415,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient /** * @param string $sScriptName - * + * * @return bool * * @throws \MailSo\Net\Exceptions\Exception @@ -414,7 +425,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient { return $sScriptName === $this->GetActiveScriptName(); } - + /** * @param string $sLine * @return array|false @@ -451,7 +462,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient } /** - * @param string $sCommand + * @param string $mResponse * * @return void * @@ -480,7 +491,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient $this->aAuth = \explode(' ', \strtoupper($aTokens[1])); break; case 'SIEVE': - $this->aModules = \explode(' ', \strtoupper($aTokens[1])); + $this->aModules = \explode(' ', \strtolower($aTokens[1])); break; } } @@ -506,7 +517,6 @@ class ManageSieveClient extends \MailSo\Net\NetClient $this->IsConnected(true); - $sRequest = \trim($sRequest); $this->sendRaw($sRequest); } @@ -527,7 +537,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient /** * @param string $sLine - * + * * @return string */ private function convertEndOfLine($sLine) @@ -545,7 +555,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient $this->getNextBuffer($iLen, true); - if (strlen($this->sResponseBuffer) === $iLen) + if (\strlen($this->sResponseBuffer) === $iLen) { $sLine = \trim(\substr_replace($sLine, $this->sResponseBuffer, $iPos)); } @@ -555,7 +565,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient return $sLine; } - + /** * @return array|bool */ @@ -567,7 +577,7 @@ class ManageSieveClient extends \MailSo\Net\NetClient do { $this->getNextBuffer(); - + $sLine = $this->sResponseBuffer; if (false === $sLine) { @@ -610,12 +620,12 @@ class ManageSieveClient extends \MailSo\Net\NetClient */ protected function getLogName() { - return 'MANAGE-SIEVE'; + return 'SIEVE'; } /** * @param \MailSo\Log\Logger $oLogger - * + * * @return \MailSo\Sieve\ManageSieveClient * * @throws \MailSo\Base\Exceptions\InvalidArgumentException diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Smtp/Exceptions/Exception.php b/rainloop/v/0.0.0/app/libraries/MailSo/Smtp/Exceptions/Exception.php index 476ca4b3c..7c18f1892 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Smtp/Exceptions/Exception.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Smtp/Exceptions/Exception.php @@ -1,5 +1,14 @@ iRequestTime = microtime(true); - parent::Connect($sServerName, $iPort, $iSecurityType); + parent::Connect($sServerName, $iPort, $iSecurityType, $bVerifySsl, $bAllowSelfSigned); + $this->validateResponse(220); $this->preLoginStartTLSAndEhloProcess($sEhloHost); @@ -173,8 +186,8 @@ class SmtpClient extends \MailSo\Net\NetClient */ public function Login($sLogin, $sPassword) { - $sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin); - + $sLogin = \MailSo\Base\Utils::IdnToAscii(\MailSo\Base\Utils::Trim($sLogin)); + if ($this->IsAuthSupported('LOGIN')) { try @@ -270,7 +283,7 @@ class SmtpClient extends \MailSo\Net\NetClient { try { - $this->sendRequestWithCheck('AUTH', 235, 'XOAUTH2 '.trim($sXOAuth2Token)); + $this->sendRequestWithCheck('AUTH', 235, 'XOAUTH2 '.\trim($sXOAuth2Token)); } catch (\MailSo\Smtp\Exceptions\NegativeResponseException $oException) { @@ -293,23 +306,31 @@ class SmtpClient extends \MailSo\Net\NetClient /** * @param string $sFrom * @param string $sSizeIfSupported = '' + * @param bool $bDsn = false * * @return \MailSo\Smtp\SmtpClient * * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Smtp\Exceptions\Exception */ - public function MailFrom($sFrom, $sSizeIfSupported = '') + public function MailFrom($sFrom, $sSizeIfSupported = '', $bDsn = false) { - $sFrom = \MailSo\Base\Utils::IdnToAscii($sFrom, true); + $sFrom = \MailSo\Base\Utils::IdnToAscii( + \MailSo\Base\Utils::Trim($sFrom), true); + $sCmd = 'FROM:<'.$sFrom.'>'; - + $sSizeIfSupported = (string) $sSizeIfSupported; if (0 < \strlen($sSizeIfSupported) && \is_numeric($sSizeIfSupported) && $this->IsSupported('SIZE')) { $sCmd .= ' SIZE='.$sSizeIfSupported; } + if ($bDsn && $this->IsSupported('DSN')) + { + $sCmd .= ' RET=HDRS'; + } + $this->sendRequestWithCheck('MAIL', 250, $sCmd); $this->bMail = true; @@ -321,13 +342,14 @@ class SmtpClient extends \MailSo\Net\NetClient /** * @param string $sTo + * @param bool $bDsn = false * * @return \MailSo\Smtp\SmtpClient * * @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Smtp\Exceptions\Exception */ - public function Rcpt($sTo) + public function Rcpt($sTo, $bDsn = false) { if (!$this->bMail) { @@ -336,8 +358,17 @@ class SmtpClient extends \MailSo\Net\NetClient \MailSo\Log\Enumerations\Type::ERROR, true); } - $sTo = \MailSo\Base\Utils::IdnToAscii($sTo, true); - $this->sendRequestWithCheck('RCPT', array(250, 251), 'TO:<'.$sTo.'>'); + $sTo = \MailSo\Base\Utils::IdnToAscii( + \MailSo\Base\Utils::Trim($sTo), true); + + $sCmd = 'TO:<'.$sTo.'>'; + + if ($bDsn && $this->IsSupported('DSN')) + { + $sCmd .= ' NOTIFY=SUCCESS,FAILURE'; + } + + $this->sendRequestWithCheck('RCPT', array(250, 251), $sCmd); $this->bRcpt = true; @@ -468,6 +499,9 @@ class SmtpClient extends \MailSo\Net\NetClient */ public function Vrfy($sUser) { + $sUser = \MailSo\Base\Utils::IdnToAscii( + \MailSo\Base\Utils::Trim($sUser)); + $this->sendRequestWithCheck('VRFY', array(250, 251, 252), $sUser); return $this; @@ -527,12 +561,7 @@ class SmtpClient extends \MailSo\Net\NetClient $this->IsSupported('STARTTLS'), $this->iSecurityType, $this->HasSupportedAuth())) { $this->sendRequestWithCheck('STARTTLS', 220); - if (!@\stream_socket_enable_crypto($this->rConnect, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) - { - $this->writeLogException( - new \MailSo\Smtp\Exceptions\RuntimeException('Cannot enable STARTTLS'), - \MailSo\Log\Enumerations\Type::WARNING, true); - } + $this->EnableCrypto(); $this->ehloOrHelo($sEhloHost); } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Vendors/Net/IDNA2.php b/rainloop/v/0.0.0/app/libraries/MailSo/Vendors/Net/IDNA2.php index 758be7994..820683411 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Vendors/Net/IDNA2.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Vendors/Net/IDNA2.php @@ -28,8 +28,7 @@ //require_once 'Net/IDNA2/Exception/Nameprep.php'; defined('MAILSO_LIBRARY_ROOT_PATH') || die(); -require_once MAILSO_LIBRARY_ROOT_PATH.'Vendors/Net/IDNA2/Exception.php'; -require_once MAILSO_LIBRARY_ROOT_PATH.'Vendors/Net/IDNA2/Exception/Nameprep.php'; +require_once MAILSO_LIBRARY_ROOT_PATH.'Vendors/Net/IDNA2CustomExceptions.php'; /** * Encode/decode Internationalized Domain Names. diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Vendors/Net/IDNA2/Exception.php b/rainloop/v/0.0.0/app/libraries/MailSo/Vendors/Net/IDNA2/Exception.php deleted file mode 100644 index 49f2751ab..000000000 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Vendors/Net/IDNA2/Exception.php +++ /dev/null @@ -1,5 +0,0 @@ -getSignature(); } - + return $sSignature; } } diff --git a/rainloop/v/0.0.0/app/libraries/PHPThumb/GD.php b/rainloop/v/0.0.0/app/libraries/PHPThumb/GD.php new file mode 100644 index 000000000..190d8a0ad --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/PHPThumb/GD.php @@ -0,0 +1,1417 @@ + + * Copyright (c) 2009, Ian Selby/Gen X Design + * + * Author(s): Ian Selby + * + * Licensed under the MIT License + * Redistributions of files must retain the above copyright notice. + * + * @author Ian Selby + * @copyright Copyright (c) 2009 Gen X Design + * @link http://phpthumb.gxdlabs.com + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + +class GD extends PHPThumb +{ + /** + * The prior image (before manipulation) + * + * @var resource + */ + protected $oldImage; + + /** + * The working image (used during manipulation) + * + * @var resource + */ + protected $workingImage; + + /** + * The current dimensions of the image + * + * @var array + */ + protected $currentDimensions; + + /** + * The new, calculated dimensions of the image + * + * @var array + */ + protected $newDimensions; + + /** + * The options for this class + * + * This array contains various options that determine the behavior in + * various functions throughout the class. Functions note which specific + * option key / values are used in their documentation + * + * @var array + */ + protected $options; + + /** + * The maximum width an image can be after resizing (in pixels) + * + * @var int + */ + protected $maxWidth; + + /** + * The maximum height an image can be after resizing (in pixels) + * + * @var int + */ + protected $maxHeight; + + /** + * The percentage to resize the image by + * + * @var int + */ + protected $percent; + + /** + * @param string $fileName + * @param array $options + * @param array $plugins + */ + public function __construct($fileName, $options = array(), array $plugins = array()) + { + parent::__construct($fileName, $options, $plugins); + + $this->determineFormat(); + $this->verifyFormatCompatiblity(); + + switch ($this->format) { + case 'GIF': + $this->oldImage = @imagecreatefromgif($this->fileName); + break; + case 'JPG': + $this->oldImage = @imagecreatefromjpeg($this->fileName); + break; + case 'PNG': + $this->oldImage = @imagecreatefrompng($this->fileName); + break; + case 'STRING': + $this->oldImage = @imagecreatefromstring($this->fileName); + break; + } + + if (!is_resource($this->oldImage)) + { + throw new \Exception('Invalid image file'); + } + else + { + $this->currentDimensions = array ( + 'width' => imagesx($this->oldImage), + 'height' => imagesy($this->oldImage) + ); + } + } + + public function __destruct() + { + if (is_resource($this->oldImage)) { + imagedestroy($this->oldImage); + } + + if (is_resource($this->workingImage)) { + imagedestroy($this->workingImage); + } + } + + /** + * Pad an image to desired dimensions. Moves the image into the center and fills the rest with $color. + * @param $width + * @param $height + * @param array $color + * @return GD + */ + public function pad($width, $height, $color = array(255, 255, 255)) + { + // no resize - woohoo! + if ($width == $this->currentDimensions['width'] && $height == $this->currentDimensions['height']) { + return $this; + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($width, $height); + } else { + $this->workingImage = imagecreate($width, $height); + } + + // create the fill color + $fillColor = imagecolorallocate( + $this->workingImage, + $color[0], + $color[1], + $color[2] + ); + + // fill our working image with the fill color + imagefill( + $this->workingImage, + 0, + 0, + $fillColor + ); + + // copy the image into the center of our working image + imagecopyresampled( + $this->workingImage, + $this->oldImage, + intval(($width-$this->currentDimensions['width']) / 2), + intval(($height-$this->currentDimensions['height']) / 2), + 0, + 0, + $this->currentDimensions['width'], + $this->currentDimensions['height'], + $this->currentDimensions['width'], + $this->currentDimensions['height'] + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $width; + $this->currentDimensions['height'] = $height; + + return $this; + } + + /** + * Resizes an image to be no larger than $maxWidth or $maxHeight + * + * If either param is set to zero, then that dimension will not be considered as a part of the resize. + * Additionally, if $this->options['resizeUp'] is set to true (false by default), then this function will + * also scale the image up to the maximum dimensions provided. + * + * @param int $maxWidth The maximum width of the image in pixels + * @param int $maxHeight The maximum height of the image in pixels + * @return \PHPThumb\GD + */ + public function resize($maxWidth = 0, $maxHeight = 0) + { + // make sure our arguments are valid + if (!is_numeric($maxWidth)) { + throw new \InvalidArgumentException('$maxWidth must be numeric'); + } + + if (!is_numeric($maxHeight)) { + throw new \InvalidArgumentException('$maxHeight must be numeric'); + } + + // make sure we're not exceeding our image size if we're not supposed to + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($maxHeight) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $maxHeight; + $this->maxWidth = (intval($maxWidth) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $maxWidth; + } else { + $this->maxHeight = intval($maxHeight); + $this->maxWidth = intval($maxWidth); + } + + // get the new dimensions... + $this->calcImageSize($this->currentDimensions['width'], $this->currentDimensions['height']); + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + } else { + $this->workingImage = imagecreate($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + } + + $this->preserveAlpha(); + + // and create the newly sized image + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + 0, + 0, + $this->newDimensions['newWidth'], + $this->newDimensions['newHeight'], + $this->currentDimensions['width'], + $this->currentDimensions['height'] + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->newDimensions['newWidth']; + $this->currentDimensions['height'] = $this->newDimensions['newHeight']; + + return $this; + } + + /** + * Adaptively Resizes the Image + * + * This function attempts to get the image to as close to the provided dimensions as possible, and then crops the + * remaining overflow (from the center) to get the image to be the size specified + * + * @param int $maxWidth + * @param int $maxHeight + * @return \PHPThumb\GD + */ + public function adaptiveResize($width, $height) + { + // make sure our arguments are valid + if ((!is_numeric($width) || $width == 0) && (!is_numeric($height) || $height == 0)) { + throw new \InvalidArgumentException('$width and $height must be numeric and greater than zero'); + } + + if (!is_numeric($width) || $width == 0) { + $width = ($height * $this->currentDimensions['width']) / $this->currentDimensions['height']; + } + + if (!is_numeric($height) || $height == 0) { + $height = ($width * $this->currentDimensions['height']) / $this->currentDimensions['width']; + } + + // make sure we're not exceeding our image size if we're not supposed to + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + $this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']); + + // resize the image to be close to our desired dimensions + $this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + + // reset the max dimensions... + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight); + } else { + $this->workingImage = imagecreate($this->maxWidth, $this->maxHeight); + } + + $this->preserveAlpha(); + + $cropWidth = $this->maxWidth; + $cropHeight = $this->maxHeight; + $cropX = 0; + $cropY = 0; + + // now, figure out how to crop the rest of the image... + if ($this->currentDimensions['width'] > $this->maxWidth) { + $cropX = intval(($this->currentDimensions['width'] - $this->maxWidth) / 2); + } elseif ($this->currentDimensions['height'] > $this->maxHeight) { + $cropY = intval(($this->currentDimensions['height'] - $this->maxHeight) / 2); + } + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + $cropX, + $cropY, + $cropWidth, + $cropHeight, + $cropWidth, + $cropHeight + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->maxWidth; + $this->currentDimensions['height'] = $this->maxHeight; + + return $this; + } + + /** + * Adaptively Resizes the Image and Crops Using a Percentage + * + * This function attempts to get the image to as close to the provided dimensions as possible, and then crops the + * remaining overflow using a provided percentage to get the image to be the size specified. + * + * The percentage mean different things depending on the orientation of the original image. + * + * For Landscape images: + * --------------------- + * + * A percentage of 1 would crop the image all the way to the left, which would be the same as + * using adaptiveResizeQuadrant() with $quadrant = 'L' + * + * A percentage of 50 would crop the image to the center which would be the same as using + * adaptiveResizeQuadrant() with $quadrant = 'C', or even the original adaptiveResize() + * + * A percentage of 100 would crop the image to the image all the way to the right, etc, etc. + * Note that you can use any percentage between 1 and 100. + * + * For Portrait images: + * -------------------- + * + * This works the same as for Landscape images except that a percentage of 1 means top and 100 means bottom + * + * @param int $maxWidth + * @param int $maxHeight + * @param int $percent + * @return \PHPThumb\GD + */ + public function adaptiveResizePercent($width, $height, $percent = 50) + { + // make sure our arguments are valid + if (!is_numeric($width) || $width == 0) { + throw new \InvalidArgumentException('$width must be numeric and greater than zero'); + } + + if (!is_numeric($height) || $height == 0) { + throw new \InvalidArgumentException('$height must be numeric and greater than zero'); + } + + // make sure we're not exceeding our image size if we're not supposed to + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + $this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']); + + // resize the image to be close to our desired dimensions + $this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + + // reset the max dimensions... + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight); + } else { + $this->workingImage = imagecreate($this->maxWidth, $this->maxHeight); + } + + $this->preserveAlpha(); + + $cropWidth = $this->maxWidth; + $cropHeight = $this->maxHeight; + $cropX = 0; + $cropY = 0; + + // Crop the rest of the image using the quadrant + + if ($percent > 100) { + $percent = 100; + } elseif ($percent < 1) { + $percent = 1; + } + + if ($this->currentDimensions['width'] > $this->maxWidth) { + // Image is landscape + $maxCropX = $this->currentDimensions['width'] - $this->maxWidth; + $cropX = intval(($percent / 100) * $maxCropX); + + } elseif ($this->currentDimensions['height'] > $this->maxHeight) { + // Image is portrait + $maxCropY = $this->currentDimensions['height'] - $this->maxHeight; + $cropY = intval(($percent / 100) * $maxCropY); + } + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + $cropX, + $cropY, + $cropWidth, + $cropHeight, + $cropWidth, + $cropHeight + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->maxWidth; + $this->currentDimensions['height'] = $this->maxHeight; + + return $this; + } + + /** + * Adaptively Resizes the Image and Crops Using a Quadrant + * + * This function attempts to get the image to as close to the provided dimensions as possible, and then crops the + * remaining overflow using the quadrant to get the image to be the size specified. + * + * The quadrants available are Top, Bottom, Center, Left, and Right: + * + * + * +---+---+---+ + * | | T | | + * +---+---+---+ + * | L | C | R | + * +---+---+---+ + * | | B | | + * +---+---+---+ + * + * Note that if your image is Landscape and you choose either of the Top or Bottom quadrants (which won't + * make sence since only the Left and Right would be available, then the Center quadrant will be used + * to crop. This would have exactly the same result as using adaptiveResize(). + * The same goes if your image is portrait and you choose either the Left or Right quadrants. + * + * @param int $maxWidth + * @param int $maxHeight + * @param string $quadrant T, B, C, L, R + * @return \PHPThumb\GD + */ + public function adaptiveResizeQuadrant($width, $height, $quadrant = 'C') + { + // make sure our arguments are valid + if (!is_numeric($width) || $width == 0) { + throw new \InvalidArgumentException('$width must be numeric and greater than zero'); + } + + if (!is_numeric($height) || $height == 0) { + throw new \InvalidArgumentException('$height must be numeric and greater than zero'); + } + + // make sure we're not exceeding our image size if we're not supposed to + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + $this->calcImageSizeStrict($this->currentDimensions['width'], $this->currentDimensions['height']); + + // resize the image to be close to our desired dimensions + $this->resize($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + + // reset the max dimensions... + if ($this->options['resizeUp'] === false) { + $this->maxHeight = (intval($height) > $this->currentDimensions['height']) ? $this->currentDimensions['height'] : $height; + $this->maxWidth = (intval($width) > $this->currentDimensions['width']) ? $this->currentDimensions['width'] : $width; + } else { + $this->maxHeight = intval($height); + $this->maxWidth = intval($width); + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->maxWidth, $this->maxHeight); + } else { + $this->workingImage = imagecreate($this->maxWidth, $this->maxHeight); + } + + $this->preserveAlpha(); + + $cropWidth = $this->maxWidth; + $cropHeight = $this->maxHeight; + $cropX = 0; + $cropY = 0; + + // Crop the rest of the image using the quadrant + + if ($this->currentDimensions['width'] > $this->maxWidth) { + // Image is landscape + switch ($quadrant) { + case 'L': + $cropX = 0; + break; + case 'R': + $cropX = intval(($this->currentDimensions['width'] - $this->maxWidth)); + break; + case 'C': + default: + $cropX = intval(($this->currentDimensions['width'] - $this->maxWidth) / 2); + break; + } + } elseif ($this->currentDimensions['height'] > $this->maxHeight) { + // Image is portrait + switch ($quadrant) { + case 'T': + $cropY = 0; + break; + case 'B': + $cropY = intval(($this->currentDimensions['height'] - $this->maxHeight)); + break; + case 'C': + default: + $cropY = intval(($this->currentDimensions['height'] - $this->maxHeight) / 2); + break; + } + } + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + $cropX, + $cropY, + $cropWidth, + $cropHeight, + $cropWidth, + $cropHeight + ); + + // update all the variables and resources to be correct + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->maxWidth; + $this->currentDimensions['height'] = $this->maxHeight; + + return $this; + } + + /** + * Resizes an image by a given percent uniformly, + * Percentage should be whole number representation (i.e. 1-100) + * + * @param int $percent + * @return GD + * @throws \InvalidArgumentException + */ + public function resizePercent($percent = 0) + { + if (!is_numeric($percent)) { + throw new \InvalidArgumentException ('$percent must be numeric'); + } + + $this->percent = intval($percent); + + $this->calcImageSizePercent($this->currentDimensions['width'], $this->currentDimensions['height']); + + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + } else { + $this->workingImage = imagecreate($this->newDimensions['newWidth'], $this->newDimensions['newHeight']); + } + + $this->preserveAlpha(); + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + 0, + 0, + $this->newDimensions['newWidth'], + $this->newDimensions['newHeight'], + $this->currentDimensions['width'], + $this->currentDimensions['height'] + ); + + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $this->newDimensions['newWidth']; + $this->currentDimensions['height'] = $this->newDimensions['newHeight']; + + return $this; + } + + /** + * Crops an image from the center with provided dimensions + * + * If no height is given, the width will be used as a height, thus creating a square crop + * + * @param int $cropWidth + * @param int $cropHeight + * @return \PHPThumb\GD + */ + public function cropFromCenter($cropWidth, $cropHeight = null) + { + if (!is_numeric($cropWidth)) { + throw new \InvalidArgumentException('$cropWidth must be numeric'); + } + + if ($cropHeight !== null && !is_numeric($cropHeight)) { + throw new \InvalidArgumentException('$cropHeight must be numeric'); + } + + if ($cropHeight === null) { + $cropHeight = $cropWidth; + } + + $cropWidth = ($this->currentDimensions['width'] < $cropWidth) ? $this->currentDimensions['width'] : $cropWidth; + $cropHeight = ($this->currentDimensions['height'] < $cropHeight) ? $this->currentDimensions['height'] : $cropHeight; + + $cropX = intval(($this->currentDimensions['width'] - $cropWidth) / 2); + $cropY = intval(($this->currentDimensions['height'] - $cropHeight) / 2); + + $this->crop($cropX, $cropY, $cropWidth, $cropHeight); + + return $this; + } + + /** + * Vanilla Cropping - Crops from x,y with specified width and height + * + * @param int $startX + * @param int $startY + * @param int $cropWidth + * @param int $cropHeight + * @return \PHPThumb\GD + */ + public function crop($startX, $startY, $cropWidth, $cropHeight) + { + // validate input + if (!is_numeric($startX)) { + throw new \InvalidArgumentException('$startX must be numeric'); + } + + if (!is_numeric($startY)) { + throw new \InvalidArgumentException('$startY must be numeric'); + } + + if (!is_numeric($cropWidth)) { + throw new \InvalidArgumentException('$cropWidth must be numeric'); + } + + if (!is_numeric($cropHeight)) { + throw new \InvalidArgumentException('$cropHeight must be numeric'); + } + + // do some calculations + $cropWidth = ($this->currentDimensions['width'] < $cropWidth) ? $this->currentDimensions['width'] : $cropWidth; + $cropHeight = ($this->currentDimensions['height'] < $cropHeight) ? $this->currentDimensions['height'] : $cropHeight; + + // ensure everything's in bounds + if (($startX + $cropWidth) > $this->currentDimensions['width']) { + $startX = ($this->currentDimensions['width'] - $cropWidth); + } + + if (($startY + $cropHeight) > $this->currentDimensions['height']) { + $startY = ($this->currentDimensions['height'] - $cropHeight); + } + + if ($startX < 0) { + $startX = 0; + } + + if ($startY < 0) { + $startY = 0; + } + + // create the working image + if (function_exists('imagecreatetruecolor')) { + $this->workingImage = imagecreatetruecolor($cropWidth, $cropHeight); + } else { + $this->workingImage = imagecreate($cropWidth, $cropHeight); + } + + $this->preserveAlpha(); + + imagecopyresampled( + $this->workingImage, + $this->oldImage, + 0, + 0, + $startX, + $startY, + $cropWidth, + $cropHeight, + $cropWidth, + $cropHeight + ); + + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $cropWidth; + $this->currentDimensions['height'] = $cropHeight; + + return $this; + } + + /** + * Rotates image either 90 degrees clockwise or counter-clockwise + * + * @param string $direction + * @retunrn \PHPThumb\GD + */ + public function rotateImage($direction = 'CW') + { + if ($direction == 'CW') { + $this->rotateImageNDegrees(90); + } else { + $this->rotateImageNDegrees(-90); + } + + return $this; + } + + /** + * Rotates image specified number of degrees + * + * @param int $degrees + * @return \PHPThumb\GD + */ + public function rotateImageNDegrees($degrees) + { + if (!is_numeric($degrees)) { + throw new \InvalidArgumentException('$degrees must be numeric'); + } + + if (!function_exists('imagerotate')) { + throw new \RuntimeException('Your version of GD does not support image rotation'); + } + + $this->workingImage = imagerotate($this->oldImage, $degrees, 0); + + $newWidth = $this->currentDimensions['height']; + $newHeight = $this->currentDimensions['width']; + $this->oldImage = $this->workingImage; + $this->currentDimensions['width'] = $newWidth; + $this->currentDimensions['height'] = $newHeight; + + return $this; + } + + /** + * Applies a filter to the image + * + * @param int $filter + * @return \PHPThumb\GD + */ + public function imageFilter($filter, $arg1 = false, $arg2 = false, $arg3 = false, $arg4 = false) + { + if (!is_numeric($filter)) { + throw new \InvalidArgumentException('$filter must be numeric'); + } + + if (!function_exists('imagefilter')) { + throw new \RuntimeException('Your version of GD does not support image filters'); + } + + $result = false; + if ($arg1 === false) { + $result = imagefilter($this->oldImage, $filter); + } elseif ($arg2 === false) { + $result = imagefilter($this->oldImage, $filter, $arg1); + } elseif ($arg3 === false) { + $result = imagefilter($this->oldImage, $filter, $arg1, $arg2); + } elseif ($arg4 === false) { + $result = imagefilter($this->oldImage, $filter, $arg1, $arg2, $arg3); + } else { + $result = imagefilter($this->oldImage, $filter, $arg1, $arg2, $arg3, $arg4); + } + + if (!$result) { + throw new \RuntimeException('GD imagefilter failed'); + } + + $this->workingImage = $this->oldImage; + + return $this; + } + + /** + * Shows an image + * + * This function will show the current image by first sending the appropriate header + * for the format, and then outputting the image data. If headers have already been sent, + * a runtime exception will be thrown + * + * @param bool $rawData Whether or not the raw image stream should be output + * @return \PHPThumb\GD + */ + public function show($rawData = false) + { + //Execute any plugins + if ($this->plugins) { + foreach ($this->plugins as $plugin) { + /* @var $plugin \PHPThumb\PluginInterface */ + $plugin->execute($this); + } + } + + if (headers_sent() && php_sapi_name() != 'cli') { + throw new \RuntimeException('Cannot show image, headers have already been sent'); + } + + // When the interlace option equals true or false call imageinterlace else leave it to default + if ($this->options['interlace'] === true) { + imageinterlace($this->oldImage, 1); + } elseif ($this->options['interlace'] === false) { + imageinterlace($this->oldImage, 0); + } + + switch ($this->format) { + case 'GIF': + if ($rawData === false) { + header('Content-type: image/gif'); + } + imagegif($this->oldImage); + break; + case 'JPG': + if ($rawData === false) { + header('Content-type: image/jpeg'); + } + imagejpeg($this->oldImage, null, $this->options['jpegQuality']); + break; + case 'PNG': + case 'STRING': + if ($rawData === false) { + header('Content-type: image/png'); + } + imagepng($this->oldImage); + break; + } + + return $this; + } + + /** + * Returns the Working Image as a String + * + * This function is useful for getting the raw image data as a string for storage in + * a database, or other similar things. + * + * @return string + */ + public function getImageAsString() + { + $data = null; + ob_start(); + $this->show(true); + $data = ob_get_contents(); + ob_end_clean(); + + return $data; + } + + /** + * Saves an image + * + * This function will make sure the target directory is writeable, and then save the image. + * + * If the target directory is not writeable, the function will try to correct the permissions (if allowed, this + * is set as an option ($this->options['correctPermissions']). If the target cannot be made writeable, then a + * \RuntimeException is thrown. + * + * @param string $fileName The full path and filename of the image to save + * @param string $format The format to save the image in (optional, must be one of [GIF,JPG,PNG] + * @return \PHPThumb\GD + */ + public function save($fileName, $format = null) + { + $validFormats = array('GIF', 'JPG', 'PNG'); + $format = ($format !== null) ? strtoupper($format) : $this->format; + + if (!in_array($format, $validFormats)) { + throw new \InvalidArgumentException("Invalid format type specified in save function: {$format}"); + } + + // make sure the directory is writeable + if (!is_writeable(dirname($fileName))) { + // try to correct the permissions + if ($this->options['correctPermissions'] === true) { + @chmod(dirname($fileName), 0777); + + // throw an exception if not writeable + if (!is_writeable(dirname($fileName))) { + throw new \RuntimeException("File is not writeable, and could not correct permissions: {$fileName}"); + } + } else { // throw an exception if not writeable + throw new \RuntimeException("File not writeable: {$fileName}"); + } + } + + // When the interlace option equals true or false call imageinterlace else leave it to default + if ($this->options['interlace'] === true) { + imageinterlace($this->oldImage, 1); + } elseif ($this->options['interlace'] === false) { + imageinterlace($this->oldImage, 0); + } + + switch ($format) { + case 'GIF': + imagegif($this->oldImage, $fileName); + break; + case 'JPG': + imagejpeg($this->oldImage, $fileName, $this->options['jpegQuality']); + break; + case 'PNG': + imagepng($this->oldImage, $fileName); + break; + } + + return $this; + } + + ################################# + # ----- GETTERS / SETTERS ----- # + ################################# + + /** + * Sets options for all operations. + * @param array $options + * @return GD + */ + public function setOptions(array $options = array()) + { + // we've yet to init the default options, so create them here + if (sizeof($this->options) == 0) { + $defaultOptions = array( + 'resizeUp' => false, + 'jpegQuality' => 100, + 'correctPermissions' => false, + 'preserveAlpha' => true, + 'alphaMaskColor' => array (255, 255, 255), + 'preserveTransparency' => true, + 'transparencyMaskColor' => array (0, 0, 0), + 'interlace' => null + ); + } else { // otherwise, let's use what we've got already + $defaultOptions = $this->options; + } + + $this->options = array_merge($defaultOptions, $options); + + return $this; + } + + /** + * Returns $currentDimensions. + * + * @see \PHPThumb\GD::$currentDimensions + */ + public function getCurrentDimensions() + { + return $this->currentDimensions; + } + + /** + * @param $currentDimensions + * @return GD + */ + public function setCurrentDimensions($currentDimensions) + { + $this->currentDimensions = $currentDimensions; + + return $this; + } + + /** + * @return int + */ + public function getMaxHeight() + { + return $this->maxHeight; + } + + /** + * @param $maxHeight + * @return GD + */ + public function setMaxHeight($maxHeight) + { + $this->maxHeight = $maxHeight; + + return $this; + } + + /** + * @return int + */ + public function getMaxWidth() + { + return $this->maxWidth; + } + + /** + * @param $maxWidth + * @return GD + */ + public function setMaxWidth($maxWidth) + { + $this->maxWidth = $maxWidth; + + return $this; + } + + /** + * Returns $newDimensions. + * + * @see \PHPThumb\GD::$newDimensions + */ + public function getNewDimensions() + { + return $this->newDimensions; + } + + /** + * Sets $newDimensions. + * + * @param object $newDimensions + * @see \PHPThumb\GD::$newDimensions + */ + public function setNewDimensions($newDimensions) + { + $this->newDimensions = $newDimensions; + + return $this; + } + + /** + * Returns $options. + * + * @see \PHPThumb\GD::$options + */ + public function getOptions() + { + return $this->options; + } + + /** + * Returns $percent. + * + * @see \PHPThumb\GD::$percent + */ + public function getPercent() + { + return $this->percent; + } + + /** + * Sets $percent. + * + * @param object $percent + * @see \PHPThumb\GD::$percent + */ + public function setPercent($percent) + { + $this->percent = $percent; + + return $this; + } + + /** + * Returns $oldImage. + * + * @see \PHPThumb\GD::$oldImage + */ + public function getOldImage() + { + return $this->oldImage; + } + + /** + * Sets $oldImage. + * + * @param object $oldImage + * @see \PHPThumb\GD::$oldImage + */ + public function setOldImage($oldImage) + { + $this->oldImage = $oldImage; + + return $this; + } + + /** + * Returns $workingImage. + * + * @see \PHPThumb\GD::$workingImage + */ + public function getWorkingImage() + { + return $this->workingImage; + } + + /** + * Sets $workingImage. + * + * @param object $workingImage + * @see \PHPThumb\GD::$workingImage + */ + public function setWorkingImage($workingImage) + { + $this->workingImage = $workingImage; + + return $this; + } + + + ################################# + # ----- UTILITY FUNCTIONS ----- # + ################################# + + /** + * Calculates a new width and height for the image based on $this->maxWidth and the provided dimensions + * + * @return array + * @param int $width + * @param int $height + */ + protected function calcWidth($width, $height) + { + $newWidthPercentage = (100 * $this->maxWidth) / $width; + $newHeight = ($height * $newWidthPercentage) / 100; + + return array( + 'newWidth' => intval($this->maxWidth), + 'newHeight' => intval($newHeight) + ); + } + + /** + * Calculates a new width and height for the image based on $this->maxWidth and the provided dimensions + * + * @return array + * @param int $width + * @param int $height + */ + protected function calcHeight($width, $height) + { + $newHeightPercentage = (100 * $this->maxHeight) / $height; + $newWidth = ($width * $newHeightPercentage) / 100; + + return array( + 'newWidth' => ceil($newWidth), + 'newHeight' => ceil($this->maxHeight) + ); + } + + /** + * Calculates a new width and height for the image based on $this->percent and the provided dimensions + * + * @return array + * @param int $width + * @param int $height + */ + protected function calcPercent($width, $height) + { + $newWidth = ($width * $this->percent) / 100; + $newHeight = ($height * $this->percent) / 100; + + return array( + 'newWidth' => ceil($newWidth), + 'newHeight' => ceil($newHeight) + ); + } + + /** + * Calculates the new image dimensions + * + * These calculations are based on both the provided dimensions and $this->maxWidth and $this->maxHeight + * + * @param int $width + * @param int $height + */ + protected function calcImageSize($width, $height) + { + $newSize = array( + 'newWidth' => $width, + 'newHeight' => $height + ); + + if ($this->maxWidth > 0) { + $newSize = $this->calcWidth($width, $height); + + if ($this->maxHeight > 0 && $newSize['newHeight'] > $this->maxHeight) { + $newSize = $this->calcHeight($newSize['newWidth'], $newSize['newHeight']); + } + } + + if ($this->maxHeight > 0) { + $newSize = $this->calcHeight($width, $height); + + if ($this->maxWidth > 0 && $newSize['newWidth'] > $this->maxWidth) { + $newSize = $this->calcWidth($newSize['newWidth'], $newSize['newHeight']); + } + } + + $this->newDimensions = $newSize; + } + + /** + * Calculates new image dimensions, not allowing the width and height to be less than either the max width or height + * + * @param int $width + * @param int $height + */ + protected function calcImageSizeStrict($width, $height) + { + // first, we need to determine what the longest resize dimension is.. + if ($this->maxWidth >= $this->maxHeight) { + // and determine the longest original dimension + if ($width > $height) { + $newDimensions = $this->calcHeight($width, $height); + + if ($newDimensions['newWidth'] < $this->maxWidth) { + $newDimensions = $this->calcWidth($width, $height); + } + } elseif ($height >= $width) { + $newDimensions = $this->calcWidth($width, $height); + + if ($newDimensions['newHeight'] < $this->maxHeight) { + $newDimensions = $this->calcHeight($width, $height); + } + } + } elseif ($this->maxHeight > $this->maxWidth) { + if ($width >= $height) { + $newDimensions = $this->calcWidth($width, $height); + + if ($newDimensions['newHeight'] < $this->maxHeight) { + $newDimensions = $this->calcHeight($width, $height); + } + } elseif ($height > $width) { + $newDimensions = $this->calcHeight($width, $height); + + if ($newDimensions['newWidth'] < $this->maxWidth) { + $newDimensions = $this->calcWidth($width, $height); + } + } + } + + $this->newDimensions = $newDimensions; + } + + /** + * Calculates new dimensions based on $this->percent and the provided dimensions + * + * @param int $width + * @param int $height + */ + protected function calcImageSizePercent($width, $height) + { + if ($this->percent > 0) { + $this->newDimensions = $this->calcPercent($width, $height); + } + } + + /** + * Determines the file format by mime-type + * + * This function will throw exceptions for invalid images / mime-types + * + */ + protected function determineFormat() + { + $formatInfo = getimagesize($this->fileName); + + // non-image files will return false + if ($formatInfo === false) { + if ($this->remoteImage) { + throw new \Exception("Could not determine format of remote image: {$this->fileName}"); + } else { + throw new \Exception("File is not a valid image: {$this->fileName}"); + } + } + + $mimeType = isset($formatInfo['mime']) ? $formatInfo['mime'] : null; + + switch ($mimeType) { + case 'image/gif': + $this->format = 'GIF'; + break; + case 'image/jpeg': + $this->format = 'JPG'; + break; + case 'image/png': + $this->format = 'PNG'; + break; + default: + throw new \Exception("Image format not supported: {$mimeType}"); + } + } + + /** + * Makes sure the correct GD implementation exists for the file type + * + */ + protected function verifyFormatCompatiblity() + { + $isCompatible = true; + $gdInfo = gd_info(); + + switch ($this->format) { + case 'GIF': + $isCompatible = $gdInfo['GIF Create Support']; + break; + case 'JPG': + $isCompatible = (isset($gdInfo['JPG Support']) || isset($gdInfo['JPEG Support'])) ? true : false; + break; + case 'PNG': + $isCompatible = $gdInfo[$this->format . ' Support']; + break; + default: + $isCompatible = false; + } + + if (!$isCompatible) { + // one last check for "JPEG" instead + $isCompatible = $gdInfo['JPEG Support']; + + if (!$isCompatible) { + throw new \Exception("Your GD installation does not support {$this->format} image types"); + } + } + } + + /** + * Preserves the alpha or transparency for PNG and GIF files + * + * Alpha / transparency will not be preserved if the appropriate options are set to false. + * Also, the GIF transparency is pretty skunky (the results aren't awesome), but it works like a + * champ... that's the nature of GIFs tho, so no huge surprise. + * + * This functionality was originally suggested by commenter Aimi (no links / site provided) - Thanks! :) + * + */ + protected function preserveAlpha() + { + if ($this->format == 'PNG' && $this->options['preserveAlpha'] === true) { + imagealphablending($this->workingImage, false); + + $colorTransparent = imagecolorallocatealpha( + $this->workingImage, + $this->options['alphaMaskColor'][0], + $this->options['alphaMaskColor'][1], + $this->options['alphaMaskColor'][2], + 0 + ); + + imagefill($this->workingImage, 0, 0, $colorTransparent); + imagesavealpha($this->workingImage, true); + } + // preserve transparency in GIFs... this is usually pretty rough tho + if ($this->format == 'GIF' && $this->options['preserveTransparency'] === true) { + $colorTransparent = imagecolorallocate( + $this->workingImage, + $this->options['transparencyMaskColor'][0], + $this->options['transparencyMaskColor'][1], + $this->options['transparencyMaskColor'][2] + ); + + imagecolortransparent($this->workingImage, $colorTransparent); + imagetruecolortopalette($this->workingImage, true, 256); + } + } +} diff --git a/rainloop/v/0.0.0/app/libraries/PHPThumb/PHPThumb.php b/rainloop/v/0.0.0/app/libraries/PHPThumb/PHPThumb.php new file mode 100644 index 000000000..7521b9d90 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/PHPThumb/PHPThumb.php @@ -0,0 +1,149 @@ + + * Copyright (c) 2009, Ian Selby/Gen X Design + * + * Author(s): Ian Selby + * + * Licensed under the MIT License + * Redistributions of files must retain the above copyright notice. + * + * @author Ian Selby + * @copyright Copyright (c) 2009 Gen X Design + * @link http://phpthumb.gxdlabs.com + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + */ + +abstract class PHPThumb +{ + /** + * The name of the file we're manipulating + * This must include the path to the file (absolute paths recommended) + * + * @var string + */ + protected $fileName; + + /** + * @var \Symfony\Component\Filesystem\Filesystem + */ + protected $filesystem; + + /** + * What the file format is (mime-type) + * + * @var string + */ + protected $format; + + /** + * Whether or not the image is hosted remotely + * + * @var bool + */ + protected $remoteImage; + + /** + * An array of attached plugins to execute in order. + * @var array + */ + protected $plugins; + + /** + * @param $fileName + * @param array $options + * @param array $plugins + */ + public function __construct($fileName, array $options = array(), array $plugins = array()) + { + $this->filesystem = new \Symfony\Component\Filesystem\Filesystem(); + $this->fileName = $fileName; + $this->remoteImage = false; + + if(!$this->validateRequestedResource($fileName)) { + throw new \InvalidArgumentException("Image file not found: {$fileName}"); + } + + $this->setOptions($options); + + $this->plugins = $plugins; + } + + abstract public function setOptions(array $options = array()); + + /** + * Check the provided filename/url. If it is a url, validate that it is properly + * formatted. If it is a file, check to make sure that it actually exists on + * the filesystem. + * + * @param $filename + * @return bool + */ + protected function validateRequestedResource($filename) + { + if(false !== filter_var($filename, FILTER_VALIDATE_URL)) { + $this->remoteImage = true; + return true; + } + + if($this->filesystem->exists($filename)) { + return true; + } + + return false; + } + + /** + * Returns the filename. + * @return string + */ + public function getFileName() + { + return $this->fileName; + } + + /** + * Sets the filename. + * @param $fileName + * @return PHPThumb + */ + public function setFileName($fileName) + { + $this->fileName = $fileName; + + return $this; + } + + /** + * Returns the format. + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * Sets the format. + * @param $format + * @return PHPThumb + */ + public function setFormat($format) + { + $this->format = $format; + + return $this; + } + + /** + * Returns whether the image exists remotely, i.e. it was loaded via a URL. + * @return bool + */ + public function getIsRemoteImage() + { + return $this->remoteImage; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/PHPThumb/PluginInterface.php b/rainloop/v/0.0.0/app/libraries/PHPThumb/PluginInterface.php new file mode 100644 index 000000000..56df9768a --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/PHPThumb/PluginInterface.php @@ -0,0 +1,12 @@ + + * Copyright (c) 2009, Ian Selby/Gen X Design + * + * Author(s): Ian Selby + * + * Licensed under the MIT License + * Redistributions of files must retain the above copyright notice. + * + * @author Ian Selby + * @copyright Copyright (c) 2009 Gen X Design + * @link http://phpthumb.gxdlabs.com + * @license http://www.opensource.org/licenses/mit-license.php The MIT License + * @version 3.0 + * @package PhpThumb + * @filesource + */ + +/** + * GD Reflection Lib Plugin + * + * This plugin allows you to create those fun Apple(tm)-style reflections in your images + * + * @package PhpThumb + * @subpackage Plugins + */ +class Reflection implements \PHPThumb\PluginInterface +{ + protected $currentDimensions; + protected $workingImage; + protected $newImage; + protected $options; + + protected $percent; + protected $reflection; + protected $white; + protected $border; + protected $borderColor; + + public function __construct($percent, $reflection, $white, $border, $borderColor) + { + $this->percent = $percent; + $this->reflection = $reflection; + $this->white = $white; + $this->border = $border; + $this->borderColor = $borderColor; + } + + /** + * @param \PHPThumb\PHPThumb $phpthumb + * @return \PHPThumb\PHPThumb + */ + public function execute($phpthumb) + { + $this->currentDimensions = $phpthumb->getCurrentDimensions(); + $this->workingImage = $phpthumb->getWorkingImage(); + $this->newImage = $phpthumb->getOldImage(); + $this->options = $phpthumb->getOptions(); + + $width = $this->currentDimensions['width']; + $height = $this->currentDimensions['height']; + $this->reflectionHeight = intval($height * ($this->reflection / 100)); + $newHeight = $height + $this->reflectionHeight; + $reflectedPart = $height * ($this->percent / 100); + + $this->workingImage = imagecreatetruecolor($width, $newHeight); + + imagealphablending($this->workingImage, true); + + $colorToPaint = imagecolorallocatealpha( + $this->workingImage, + 255, + 255, + 255, + 0 + ); + + imagefilledrectangle( + $this->workingImage, + 0, + 0, + $width, + $newHeight, + $colorToPaint + ); + + imagecopyresampled( + $this->workingImage, + $this->newImage, + 0, + 0, + 0, + $reflectedPart, + $width, + $this->reflectionHeight, + $width, + ($height - $reflectedPart) + ); + + $this->imageFlipVertical(); + + imagecopy( + $this->workingImage, + $this->newImage, + 0, + 0, + 0, + 0, + $width, + $height + ); + + imagealphablending($this->workingImage, true); + + for ($i = 0; $i < $this->reflectionHeight; $i++) { + $colorToPaint = imagecolorallocatealpha( + $this->workingImage, + 255, + 255, + 255, + ($i / $this->reflectionHeight * -1 + 1) * $this->white + ); + + imagefilledrectangle( + $this->workingImage, + 0, + $height + $i, + $width, + $height + $i, + $colorToPaint + ); + } + + if ($this->border == true) { + $rgb = $this->hex2rgb($this->borderColor, false); + $colorToPaint = imagecolorallocate($this->workingImage, $rgb[0], $rgb[1], $rgb[2]); + + //top line + imageline( + $this->workingImage, + 0, + 0, + $width, + 0, + $colorToPaint + ); + + //bottom line + imageline( + $this->workingImage, + 0, + $height, + $width, + $height, + $colorToPaint + ); + + //left line + imageline( + $this->workingImage, + 0, + 0, + 0, + $height, + $colorToPaint + ); + + //right line + imageline( + $this->workingImage, + $width - 1, + 0, + $width - 1, + $height, + $colorToPaint + ); + } + + if ($phpthumb->getFormat() == 'PNG') { + $colorTransparent = imagecolorallocatealpha( + $this->workingImage, + $this->options['alphaMaskColor'][0], + $this->options['alphaMaskColor'][1], + $this->options['alphaMaskColor'][2], + 0 + ); + + imagefill($this->workingImage, 0, 0, $colorTransparent); + imagesavealpha($this->workingImage, true); + } + + $phpthumb->setOldImage($this->workingImage); + $this->currentDimensions['width'] = $width; + $this->currentDimensions['height'] = $newHeight; + $phpthumb->setCurrentDimensions($this->currentDimensions); + + return $phpthumb; + } + + /** + * Flips the image vertically + * + */ + protected function imageFlipVertical () + { + $x_i = imagesx($this->workingImage); + $y_i = imagesy($this->workingImage); + + for ($x = 0; $x < $x_i; $x++) { + for ($y = 0; $y < $y_i; $y++) { + imagecopy( + $this->workingImage, + $this->workingImage, + $x, + $y_i - $y - 1, + $x, + $y, + 1, + 1 + ); + } + } + } + + /** + * Converts a hex color to rgb tuples + * + * @return mixed + * @param string $hex + * @param bool $asString + */ + protected function hex2rgb ($hex, $asString = false) + { + // strip off any leading # + if (0 === strpos($hex, '#')) { + $hex = substr($hex, 1); + } elseif (0 === strpos($hex, '&H')) { + $hex = substr($hex, 2); + } + + // break into hex 3-tuple + $cutpoint = ceil(strlen($hex) / 2)-1; + $rgb = explode(':', wordwrap($hex, $cutpoint, ':', $cutpoint), 3); + + // convert each tuple to decimal + $rgb[0] = (isset($rgb[0]) ? hexdec($rgb[0]) : 0); + $rgb[1] = (isset($rgb[1]) ? hexdec($rgb[1]) : 0); + $rgb[2] = (isset($rgb[2]) ? hexdec($rgb[2]) : 0); + + return ($asString ? "{$rgb[0]} {$rgb[1]} {$rgb[2]}" : $rgb); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Account.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Account.php deleted file mode 100644 index 27202f1f9..000000000 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Account.php +++ /dev/null @@ -1,206 +0,0 @@ -sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true); - $this->sLogin = \MailSo\Base\Utils::IdnToAscii($sLogin); - $this->sPassword = $sPassword; - $this->oDomain = $oDomain; - $this->sSignMeToken = $sSignMeToken; - } - - /** - * @param string $sEmail - * @param string $sLogin - * @param string $sPassword - * @param \RainLoop\Domain $oDomain - * @param string $sSignMeToken = '' - * - * @return \RainLoop\Account - */ - public static function NewInstance($sEmail, $sLogin, $sPassword, \RainLoop\Domain $oDomain, $sSignMeToken = '') - { - return new self($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken); - } - - /** - * @return string - */ - public function Email() - { - return $this->sEmail; - } - - /** - * @return string - */ - public function ParentEmail() - { - return $this->sParentEmail; - } - - /** - * @return string - */ - public function ParentEmailHelper() - { - return 0 < \strlen($this->sParentEmail) ? $this->sParentEmail : $this->sEmail; - } - - /** - * @return string - */ - public function IncLogin() - { - $sLogin = $this->sLogin; - if ($this->oDomain->IncShortLogin()) - { - $sLogin = \MailSo\Base\Utils::GetAccountNameFromEmail($this->sLogin); - } - - return $sLogin; - } - - /** - * @return string - */ - public function OutLogin() - { - $sLogin = $this->sLogin; - if ($this->oDomain->OutShortLogin()) - { - $sLogin = \MailSo\Base\Utils::GetAccountNameFromEmail($this->sLogin); - } - - return $sLogin; - } - - /** - * @return string - */ - public function Login() - { - return $this->IncLogin(); - } - - /** - * @return string - */ - public function Password() - { - return $this->sPassword; - } - - /** - * @return bool - */ - public function SignMe() - { - return 0 < \strlen($this->sSignMeToken); - } - - /** - * @return string - */ - public function SignMeToken() - { - return $this->sSignMeToken; - } - - /** - * @return \RainLoop\Domain - */ - public function Domain() - { - return $this->oDomain; - } - - /** - * @return \RainLoop\Domain - */ - public function Hash() - { - return md5(APP_SALT.$this->Email().APP_SALT.$this->oDomain->IncHost(\MailSo\Base\Utils::GetDomainFromEmail($this->Email())). - APP_SALT.$this->oDomain->IncPort().APP_SALT.$this->Password().APP_SALT.'0'.APP_SALT.$this->ParentEmail().APP_SALT); - } - - /** - * @param string $sPassword - * - * @return void - */ - public function SetPassword($sPassword) - { - $this->sPassword = $sPassword; - } - - /** - * @param string $sParentEmail - * - * @return void - */ - public function SetParentEmail($sParentEmail) - { - $this->sParentEmail = \MailSo\Base\Utils::IdnToAscii($sParentEmail, true); - } - - /** - * @return string - */ - public function GetAuthToken() - { - return \RainLoop\Utils::EncodeKeyValues(array( - 'token', - $this->sEmail, - $this->sLogin, - $this->sPassword, - \RainLoop\Utils::Fingerprint(), - $this->sSignMeToken, - $this->sParentEmail, - \RainLoop\Utils::GetShortToken() - )); - } -} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index f427dc185..713062acf 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -40,21 +40,31 @@ class Actions */ private $oLogger; + /** + * @var \MailSo\Log\Logger + */ + private $oLoggerAuth; + /** * @var \RainLoop\Social */ private $oSocial; /** - * @var \MailSo\Cache\CacheClient + * @var array of \MailSo\Cache\CacheClient */ - private $oCacher; + private $aCachers; /** * @var \RainLoop\Providers\Storage */ private $oStorageProvider; + /** + * @var \RainLoop\Providers\Storage + */ + private $oLocalStorageProvider; + /** * @var \RainLoop\Providers\Files */ @@ -70,6 +80,16 @@ class Actions */ private $oSettingsProvider; + /** + * @var \RainLoop\Providers\Settings + */ + private $oLocalSettingsProvider; + + /** + * @var \RainLoop\Providers\Filters + */ + private $oFiltersProvider; + /** * @var \RainLoop\Providers\AddressBook */ @@ -113,11 +133,14 @@ class Actions $this->oMailClient = null; $this->oSocial = null; $this->oConfig = null; - $this->oCacher = null; + $this->aCachers = array(); $this->oStorageProvider = null; - $this->oFilesProvider = null; + $this->oLocalStorageProvider = null; $this->oSettingsProvider = null; + $this->oLocalSettingsProvider = null; + $this->oFilesProvider = null; + $this->oFiltersProvider = null; $this->oDomainProvider = null; $this->oAddressBookProvider = null; $this->oSuggestionsProvider = null; @@ -160,6 +183,22 @@ class Actions return $this->sSpecAuthToken; } + /** + * @return string + */ + public function GetShortLifeSpecAuthToken($iLife = 60) + { + $sToken = $this->getAuthToken(); + $aAccountHash = \RainLoop\Utils::DecodeKeyValues($sToken); + if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && is_array($aAccountHash)) + { + $aAccountHash[10] = \time() + $iLife; + return \RainLoop\Utils::EncodeKeyValues($aAccountHash); + } + + return ''; + } + /** * @return \RainLoop\Application */ @@ -190,7 +229,7 @@ class Actions /** * @param string $sName - * @param \RainLoop\Account $oAccount = null + * @param \RainLoop\Model\Account $oAccount = null * * @return mixed */ @@ -207,16 +246,20 @@ class Actions switch ($sName) { case 'files': - // RainLoop\Providers\Files\FilesInterface - $oResult = new \RainLoop\Providers\Files\DefaultStorage(APP_PRIVATE_DATA.'storage/files'); + // RainLoop\Providers\Files\IFiles + $oResult = new \RainLoop\Providers\Files\FileStorage(APP_PRIVATE_DATA.'storage/files'); break; case 'storage': - // RainLoop\Providers\Storage\StorageInterface - $oResult = new \RainLoop\Providers\Storage\DefaultStorage(APP_PRIVATE_DATA.'storage'); + case 'storage-local': + // RainLoop\Providers\Storage\IStorage + $oResult = new \RainLoop\Providers\Storage\FileStorage( + APP_PRIVATE_DATA.'storage', 'storage-local' === $sName); break; case 'settings': - // RainLoop\Providers\Settings\SettingsInterface - $oResult = new \RainLoop\Providers\Settings\DefaultSettings($this->StorageProvider()); + case 'settings-local': + // RainLoop\Providers\Settings\ISettings + $oResult = new \RainLoop\Providers\Settings\DefaultSettings( + $this->StorageProvider('settings-local' === $sName)); break; case 'login': // \RainLoop\Providers\Login\LoginInterface @@ -226,28 +269,42 @@ class Actions // \RainLoop\Providers\Domain\DomainAdminInterface $oResult = new \RainLoop\Providers\Domain\DefaultDomain(APP_PRIVATE_DATA.'domains', $this->Cacher()); break; + case 'filters': + // \RainLoop\Providers\Filters\FiltersInterface + $oResult = new \RainLoop\Providers\Filters\SieveStorage( + $this->Plugins(), $this->Config() + ); + break; case 'address-book': // \RainLoop\Providers\AddressBook\AddressBookInterface - $sDsn = \trim($this->Config()->Get('contacts', 'pdo_dsn', '')); - $sUser = \trim($this->Config()->Get('contacts', 'pdo_user', '')); - $sPassword = (string) $this->Config()->Get('contacts', 'pdo_password', ''); - - $sDsnType = $this->ValidateContactPdoType(\trim($this->Config()->Get('contacts', 'type', 'sqlite'))); - if ('sqlite' === $sDsnType) + if (!\RainLoop\Utils::IsOwnCloud()) // disabled for ownCloud { - $oResult = new \RainLoop\Providers\AddressBook\PdoAddressBook( - 'sqlite:'.APP_PRIVATE_DATA.'AddressBook.sqlite', '', '', 'sqlite'); - } - else - { - $oResult = new \RainLoop\Providers\AddressBook\PdoAddressBook($sDsn, $sUser, $sPassword, $sDsnType); - } + $sDsn = \trim($this->Config()->Get('contacts', 'pdo_dsn', '')); + $sUser = \trim($this->Config()->Get('contacts', 'pdo_user', '')); + $sPassword = (string) $this->Config()->Get('contacts', 'pdo_password', ''); - $oResult->SetLogger($this->Logger()); + $sDsnType = $this->ValidateContactPdoType(\trim($this->Config()->Get('contacts', 'type', 'sqlite'))); + if ('sqlite' === $sDsnType) + { + $oResult = new \RainLoop\Providers\AddressBook\PdoAddressBook( + 'sqlite:'.APP_PRIVATE_DATA.'AddressBook.sqlite', '', '', 'sqlite'); + } + else + { + $oResult = new \RainLoop\Providers\AddressBook\PdoAddressBook($sDsn, $sUser, $sPassword, $sDsnType); + } + } break; case 'suggestions': - // \RainLoop\Providers\Suggestions\SuggestionsInterface + // \RainLoop\Providers\Suggestions\ISuggestions +// $oResult = new \RainLoop\Providers\Suggestions\TestSuggestions(); + + if (\RainLoop\Utils::IsOwnCloud()) + { + $oResult = new \RainLoop\Providers\Suggestions\OwnCloudSuggestions(); + } + break; case 'change-password': // \RainLoop\Providers\ChangePassword\ChangePasswordInterface @@ -259,6 +316,11 @@ class Actions } } + if ($oResult && \method_exists($oResult, 'SetLogger')) + { + $oResult->SetLogger($this->Logger()); + } + $this->Plugins()->RunHook('filter.fabrica', array($sName, &$oResult, $oAccount), false); return $oResult; @@ -276,7 +338,7 @@ class Actions { $this->KeenIO('Install'); } - catch (\Exception $oException) {} + catch (\Exception $oException) { unset($oException); } } } @@ -292,7 +354,7 @@ class Actions $this->MailClient()->LogoutAndDisconnect(); } } - catch (\Exception $oException) {} + catch (\Exception $oException) { unset($oException); } } /** @@ -300,78 +362,155 @@ class Actions */ public function ParseQueryAuthString() { + $sQuery = \trim($this->Http()->GetQueryString()); + + $iPos = \strpos($sQuery, '&'); + if (0 < $iPos) + { + $sQuery = \substr($sQuery, 0, $iPos); + } + + $sQuery = \trim(\trim($sQuery), ' /'); + + $aSubQuery = $this->Http()->GetQuery('q', null); + if (\is_array($aSubQuery)) + { + $aSubQuery = \array_map(function ($sS) { + return \trim(\trim($sS), ' /'); + }, $aSubQuery); + + if (0 < \count($aSubQuery)) + { + $sQuery .= '/'.\implode('/', $aSubQuery); + } + } + if ('' === $this->GetSpecAuthToken()) { - $sQuery = \trim(\trim($this->Http()->GetServer('QUERY_STRING', '')), ' /'); - $iPos = \strpos($sQuery, '&'); - if (0 < $iPos) - { - $sQuery = \substr($sQuery, 0, $iPos); - } - $aPaths = \explode('/', $sQuery); if (!empty($aPaths[0]) && !empty($aPaths[1]) && '_' === substr($aPaths[1], 0, 1)) { $this->SetSpecAuthToken($aPaths[1]); } } + + return $sQuery; } /** + * @param string $sLine + * @param \RainLoop\Model\Account $oAccount = null + * * @return string */ - private function compileLogFileName() + private function compileLogParams($sLine, $oAccount = null) { - $sFileName = (string) $this->Config()->Get('logs', 'filename', ''); - - if (false !== \strpos($sFileName, '{date:')) + if (false !== \strpos($sLine, '{date:')) { - $sFileName = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) { + $sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) { return \gmdate($aMatch[1]); - }, $sFileName); + }, $sLine); - $sFileName = \preg_replace('/\{date:([^}]*)\}/', 'date', $sFileName); + $sLine = \preg_replace('/\{date:([^}]*)\}/', 'date', $sLine); } - if (false !== \strpos($sFileName, '{user:')) + if (false !== \strpos($sLine, '{imap:') || false !== \strpos($sLine, '{smtp:')) { - if (false !== \strpos($sFileName, '{user:uid}')) + if (!$oAccount) { - $sFileName = \str_replace('{user:uid}', + $this->ParseQueryAuthString(); + $oAccount = $this->getAccountFromToken(false); + } + + if ($oAccount) + { + $sLine = \str_replace('{imap:login}', $oAccount->IncLogin(), $sLine); + $sLine = \str_replace('{imap:host}', $oAccount->DomainIncHost(), $sLine); + $sLine = \str_replace('{imap:port}', $oAccount->DomainIncPort(), $sLine); + + $sLine = \str_replace('{smtp:login}', $oAccount->OutLogin(), $sLine); + $sLine = \str_replace('{smtp:host}', $oAccount->DomainOutHost(), $sLine); + $sLine = \str_replace('{smtp:port}', $oAccount->DomainOutPort(), $sLine); + } + + $sLine = \preg_replace('/\{imap:([^}]*)\}/i', 'imap', $sLine); + $sLine = \preg_replace('/\{smtp:([^}]*)\}/i', 'imap', $sLine); + } + + if (false !== \strpos($sLine, '{request:')) + { + if (false !== \strpos($sLine, '{request:ip}')) + { + $sLine = \str_replace('{request:ip}', $this->Http()->GetClientIp( + $this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $sLine); + } + + $sLine = \preg_replace('/\{request:([^}]*)\}/i', 'request', $sLine); + } + + if (false !== \strpos($sLine, '{user:')) + { + if (false !== \strpos($sLine, '{user:uid}')) + { + $sLine = \str_replace('{user:uid}', \base_convert(\sprintf('%u', \crc32(\md5(\RainLoop\Utils::GetConnectionToken()))), 10, 32), - $sFileName + $sLine ); } - if (false !== \strpos($sFileName, '{user:ip}')) + if (false !== \strpos($sLine, '{user:ip}')) { - $sFileName = \str_replace('{user:ip}', $this->Http()->GetClientIp(), $sFileName); + $sLine = \str_replace('{user:ip}', $this->Http()->GetClientIp( + $this->Config()->Get('labs', 'http_client_ip_check_proxy', false)), $sLine); } - if (\preg_match('/\{user:(email|login|domain)\}/i', $sFileName)) + if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine)) { - $this->ParseQueryAuthString(); + if (!$oAccount) + { + $this->ParseQueryAuthString(); + $oAccount = $this->getAccountFromToken(false); + } - $oAccount = $this->getAccountFromToken(false); if ($oAccount) { $sEmail = $oAccount->Email(); - $sFileName = \str_replace('{user:email}', $sEmail, $sFileName); - $sFileName = \str_replace('{user:login}', \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sFileName); - $sFileName = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sFileName); + $sLine = \str_replace('{user:email}', $sEmail, $sLine); + $sLine = \str_replace('{user:login}', \MailSo\Base\Utils::GetAccountNameFromEmail($sEmail), $sLine); + $sLine = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sLine); } } - $sFileName = \preg_replace('/\{user:([^}]*)\}/i', 'unknown', $sFileName); + $sLine = \preg_replace('/\{user:([^}]*)\}/i', 'unknown', $sLine); } - if (false !== \strpos($sFileName, '{labs:')) + if (false !== \strpos($sLine, '{labs:')) { - $sFileName = \preg_replace_callback('/\{labs:rand:([1-9])\}/', function ($aMatch) { + $sLine = \preg_replace_callback('/\{labs:rand:([1-9])\}/', function ($aMatch) { return \rand(\pow(10, $aMatch[1] - 1), \pow(10, $aMatch[1]) - 1); - }, $sFileName); + }, $sLine); - $sFileName = \preg_replace('/\{labs:([^}]*)\}/', 'labs', $sFileName); + $sLine = \preg_replace('/\{labs:([^}]*)\}/', 'labs', $sLine); + } + + return $sLine; + } + + /** + * @param string $sFileName + * + * @return string + */ + private function compileLogFileName($sFileName) + { + $sFileName = \trim($sFileName); + + if (0 !== \strlen($sFileName)) + { + $sFileName = $this->compileLogParams($sFileName); + + $sFileName = \preg_replace('/[\/]+/', '/', \preg_replace('/[.]+/', '.', $sFileName)); + $sFileName = \preg_replace('/[^a-zA-Z0-9@_+=\-\.\/!()\[\]]/', '', $sFileName); } if (0 === \strlen($sFileName)) @@ -379,9 +518,6 @@ class Actions $sFileName = 'rainloop-log.txt'; } - $sFileName = \preg_replace('/[\/]+/', '/', \preg_replace('/[.]+/', '.', $sFileName)); - $sFileName = \preg_replace('/[^a-zA-Z0-9@_+=\-\.\/!()\[\]]/', '', $sFileName); - return $sFileName; } @@ -390,11 +526,12 @@ class Actions */ public function SetAuthLogoutToken() { - \RainLoop\Utils::SetCookie(self::AUTH_SPEC_TOKEN_KEY, \md5(APP_START_TIME), 0, '/', null, null, true); + @\header('X-RainLoop-Action: Logout'); + \RainLoop\Utils::SetCookie(self::AUTH_SPEC_LOGOUT_TOKEN_KEY, \md5(APP_START_TIME), 0, '/', null, null, true); } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return void */ @@ -409,13 +546,21 @@ class Actions if ($oAccount->SignMe() && 0 < \strlen($oAccount->SignMeToken())) { - \RainLoop\Utils::SetCookie(self::AUTH_SIGN_ME_TOKEN_KEY, $oAccount->SignMeToken(), + \RainLoop\Utils::SetCookie(self::AUTH_SIGN_ME_TOKEN_KEY, + \RainLoop\Utils::EncodeKeyValues(array( + 'e' => $oAccount->Email(), + 't' => $oAccount->SignMeToken() + )), \time() + 60 * 60 * 24 * 30, '/', null, null, true); - $this->StorageProvider()->Put(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::SignMeUserToken($oAccount->SignMeToken()), - $oAccount->GetAuthToken() + $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'sign_me', + \RainLoop\Utils::EncodeKeyValues(array( + 'Time' => \time(), + 'AuthToken' => $oAccount->GetAuthToken(), + 'SignMetToken' => $oAccount->SignMeToken() + )) ); } } @@ -463,7 +608,7 @@ class Actions private function getAuthToken() { $sToken = $this->GetSpecAuthToken(); - return $sToken && '_' === \substr($sToken, 0, 1) ? \substr($sToken, 1) : ''; + return !empty($sToken) && '_' === \substr($sToken, 0, 1) ? \substr($sToken, 1) : ''; } /** @@ -479,13 +624,22 @@ class Actions */ public function ClearAdminAuthToken() { + $aAdminHash = \RainLoop\Utils::DecodeKeyValues($this->getAdminAuthToken()); + if ( + !empty($aAdminHash[0]) && !empty($aAdminHash[1]) && !empty($aAdminHash[2]) && + 'token' === $aAdminHash[0] && \md5(APP_SALT) === $aAdminHash[1] + ) + { + $this->Cacher(null, true)->Delete(\RainLoop\KeyPathHelper::SessionAdminKey($aAdminHash[2])); + } + \RainLoop\Utils::ClearCookie(self::AUTH_ADMIN_TOKEN_KEY); } /** * @param bool $bThrowExceptionOnFalse = false * - * @return \RainLoop\Account|bool + * @return \RainLoop\Model\Account|bool * @throws \RainLoop\Exceptions\ClientException */ public function GetAccount($bThrowExceptionOnFalse = false) @@ -534,17 +688,17 @@ class Actions } /** - * @return \RainLoop\Providers\Settings + * @return \RainLoop\Providers\Filters */ - public function SettingsProvider() + public function FiltersProvider() { - if (null === $this->oSettingsProvider) + if (null === $this->oFiltersProvider) { - $this->oSettingsProvider = new \RainLoop\Providers\Settings( - $this->fabrica('settings')); + $this->oFiltersProvider = new \RainLoop\Providers\Filters( + $this->fabrica('filters')); } - return $this->oSettingsProvider; + return $this->oFiltersProvider; } /** @@ -555,7 +709,7 @@ class Actions if (null === $this->oChangePasswordProvider) { $this->oChangePasswordProvider = new \RainLoop\Providers\ChangePassword( - $this, $this->fabrica('change-password'), !!$this->Config()->Get('labs', 'check_new_password_strength', false) + $this, $this->fabrica('change-password'), !!$this->Config()->Get('labs', 'check_new_password_strength', true) ); } @@ -578,17 +732,63 @@ class Actions } /** + * @param bool $bLocal = false + * * @return \RainLoop\Providers\Storage */ - public function StorageProvider() + public function StorageProvider($bLocal = false) { - if (null === $this->oStorageProvider) + if ($bLocal) { - $this->oStorageProvider = new \RainLoop\Providers\Storage( - $this->fabrica('storage')); + if (null === $this->oLocalStorageProvider) + { + $this->oLocalStorageProvider = new \RainLoop\Providers\Storage( + $this->fabrica('storage-local')); + } + + return $this->oLocalStorageProvider; + } + else + { + if (null === $this->oStorageProvider) + { + $this->oStorageProvider = new \RainLoop\Providers\Storage( + $this->fabrica('storage')); + } + + return $this->oStorageProvider; } - return $this->oStorageProvider; + return null; + } + + /** + * @return \RainLoop\Providers\Settings + */ + public function SettingsProvider($bLocal = false) + { + if ($bLocal) + { + if (null === $this->oLocalSettingsProvider) + { + $this->oLocalSettingsProvider = new \RainLoop\Providers\Settings( + $this->fabrica('settings-local')); + } + + return $this->oLocalSettingsProvider; + } + else + { + if (null === $this->oSettingsProvider) + { + $this->oSettingsProvider = new \RainLoop\Providers\Settings( + $this->fabrica('settings')); + } + + return $this->oSettingsProvider; + } + + return null; } /** @@ -613,7 +813,7 @@ class Actions if (null === $this->oDomainProvider) { $this->oDomainProvider = new \RainLoop\Providers\Domain( - $this->fabrica('domain')); + $this->fabrica('domain'), $this->Plugins()); } return $this->oDomainProvider; @@ -633,7 +833,7 @@ class Actions } /** - * @param \RainLoop\Account $oAccount = null + * @param \RainLoop\Model\Account $oAccount = null * @param bool $bForceEnable = false * * @return \RainLoop\Providers\AddressBook @@ -652,43 +852,66 @@ class Actions } /** + * @param \RainLoop\Model\Account $oAccount = null + * @param bool $bForceFile = false + * * @return \MailSo\Cache\CacheClient */ - public function Cacher() + public function Cacher($oAccount = null, $bForceFile = false) { - if (null === $this->oCacher) + $sKey = ''; + if ($oAccount) { - $this->oCacher = \MailSo\Cache\CacheClient::NewInstance(); + $sKey = $oAccount->ParentEmailHelper(); + } + + $sIndexKey = empty($sKey) ? '_default_' : $sKey; + if ($bForceFile) + { + $sIndexKey .= '/_files_'; + } + + if (!isset($this->aCachers[$sIndexKey])) + { + $this->aCachers[$sIndexKey] = \MailSo\Cache\CacheClient::NewInstance(); $oDriver = null; $sDriver = \strtoupper(\trim($this->Config()->Get('cache', 'fast_cache_driver', 'files'))); switch (true) { - case 'APC' === $sDriver && \MailSo\Base\Utils::FunctionExistsAndEnabled('apc_store'): - $oDriver = \MailSo\Cache\Drivers\APC::NewInstance(); + default: + case $bForceFile: + $oDriver = \MailSo\Cache\Drivers\File::NewInstance(APP_PRIVATE_DATA.'cache', $sKey); break; - case 'MEMCACHE' === $sDriver && \MailSo\Base\Utils::FunctionExistsAndEnabled('memcache_connect'): - case 'MEMCACHED' === $sDriver && \MailSo\Base\Utils::FunctionExistsAndEnabled('memcache_connect'): + + case ('APC' === $sDriver || 'APCU' === $sDriver) && + \MailSo\Base\Utils::FunctionExistsAndEnabled(array( + 'apc_store', 'apc_fetch', 'apc_delete', 'apc_clear_cache')): + + $oDriver = \MailSo\Cache\Drivers\APC::NewInstance($sKey); + break; + + case ('MEMCACHE' === $sDriver || 'MEMCACHED' === $sDriver) && + \MailSo\Base\Utils::FunctionExistsAndEnabled('memcache_connect'): + $oDriver = \MailSo\Cache\Drivers\Memcache::NewInstance( $this->Config()->Get('labs', 'fast_cache_memcache_host', '127.0.0.1'), - (int) $this->Config()->Get('labs', 'fast_cache_memcache_port', 11211) + (int) $this->Config()->Get('labs', 'fast_cache_memcache_port', 11211), + 43200, $sKey ); break; - default: - $oDriver = \MailSo\Cache\Drivers\File::NewInstance(APP_PRIVATE_DATA.'cache'); - break; } if ($oDriver) { - $this->oCacher->SetDriver($oDriver); + $this->aCachers[$sIndexKey]->SetDriver($oDriver); } - $this->oCacher->SetCacheIndex($this->Config()->Get('cache', 'fast_cache_index', '')); + $this->aCachers[$sIndexKey]->SetCacheIndex($this->Config()->Get('cache', 'fast_cache_index', '')); } - return $this->oCacher; + return $this->aCachers[$sIndexKey]; } /** @@ -714,11 +937,13 @@ class Actions { $this->oLogger = \MailSo\Log\Logger::SingletonInstance(); - if (!!$this->Config()->Get('logs', 'enable', true)) + if (!!$this->Config()->Get('logs', 'enable', false)) { $this->oLogger->SetShowSecter(!$this->Config()->Get('logs', 'hide_passwords', true)); - $sLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName(); + $sLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName( + $this->Config()->Get('logs', 'filename', '')); + $sLogFileDir = \dirname($sLogFileFullPath); if (!@is_dir($sLogFileDir)) @@ -741,9 +966,25 @@ class Actions $this->oLogger->WriteEmptyLine(); $oHttp = $this->Http(); + $this->oLogger->Write('[DATE:'.\gmdate('d.m.y').'][RL:'.APP_VERSION.'][PHP:'.PHP_VERSION.'][IP:'. - $oHttp->GetClientIp().'][PID:'.(\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? \getmypid() : 'unknown'). - '][GUID:'.\MailSo\Log\Logger::Guid().']'); + $oHttp->GetClientIp($this->Config()->Get('labs', 'http_client_ip_check_proxy', false)).'][PID:'. + (\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? \getmypid() : 'unknown').']['. + $oHttp->GetServer('SERVER_SOFTWARE', '~').']['. + (\MailSo\Base\Utils::FunctionExistsAndEnabled('php_sapi_name') ? \php_sapi_name() : '~' ).']' + ); + + $sPdo = (\class_exists('PDO') ? \implode(',', \PDO::getAvailableDrivers()) : 'off'); + $sPdo = empty($sPdo) ? '~' : $sPdo; + + $this->oLogger->Write('['. + 'Suhosin:'.(\extension_loaded('suhosin') || @\ini_get('suhosin.get.max_value_length') ? 'on' : 'off'). + '][APC:'.(\MailSo\Base\Utils::FunctionExistsAndEnabled('apc_fetch') ? 'on' : 'off'). + '][MB:'.(\MailSo\Base\Utils::FunctionExistsAndEnabled('mb_convert_encoding') ? 'on' : 'off'). + '][PDO:'.$sPdo. + (\RainLoop\Utils::IsOwnCloud() ? '][ownCloud:true' : ''). + '][Streams:'.\implode(',', \stream_get_transports()). + ']'); $this->oLogger->Write( '['.$oHttp->GetMethod().'] '.$oHttp->GetScheme().'://'.$oHttp->GetHost(false, false).$oHttp->GetServer('REQUEST_URI', ''), @@ -755,11 +996,54 @@ class Actions } /** - * @return array + * @return \MailSo\Log\Logger + */ + public function LoggerAuth() + { + if (null === $this->oLoggerAuth) + { + $this->oLoggerAuth = \MailSo\Log\Logger::NewInstance(false); + + if (!!$this->Config()->Get('logs', 'auth_logging', false)) + { + $sAuthLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName( + $this->Config()->Get('logs', 'auth_logging_filename', '')); + + $sLogFileDir = \dirname($sAuthLogFileFullPath); + + if (!@is_dir($sLogFileDir)) + { + @mkdir($sLogFileDir, 0755, true); + } + + $this->oLoggerAuth->AddForbiddenType(\MailSo\Log\Enumerations\Type::MEMORY); + $this->oLoggerAuth->AddForbiddenType(\MailSo\Log\Enumerations\Type::TIME); + $this->oLoggerAuth->AddForbiddenType(\MailSo\Log\Enumerations\Type::TIME_DELTA); + + $this->oLoggerAuth->Add( + \MailSo\Log\Drivers\File::NewInstance($sAuthLogFileFullPath) + ); + } + } + + return $this->oLoggerAuth; + } + + /** + * @return string */ private function getAdminToken() { - return \RainLoop\Utils::EncodeKeyValues(array('token', \md5(APP_SALT))); + $sRand = \MailSo\Base\Utils::Md5Rand(); + if (!$this->Cacher(null, true)->Set(\RainLoop\KeyPathHelper::SessionAdminKey($sRand), \time())) + { + $this->oLogger->Write('Cannot store an admin token', + \MailSo\Log\Enumerations\Type::WARNING); + + $sRand = ''; + } + + return '' === $sRand ? '' : \RainLoop\Utils::EncodeKeyValues(array('token', \md5(APP_SALT), $sRand)); } /** @@ -773,17 +1057,16 @@ class Actions if ($this->Config()->Get('security', 'allow_admin_panel', true)) { $aAdminHash = \RainLoop\Utils::DecodeKeyValues($this->getAdminAuthToken()); - if ((!empty($aAdminHash[0]) && !empty($aAdminHash[1]) && - 'token' === $aAdminHash[0] && \md5(APP_SALT) === $aAdminHash[1])) + if (!empty($aAdminHash[0]) && !empty($aAdminHash[1]) && !empty($aAdminHash[2]) && + 'token' === $aAdminHash[0] && \md5(APP_SALT) === $aAdminHash[1] && + '' !== $this->Cacher(null, true)->Get(\RainLoop\KeyPathHelper::SessionAdminKey($aAdminHash[2]), '') + ) { $bResult = true; } - else if ($bThrowExceptionOnFalse) - { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); - } } - else if ($bThrowExceptionOnFalse) + + if (!$bResult && $bThrowExceptionOnFalse) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } @@ -811,23 +1094,41 @@ class Actions * @param string $sEmail * @param string $sLogin * @param string $sPassword - * @param bool $sSignMeToken = '' + * @param string $sSignMeToken = '' + * @param bool $bThrowProvideException = false * - * @return \RainLoop\Account|null + * @return \RainLoop\Model\Account|null */ - public function LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken = '') + public function LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken = '', $bThrowProvideException = false) { - $oResult = null; + $oAccount = null; if (0 < \strlen($sEmail) && 0 < \strlen($sLogin) && 0 < \strlen($sPassword)) { $oDomain = $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true); - if ($oDomain instanceof \RainLoop\Domain && $oDomain->ValidateWhiteList($sEmail, $sLogin)) + if ($oDomain instanceof \RainLoop\Model\Domain) { - $oResult = \RainLoop\Account::NewInstance($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken); + if ($oDomain->ValidateWhiteList($sEmail, $sLogin)) + { + $oAccount = \RainLoop\Model\Account::NewInstance($sEmail, $sLogin, $sPassword, $oDomain, $sSignMeToken); + $this->Plugins()->RunHook('filter.acount', array(&$oAccount)); + + if ($bThrowProvideException && !($oAccount instanceof \RainLoop\Model\Account)) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); + } + } + else if ($bThrowProvideException) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountNotAllowed); + } + } + else if ($bThrowProvideException) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::DomainNotAllowed); } } - return $oResult; + return $oAccount; } /** @@ -835,7 +1136,7 @@ class Actions * @param bool $bThrowExceptionOnFalse = true * @param bool $bValidateShortToken = true * - * @return \RainLoop\Account|bool + * @return \RainLoop\Model\Account|bool * @throws \RainLoop\Exceptions\ClientException */ public function GetAccountFromCustomToken($sToken, $bThrowExceptionOnFalse = true, $bValidateShortToken = true) @@ -844,36 +1145,30 @@ class Actions if (!empty($sToken)) { $aAccountHash = \RainLoop\Utils::DecodeKeyValues($sToken); - if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && 8 === \count($aAccountHash) && -// !empty($aAccountHash[4]) && \RainLoop\Utils::Fingerprint() === $aAccountHash[4] && - !empty($aAccountHash[7]) && (!$bValidateShortToken || \RainLoop\Utils::GetShortToken() === $aAccountHash[7]) + if (!empty($aAccountHash[0]) && 'token' === $aAccountHash[0] && // simple token validation + 8 <= \count($aAccountHash) && // length checking + !empty($aAccountHash[7]) && // does short token exist + (!$bValidateShortToken || \RainLoop\Utils::GetShortToken() === $aAccountHash[7] || // check short token if needed + (isset($aAccountHash[10]) && 0 < $aAccountHash[10] && \time() < $aAccountHash[10])) ) { $oAccount = $this->LoginProvide($aAccountHash[1], $aAccountHash[2], $aAccountHash[3], - empty($aAccountHash[5]) ? '' : $aAccountHash[5]); + empty($aAccountHash[5]) ? '' : $aAccountHash[5], $bThrowExceptionOnFalse); - if ($oAccount instanceof \RainLoop\Account) + if ($oAccount instanceof \RainLoop\Model\Account) { + if (!empty($aAccountHash[8]) && !empty($aAccountHash[9])) // init proxy user/password + { + $oAccount->SetProxyAuthUser($aAccountHash[8]); + $oAccount->SetProxyAuthPassword($aAccountHash[9]); + } + $this->Logger()->AddSecret($oAccount->Password()); + $this->Logger()->AddSecret($oAccount->ProxyAuthPassword()); $oAccount->SetParentEmail($aAccountHash[6]); $oResult = $oAccount; } - else - { - $oDomain = $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($aAccountHash[1]), true); - if ($bThrowExceptionOnFalse) - { - if (!($oDomain instanceof \RainLoop\Domain)) - { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::DomainNotAllowed); - } - else if (!$oDomain->ValidateWhiteList($aAccountHash[1], $aAccountHash[2])) - { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountNotAllowed); - } - } - } } else if ($bThrowExceptionOnFalse) { @@ -881,7 +1176,7 @@ class Actions } } - if ($bThrowExceptionOnFalse && !($oResult instanceof \RainLoop\Account)) + if ($bThrowExceptionOnFalse && !($oResult instanceof \RainLoop\Model\Account)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } @@ -890,7 +1185,7 @@ class Actions } /** - * @return \RainLoop\Account|bool + * @return \RainLoop\Model\Account|bool */ public function GetAccountFromSignMeToken() { @@ -899,10 +1194,30 @@ class Actions $sSignMeToken = \RainLoop\Utils::GetCookie(\RainLoop\Actions::AUTH_SIGN_ME_TOKEN_KEY, ''); if (!empty($sSignMeToken)) { - $oAccount = $this->GetAccountFromCustomToken($this->StorageProvider()->Get(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::SignMeUserToken($sSignMeToken) - ), false, false); + $aTokenData = \RainLoop\Utils::DecodeKeyValues($sSignMeToken); + if (\is_array($aTokenData) && !empty($aTokenData['e']) && !empty($aTokenData['t'])) + { + $sTokenSettings = $this->StorageProvider()->Get($aTokenData['e'], + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'sign_me' + ); + + if (!empty($sTokenSettings)) + { + $aSignMeData = \RainLoop\Utils::DecodeKeyValues($sTokenSettings); + if (\is_array($aSignMeData) && + !empty($aSignMeData['AuthToken']) && + !empty($aSignMeData['SignMetToken']) && + $aSignMeData['SignMetToken'] === $aTokenData['t']) + { + $oAccount = $this->GetAccountFromCustomToken($aSignMeData['AuthToken'], false, false); + } + } + } + } + else + { + \RainLoop\Utils::ClearCookie(\RainLoop\Actions::AUTH_SIGN_ME_TOKEN_KEY); } return $oAccount; @@ -911,7 +1226,7 @@ class Actions /** * @param bool $bThrowExceptionOnFalse = true * - * @return \RainLoop\Account|bool + * @return \RainLoop\Model\Account|bool * @throws \RainLoop\Exceptions\ClientException */ private function getAccountFromToken($bThrowExceptionOnFalse = true) @@ -919,6 +1234,20 @@ class Actions return $this->GetAccountFromCustomToken($this->getAuthToken(), $bThrowExceptionOnFalse); } + /** + * @return bool + */ + private function PremType() + { + static $bResult = null; + if (null === $bResult) + { + $bResult = $this->licenseParser($this->licenseHelper(false, true)); + } + + return $bResult; + } + /** * @param bool $bAdmin * @param string $sAuthAccountHash = '' @@ -939,7 +1268,8 @@ class Actions 'Version' => APP_VERSION, 'Auth' => false, 'AccountHash' => '', - 'StaticPrefix' => 'rainloop/v/'.APP_VERSION.'/static/', + 'WebPath' => \RainLoop\Utils::WebPath(), + 'WebVersionPath' => \RainLoop\Utils::WebVersionPath(), 'AccountSignMe' => false, 'AuthAccountHash' => '', 'MailToEmail' => '', @@ -948,13 +1278,24 @@ class Actions 'DevPassword' => '', 'Title' => 'RainLoop Webmail', 'LoadingDescription' => 'RainLoop', - 'LoginLogo' => '', + 'LoadingDescriptionEsc' => 'RainLoop', 'LoginDescription' => '', + 'LoginPowered' => true, + 'LoginLogo' => '', + 'LoginBackground' => '', 'LoginCss' => '', + 'UserLogo' => '', + 'UserCss' => '', + 'WelcomePageUrl' => '', + 'WelcomePageDisplay' => 'none', + 'IncludeCss' => '', + 'IncludeBackground' => '', 'Token' => $oConfig->Get('security', 'csrf_protection', false) ? \RainLoop\Utils::GetCsrfToken() : '', 'InIframe' => (bool) $oConfig->Get('labs', 'in_iframe', false), 'AllowAdminPanel' => (bool) $oConfig->Get('security', 'allow_admin_panel', true), 'AllowHtmlEditorSourceButton' => (bool) $oConfig->Get('labs', 'allow_html_editor_source_button', false), + 'AllowHtmlEditorBitiButtons' => (bool) $oConfig->Get('labs', 'allow_html_editor_biti_buttons', false), + 'AllowCtrlEnterOnCompose' => (bool) $oConfig->Get('labs', 'allow_ctrl_enter_on_compose', false), 'UseRsaEncryption' => (bool) $oConfig->Get('security', 'use_rsa_encryption', false), 'RsaPublicKey' => '', 'HideDangerousActions' => $oConfig->Get('labs', 'hide_dangerous_actions', false), @@ -963,22 +1304,29 @@ class Actions 'LoginDefaultDomain' => $oConfig->Get('login', 'default_domain', ''), 'DetermineUserLanguage' => (bool) $oConfig->Get('login', 'determine_user_language', true), 'DetermineUserDomain' => (bool) $oConfig->Get('login', 'determine_user_domain', false), + 'ForgotPasswordLinkUrl' => \trim($oConfig->Get('login', 'forgot_password_link_url', '')), + 'RegistrationLinkUrl' => \trim($oConfig->Get('login', 'registration_link_url', '')), 'ContactsIsAllowed' => false, 'ChangePasswordIsAllowed' => false, + 'RequireTwoFactor' => false, 'JsHash' => \md5(\RainLoop\Utils::GetConnectionToken()), 'UseImapThread' => (bool) $oConfig->Get('labs', 'use_imap_thread', false), 'UseImapSubscribe' => (bool) $oConfig->Get('labs', 'use_imap_list_subscribe', true), 'AllowAppendMessage' => (bool) $oConfig->Get('labs', 'allow_message_append', false), + 'MaterialDesign' => (bool) $oConfig->Get('labs', 'use_material_design', true), + 'PremType' => $this->PremType(), + 'ZipSupported' => !!\class_exists('ZipArchive'), + 'Admin' => array(), 'Capa' => array(), 'Plugins' => array() ); - if ($aResult['UseRsaEncryption'] && + if ($aResult['UseRsaEncryption'] && \file_exists(APP_PRIVATE_DATA.'rsa/public') && \file_exists(APP_PRIVATE_DATA.'rsa/private')) { $aResult['RsaPublicKey'] = \file_get_contents(APP_PRIVATE_DATA.'rsa/public'); $aResult['RsaPublicKey'] = $aResult['RsaPublicKey'] ? $aResult['RsaPublicKey'] : ''; - + if (false === \strpos($aResult['RsaPublicKey'], 'PUBLIC KEY')) { $aResult['RsaPublicKey'] = ''; @@ -995,22 +1343,34 @@ class Actions $aResult['AuthAccountHash'] = $sAuthAccountHash; } - if ($this->GetCapa(true, \RainLoop\Enumerations\Capa::PREM)) - { - $aResult['Title'] = $oConfig->Get('webmail', 'title', ''); - $aResult['LoadingDescription'] = $oConfig->Get('webmail', 'loading_description', ''); + $aResult['Title'] = $oConfig->Get('webmail', 'title', ''); + $aResult['LoadingDescription'] = $oConfig->Get('webmail', 'loading_description', ''); + if ($this->PremType()) + { $aResult['LoginLogo'] = $oConfig->Get('branding', 'login_logo', ''); - $aResult['LoginDescription'] = $oConfig->Get('branding', 'login_desc', ''); + $aResult['LoginBackground'] = $oConfig->Get('branding', 'login_background', ''); $aResult['LoginCss'] = $oConfig->Get('branding', 'login_css', ''); + $aResult['LoginDescription'] = $oConfig->Get('branding', 'login_desc', ''); + $aResult['LoginPowered'] = !!$oConfig->Get('branding', 'login_powered', true); + $aResult['UserLogo'] = $oConfig->Get('branding', 'user_logo', ''); + $aResult['UserCss'] = $oConfig->Get('branding', 'user_css', ''); + $aResult['WelcomePageUrl'] = $oConfig->Get('branding', 'welcome_page_url', ''); + $aResult['WelcomePageDisplay'] = \strtolower($oConfig->Get('branding', 'welcome_page_display', 'none')); } + $aResult['LoadingDescriptionEsc'] = \htmlspecialchars($aResult['LoadingDescription'], ENT_QUOTES|ENT_IGNORE, 'UTF-8'); + $oSettings = null; + $oSettingsLocal = null; + if (!$bAdmin) { $oAccount = $this->getAccountFromToken(false); - if ($oAccount instanceof \RainLoop\Account) + if ($oAccount instanceof \RainLoop\Model\Account) { + $aResult['IncludeCss'] = $aResult['UserCss']; + $oAddressBookProvider = $this->AddressBookProvider($oAccount); $aResult['Auth'] = true; @@ -1058,26 +1418,67 @@ class Actions } $oSettings = $this->SettingsProvider()->Load($oAccount); + $oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount); + + if (!$oAccount->IsAdditionalAccount() && !empty($aResult['WelcomePageUrl']) && + ('once' === $aResult['WelcomePageDisplay'] || 'always' === $aResult['WelcomePageDisplay'])) + { + if ('once' === $aResult['WelcomePageDisplay']) + { + if ($aResult['WelcomePageUrl'] === $oSettings->GetConf('LastWelcomePage', '')) + { + $aResult['WelcomePageUrl'] = ''; + $aResult['WelcomePageDisplay'] = ''; + } + } + } + else + { + $aResult['WelcomePageUrl'] = ''; + $aResult['WelcomePageDisplay'] = ''; + } } else { $oAccount = null; + $aResult['IncludeBackground'] = $aResult['LoginBackground']; + $aResult['IncludeCss'] = $aResult['LoginCss']; + $aResult['DevEmail'] = $oConfig->Get('labs', 'dev_email', ''); $aResult['DevPassword'] = $oConfig->Get('labs', 'dev_password', ''); + + $aResult['WelcomePageUrl'] = ''; + $aResult['WelcomePageDisplay'] = ''; } $aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false); + $aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true); + $aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true); + $aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true); + $aResult['GoogleClientID'] = \trim($oConfig->Get('social', 'google_client_id', '')); $aResult['GoogleApiKey'] = \trim($oConfig->Get('social', 'google_api_key', '')); - if ($aResult['AllowGoogleSocial'] && ( - '' === \trim($oConfig->Get('social', 'google_client_id', '')) || '' === \trim($oConfig->Get('social', 'google_client_secret', '')))) + + if (!$aResult['AllowGoogleSocial'] || ($aResult['AllowGoogleSocial'] && ( + '' === \trim($oConfig->Get('social', 'google_client_id', '')) || '' === \trim($oConfig->Get('social', 'google_client_secret', ''))))) { - $aResult['AllowGoogleSocial'] = false; + $aResult['AllowGoogleSocialAuth'] = false; + $aResult['AllowGoogleSocialDrive'] = false; $aResult['GoogleClientID'] = ''; $aResult['GoogleApiKey'] = ''; } + if (!$aResult['AllowGoogleSocial']) + { + $aResult['AllowGoogleSocialPreview'] = false; + } + + if ($aResult['AllowGoogleSocial'] && !$aResult['AllowGoogleSocialAuth'] && !$aResult['AllowGoogleSocialDrive'] && !$aResult['AllowGoogleSocialPreview']) + { + $aResult['AllowGoogleSocial'] = false; + } + $aResult['AllowFacebookSocial'] = (bool) $oConfig->Get('social', 'fb_enable', false); if ($aResult['AllowFacebookSocial'] && ( '' === \trim($oConfig->Get('social', 'fb_app_id', '')) || '' === \trim($oConfig->Get('social', 'fb_app_secret', '')))) @@ -1094,6 +1495,7 @@ class Actions $aResult['AllowDropboxSocial'] = (bool) $oConfig->Get('social', 'dropbox_enable', false); $aResult['DropboxApiKey'] = \trim($oConfig->Get('social', 'dropbox_api_key', '')); + if (!$aResult['AllowDropboxSocial']) { $aResult['DropboxApiKey'] = ''; @@ -1104,6 +1506,20 @@ class Actions } $aResult['Capa'] = $this->Capa(false, $oAccount); + + if ($aResult['Auth'] && !$aResult['RequireTwoFactor']) + { + if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount) && + $this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR_FORCE, $oAccount) && + $this->TwoFactorAuthProvider()->IsActive()) + { + $aData = $this->getTwoFactorInfo($oAccount, true); + + $aResult['RequireTwoFactor'] = !$aData || + !isset($aData['User'], $aData['IsSet'], $aData['Enable']) || + !($aData['IsSet'] && $aData['Enable']); + } + } } else { @@ -1115,6 +1531,9 @@ class Actions $aResult['UseTokenProtection'] = (bool) $oConfig->Get('security', 'csrf_protection', true); $aResult['EnabledPlugins'] = (bool) $oConfig->Get('plugins', 'enable', false); + $aResult['VerifySslCertificate'] = !!$oConfig->Get('ssl', 'verify_certificate', false); + $aResult['AllowSelfSigned'] = !!$oConfig->Get('ssl', 'allow_self_signed', true); + $aDrivers = \class_exists('PDO') ? \PDO::getAvailableDrivers() : array(); $aResult['MySqlIsSupported'] = \is_array($aDrivers) ? \in_array('mysql', $aDrivers) : false; $aResult['SQLiteIsSupported'] = \is_array($aDrivers) ? \in_array('sqlite', $aDrivers) : false; @@ -1130,6 +1549,10 @@ class Actions $aResult['ContactsPdoPassword'] = APP_DUMMY; $aResult['AllowGoogleSocial'] = (bool) $oConfig->Get('social', 'google_enable', false); + $aResult['AllowGoogleSocialAuth'] = (bool) $oConfig->Get('social', 'google_enable_auth', true); + $aResult['AllowGoogleSocialDrive'] = (bool) $oConfig->Get('social', 'google_enable_drive', true); + $aResult['AllowGoogleSocialPreview'] = (bool) $oConfig->Get('social', 'google_enable_preview', true); + $aResult['GoogleClientID'] = (string) $oConfig->Get('social', 'google_client_id', ''); $aResult['GoogleClientSecret'] = (string) $oConfig->Get('social', 'google_client_secret', ''); $aResult['GoogleApiKey'] = (string) $oConfig->Get('social', 'google_api_key', ''); @@ -1142,11 +1565,11 @@ class Actions $aResult['TwitterConsumerKey'] = (string) $oConfig->Get('social', 'twitter_consumer_key', ''); $aResult['TwitterConsumerSecret'] = (string) $oConfig->Get('social', 'twitter_consumer_secret', ''); - $aResult['AllowDropboxSocial'] = (bool) $oConfig->Get('social', 'dropbox_enable', false); $aResult['DropboxApiKey'] = (string) $oConfig->Get('social', 'dropbox_api_key', ''); $aResult['SubscriptionEnabled'] = \MailSo\Base\Utils::ValidateDomain($aResult['AdminDomain']); +// || \MailSo\Base\Utils::ValidateIP($aResult['AdminDomain']); $aResult['WeakPassword'] = $oConfig->ValidatePassword('12345'); $aResult['CoreAccess'] = $this->rainLoopCoreAccess(); @@ -1171,10 +1594,12 @@ class Actions $aResult['ProjectHash'] = \md5($aResult['AccountHash'].APP_VERSION.$this->Plugins()->Hash()); $sLanguage = $oConfig->Get('webmail', 'language', 'en'); + $sLanguageAdmin = $oConfig->Get('webmail', 'language_admin', 'en'); $sTheme = $oConfig->Get('webmail', 'theme', 'Default'); $aResult['Themes'] = $this->GetThemes(); - $aResult['Languages'] = $this->GetLanguages(); + $aResult['Languages'] = $this->GetLanguages(false); + $aResult['LanguagesAdmin'] = $this->GetLanguages(true); $aResult['AllowLanguagesOnSettings'] = (bool) $oConfig->Get('webmail', 'allow_languages_on_settings', true); $aResult['AllowLanguagesOnLogin'] = (bool) $oConfig->Get('login', 'allow_languages_on_login', true); $aResult['AttachmentLimit'] = ((int) $oConfig->Get('webmail', 'attachment_size_limit', 10)) * 1024 * 1024; @@ -1182,115 +1607,118 @@ class Actions $aResult['UseLocalProxyForExternalImages'] = (bool) $oConfig->Get('labs', 'use_local_proxy_for_external_images', false); // user - $aResult['EditorDefaultType'] = (string) $oConfig->Get('webmail', 'editor_default_type', ''); - $aResult['ShowImages'] = (bool) $oConfig->Get('webmail', 'show_images', false); - $aResult['ContactsAutosave'] = true; + $aResult['ShowImages'] = (bool) $oConfig->Get('defaults', 'show_images', false); $aResult['MPP'] = (int) $oConfig->Get('webmail', 'messages_per_page', 25); + $aResult['SoundNotification'] = false; $aResult['DesktopNotifications'] = false; - $aResult['UseThreads'] = false; - $aResult['ReplySameFolder'] = false; - $aResult['Layout'] = \RainLoop\Enumerations\Layout::SIDE_PREVIEW; - $aResult['UseCheckboxesInList'] = true; - $aResult['DefaultIdentityID'] = ''; - $aResult['DisplayName'] = ''; - $aResult['ReplyTo'] = ''; - $aResult['Signature'] = ''; - $aResult['SignatureToAll'] = false; + $aResult['Layout'] = (int) $oConfig->Get('defaults', 'view_layout', \RainLoop\Enumerations\Layout::SIDE_PREVIEW); + $aResult['EditorDefaultType'] = (string) $oConfig->Get('defaults', 'view_editor_type', ''); + $aResult['UseCheckboxesInList'] = (bool) $oConfig->Get('defaults', 'view_use_checkboxes', true); + $aResult['AutoLogout'] = (int) $oConfig->Get('defaults', 'autologout', 30); + $aResult['UseThreads'] = (bool) $oConfig->Get('defaults', 'mail_use_threads', false); + $aResult['ReplySameFolder'] = (bool) $oConfig->Get('defaults', 'mail_reply_same_folder', false); + $aResult['ContactsAutosave'] = (bool) $oConfig->Get('defaults', 'contacts_autosave', true); $aResult['EnableTwoFactor'] = false; $aResult['ParentEmail'] = ''; - $aResult['InterfaceAnimation'] = \RainLoop\Enumerations\InterfaceAnimation::NORMAL; + $aResult['InterfaceAnimation'] = true; + $aResult['UserBackgroundName'] = ''; + $aResult['UserBackgroundHash'] = ''; - if (!$bAdmin && $oSettings instanceof \RainLoop\Settings) + if (!$bAdmin && $oSettings instanceof \RainLoop\Settings && + $oSettingsLocal instanceof \RainLoop\Settings) { if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) { $sLanguage = $oSettings->GetConf('Language', $sLanguage); } - if ($oConfig->Get('webmail', 'allow_themes', true)) + if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::THEMES, $oAccount)) { - $sTheme = $oSettings->GetConf('Theme', $sTheme); + $sTheme = $oSettingsLocal->GetConf('Theme', $sTheme); } - $aResult['SentFolder'] = $oSettings->GetConf('SentFolder', ''); - $aResult['DraftFolder'] = $oSettings->GetConf('DraftFolder', ''); - $aResult['SpamFolder'] = $oSettings->GetConf('SpamFolder', ''); - $aResult['TrashFolder'] = $oSettings->GetConf('TrashFolder', ''); - $aResult['ArchiveFolder'] = $oSettings->GetConf('ArchiveFolder', ''); - $aResult['NullFolder'] = $oSettings->GetConf('NullFolder', ''); + $aResult['SentFolder'] = $oSettingsLocal->GetConf('SentFolder', ''); + $aResult['DraftFolder'] = $oSettingsLocal->GetConf('DraftFolder', ''); + $aResult['SpamFolder'] = $oSettingsLocal->GetConf('SpamFolder', ''); + $aResult['TrashFolder'] = $oSettingsLocal->GetConf('TrashFolder', ''); + $aResult['ArchiveFolder'] = $oSettingsLocal->GetConf('ArchiveFolder', ''); + $aResult['NullFolder'] = $oSettingsLocal->GetConf('NullFolder', ''); + $aResult['EditorDefaultType'] = $oSettings->GetConf('EditorDefaultType', $aResult['EditorDefaultType']); $aResult['ShowImages'] = (bool) $oSettings->GetConf('ShowImages', $aResult['ShowImages']); $aResult['ContactsAutosave'] = (bool) $oSettings->GetConf('ContactsAutosave', $aResult['ContactsAutosave']); $aResult['MPP'] = (int) $oSettings->GetConf('MPP', $aResult['MPP']); + $aResult['SoundNotification'] = (bool) $oSettings->GetConf('SoundNotification', $aResult['SoundNotification']); $aResult['DesktopNotifications'] = (bool) $oSettings->GetConf('DesktopNotifications', $aResult['DesktopNotifications']); - $aResult['UseThreads'] = (bool) $oSettings->GetConf('UseThreads', $aResult['UseThreads']); - $aResult['ReplySameFolder'] = (bool) $oSettings->GetConf('ReplySameFolder', $aResult['ReplySameFolder']); - $aResult['Layout'] = (int) $oSettings->GetConf('Layout', $aResult['Layout']); $aResult['UseCheckboxesInList'] = (bool) $oSettings->GetConf('UseCheckboxesInList', $aResult['UseCheckboxesInList']); - $aResult['InterfaceAnimation'] = (string) $oSettings->GetConf('InterfaceAnimation', $aResult['InterfaceAnimation']); + $aResult['AutoLogout'] = (int) $oSettings->GetConf('AutoLogout', $aResult['AutoLogout']); + $aResult['Layout'] = (int) $oSettings->GetConf('Layout', $aResult['Layout']); + + $aResult['UseThreads'] = (bool) $oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']); + $aResult['ReplySameFolder'] = (bool) $oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']); + + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::AUTOLOGOUT, $oAccount)) + { + $aResult['AutoLogout'] = 0; + } + + if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::USER_BACKGROUND, $oAccount)) + { + $aResult['UserBackgroundName'] = (string) $oSettings->GetConf('UserBackgroundName', $aResult['UserBackgroundName']); + $aResult['UserBackgroundHash'] = (string) $oSettings->GetConf('UserBackgroundHash', $aResult['UserBackgroundHash']); +// if (!empty($aResult['UserBackgroundName']) && !empty($aResult['UserBackgroundHash'])) +// { +// $aResult['IncludeBackground'] = './?/Raw/&q[]=/{{USER}}/UserBackground/&q[]=/'. +// $aResult['UserBackgroundHash'].'/'; +// } + } - $aResult['DefaultIdentityID'] = $oSettings->GetConf('DefaultIdentityID', $oAccount ? $oAccount->Email() : $aResult['DefaultIdentityID']); - $aResult['DisplayName'] = $oSettings->GetConf('DisplayName', $aResult['DisplayName']); - $aResult['ReplyTo'] = $oSettings->GetConf('ReplyTo', $aResult['ReplyTo']); - $aResult['Signature'] = $oSettings->GetConf('Signature', $aResult['Signature']); - $aResult['SignatureToAll'] = !!$oSettings->GetConf('SignatureToAll', $aResult['SignatureToAll']); $aResult['EnableTwoFactor'] = !!$oSettings->GetConf('EnableTwoFactor', $aResult['EnableTwoFactor']); $aResult['ParentEmail'] = $oAccount->ParentEmail(); } - $aResult['InterfaceAnimation'] = \RainLoop\Enumerations\InterfaceAnimation::NONE === $aResult['InterfaceAnimation'] - ? $aResult['InterfaceAnimation'] : \RainLoop\Enumerations\InterfaceAnimation::NORMAL; - - if (0 < \strlen($aResult['ParentEmail'])) - { - $aResult['AllowGoogleSocial'] = false; - $aResult['AllowFacebookSocial'] = false; - $aResult['AllowTwitterSocial'] = false; - } - $sStaticCache = \md5(APP_VERSION.$this->Plugins()->Hash()); $sTheme = $this->ValidateTheme($sTheme); - $sNewThemeLink = './?/Css/0/'.($bAdmin ? 'Admin' : 'User').'/-/'.($bAdmin ? 'Default' : $sTheme).'/-/'.$sStaticCache.'/'; + $sNewThemeLink = './?/Css/0/'.($bAdmin ? 'Admin' : 'User').'/-/'.$sTheme.'/-/'.$sStaticCache.'/Hash/-/'; - $bUserLanguage = false; - if (!$bAdmin && !$aResult['Auth'] && !empty($_COOKIE['rllang']) && - $oConfig->Get('webmail', 'allow_languages_on_login', true)) + if (!$aResult['Auth']) { - $sLanguage = $_COOKIE['rllang']; - } - else if (!$bAdmin && !$aResult['Auth']) - { - $sUserLanguage = ''; - if (!$bAdmin && !$aResult['Auth'] && - $oConfig->Get('login', 'determine_user_language', true)) + if (!$bAdmin) { - $sUserLanguage = $this->detectUserLanguage(); - } - - $sLanguage = $this->ValidateLanguage($sLanguage); - if (0 < \strlen($sUserLanguage) && $sLanguage !== $sUserLanguage) - { - $sLanguage = $sUserLanguage; - $bUserLanguage = true; + if ($oConfig->Get('login', 'allow_languages_on_login', true) && + $oConfig->Get('login', 'determine_user_language', true)) + { + $sLanguage = $this->ValidateLanguage( + $this->detectUserLanguage(), $sLanguage, false); + } } } $sPluginsLink = ''; - if (0 < $this->Plugins()->Count()) + if (0 < $this->Plugins()->Count() && $this->Plugins()->HaveJs($bAdmin)) { $sPluginsLink = './?/Plugins/0/'.($bAdmin ? 'Admin' : 'User').'/'.$sStaticCache.'/'; } $aResult['Theme'] = $sTheme; $aResult['NewThemeLink'] = $sNewThemeLink; - $aResult['Language'] = $this->ValidateLanguage($sLanguage); - $aResult['UserLanguage'] = $bUserLanguage; - $aResult['LangLink'] = './?/Lang/0/'.($bAdmin ? 'en' : $aResult['Language']).'/'.$sStaticCache.'/'; + + $aResult['Language'] = $this->ValidateLanguage($sLanguage, '', false); + $aResult['LanguageAdmin'] = $this->ValidateLanguage($sLanguageAdmin, '', true); + + $aResult['UserLanguageRaw'] = $this->detectUserLanguage(); + $aResult['UserLanguage'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], $aResult['Language'], false); + $aResult['UserLanguageAdmin'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], $aResult['LanguageAdmin'], true); + + $aResult['LangLink'] = './?/Lang/0/'.($bAdmin ? 'Admin' : 'App').'/'. + ($bAdmin ? $aResult['LanguageAdmin'] : $aResult['Language']).'/'.$sStaticCache.'/'; + $aResult['TemplatesLink'] = './?/Templates/0/'.($bAdmin ? 'Admin' : 'App').'/'.$sStaticCache.'/'; $aResult['PluginsLink'] = $sPluginsLink; - $aResult['EditorDefaultType'] = 'Html' === $aResult['EditorDefaultType'] ? 'Html' : 'Plain'; + $aResult['EditorDefaultType'] = \in_array($aResult['EditorDefaultType'], array('Plain', 'Html', 'HtmlForced', 'PlainForced')) ? + $aResult['EditorDefaultType'] : 'Plain'; // IDN $aResult['Email'] = \MailSo\Base\Utils::IdnToUtf8($aResult['Email']); @@ -1303,29 +1731,28 @@ class Actions return $aResult; } - private function detectUserLanguage() + /** + * @return string + */ + private function getUserLanguageFromHeader() { $sLang = ''; $sAcceptLang = $this->Http()->GetServer('HTTP_ACCEPT_LANGUAGE', 'en'); if (false !== \strpos($sAcceptLang, ',')) { $aParts = \explode(',', $sAcceptLang, 2); - $sLang = !empty($aParts[0]) ? \strtolower($aParts[0]) : ''; + $sLang = !empty($aParts[0]) ? \trim(\strtolower($aParts[0])) : ''; } - if (!empty($sLang)) - { - $sLang = \preg_replace('/[^a-zA-Z0-9]+/', '-', $sLang); - if ($sLang !== $this->ValidateLanguage($sLang)) - { - if (2 < strlen($sLang)) - { - $sLang = \substr($sLang, 0, 2); - } - } - } + return $sLang; + } - return $this->ValidateLanguage($sLang); + /** + * @return string + */ + private function detectUserLanguage() + { + return \preg_replace('/[^a-zA-Z0-9]+/', '-', $this->getUserLanguageFromHeader()); } private function loginErrorDelay() @@ -1337,11 +1764,11 @@ class Actions } } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount */ - public function AuthProcess($oAccount) + public function AuthToken($oAccount) { - if ($oAccount instanceof \RainLoop\Account) + if ($oAccount instanceof \RainLoop\Model\Account) { $this->SetAuthToken($oAccount); @@ -1355,19 +1782,16 @@ class Actions } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount + * @param bool $bAuthLog = false * * @throws \RainLoop\Exceptions\ClientException */ - public function CheckMailConnection($oAccount) + public function CheckMailConnection($oAccount, $bAuthLog = false) { try { - $this->MailClient() - ->Connect($oAccount->Domain()->IncHost(\MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email())), - $oAccount->Domain()->IncPort(), $oAccount->Domain()->IncSecure()) - ->Login($oAccount->IncLogin(), $oAccount->Password()) - ; + $oAccount->IncConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config()); } catch (\RainLoop\Exceptions\ClientException $oException) { @@ -1377,6 +1801,28 @@ class Actions { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ConnectionError, $oException); } + catch (\MailSo\Imap\Exceptions\LoginBadCredentialsException $oException) + { + if ($bAuthLog) + { + $sLine = $this->Config()->Get('logs', 'auth_logging_format', ''); + if (!empty($sLine)) + { + $this->LoggerAuth()->Write($this->compileLogParams($sLine, $oAccount), + \MailSo\Log\Enumerations\Type::WARNING, 'IMAP'); + } + } + + if ($this->Config()->Get('labs', 'imap_show_login_alert', true)) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError, + $oException, $oException->getAlertFromStatus()); + } + else + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError, $oException); + } + } catch (\Exception $oException) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError, $oException); @@ -1390,7 +1836,7 @@ class Actions * @param string $sAdditionalCode = '' * @param string $bAdditionalCodeSignMe = false * - * @return \RainLoop\Account + * @return \RainLoop\Model\Account * @throws \RainLoop\Exceptions\ClientException */ public function LoginProcess(&$sEmail, &$sPassword, $sSignMeToken = '', @@ -1398,13 +1844,14 @@ class Actions { $this->Plugins()->RunHook('filter.login-credentials.step-1', array(&$sEmail, &$sPassword)); - $sEmail = \MailSo\Base\Utils::StrToLowerIfAscii($sEmail); + $sEmail = \MailSo\Base\Utils::StrToLowerIfAscii( + \MailSo\Base\Utils::Trim($sEmail)); if (false === \strpos($sEmail, '@')) { $this->Logger()->Write('The email address "'.$sEmail.'" is not complete', \MailSo\Log\Enumerations\Type::INFO, 'LOGIN'); - if (false === \strpos($sEmail, '@') && 0 < \strlen(\trim($this->Config()->Get('login', 'determine_user_domain', false)))) + if (false === \strpos($sEmail, '@') && !!$this->Config()->Get('login', 'determine_user_domain', false)) { $sUserHost = \trim($this->Http()->GetHost(false, true, true)); $this->Logger()->Write('Determined user domain: '.$sUserHost, \MailSo\Log\Enumerations\Type::INFO, 'LOGIN'); @@ -1420,7 +1867,7 @@ class Actions $sLine = \trim(\implode('.', $aDomainParts), '. '); $oDomain = $oDomainProvider->Load($sLine); - if ($oDomain && $oDomain instanceof \RainLoop\Domain) + if ($oDomain && $oDomain instanceof \RainLoop\Model\Domain) { $bAdded = true; $this->Logger()->Write('Check "'.$sLine.'": OK ('.$sEmail.' > '.$sEmail.'@'.$sLine.')', @@ -1458,6 +1905,8 @@ class Actions if (false === \strpos($sEmail, '@') || 0 === \strlen($sPassword)) { + $this->loginErrorDelay(); + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument); } @@ -1468,30 +1917,35 @@ class Actions $this->Logger()->AddSecret($sPassword); - $oAccount = $this->LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken); - if (!($oAccount instanceof \RainLoop\Account)) - { - $this->loginErrorDelay(); + $this->Plugins()->RunHook('event.login-pre-login-provide', array()); - $oDomain = $this->DomainProvider()->Load(\MailSo\Base\Utils::GetDomainFromEmail($sEmail), true); - if (!($oDomain instanceof \RainLoop\Domain)) + try + { + $oAccount = $this->LoginProvide($sEmail, $sLogin, $sPassword, $sSignMeToken, true); + + if (!($oAccount instanceof \RainLoop\Model\Account)) { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::DomainNotAllowed); + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } - else if (!$oDomain->ValidateWhiteList($sEmail, $sLogin)) - { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountNotAllowed); - } - else + + $this->Plugins()->RunHook('event.login-post-login-provide', array(&$oAccount)); + + if (!($oAccount instanceof \RainLoop\Model\Account)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } } + catch (\Exception $oException) + { + $this->loginErrorDelay(); + + throw $oException; + } // Two factor auth - if ($oAccount && $this->TwoFactorAuthProvider()->IsActive()) + if ($this->TwoFactorAuthProvider()->IsActive()) { - $aData = $this->getTwoFactorInfo($oAccount->ParentEmailHelper()); + $aData = $this->getTwoFactorInfo($oAccount); if ($aData && isset($aData['IsSet'], $aData['Enable']) && !empty($aData['Secret']) && $aData['IsSet'] && $aData['Enable']) { $sSecretHash = \md5(APP_SALT.$aData['Secret'].\RainLoop\Utils::Fingerprint()); @@ -1503,6 +1957,7 @@ class Actions if (empty($sAdditionalCode)) { $this->Logger()->Write('TFA: Required Code for '.$oAccount->ParentEmailHelper().' account.'); + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountTwoFactorAuthRequired); } else @@ -1530,6 +1985,7 @@ class Actions if (!$bGood && !$this->TwoFactorAuthProvider()->VerifyCode($aData['Secret'], $sAdditionalCode)) { $this->loginErrorDelay(); + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountTwoFactorAuthError); } } @@ -1539,11 +1995,12 @@ class Actions try { - $this->CheckMailConnection($oAccount); + $this->CheckMailConnection($oAccount, true); } catch (\Exception $oException) { $this->loginErrorDelay(); + throw $oException; } @@ -1563,7 +2020,7 @@ class Actions $oLogger = $this->Logger(); $oLogger->Write('Trying to decode encrypted data', \MailSo\Log\Enumerations\Type::INFO, 'RSA'); - $sPrivateKey = file_exists(APP_PRIVATE_DATA.'rsa/private') ? + $sPrivateKey = file_exists(APP_PRIVATE_DATA.'rsa/private') ? \file_get_contents(APP_PRIVATE_DATA.'rsa/private') : ''; if (!empty($sPrivateKey)) @@ -1606,6 +2063,16 @@ class Actions return $sEncryptedData; } + /** + * @param string $sEmail + * + * @return string + */ + private function generateSignMeToken($sEmail) + { + return \MailSo\Base\Utils::Md5Rand(APP_SALT.$sEmail); + } + /** * @return array * @@ -1613,7 +2080,7 @@ class Actions */ public function DoLogin() { - $sEmail = \trim($this->GetActionParam('Email', '')); + $sEmail = \MailSo\Base\Utils::Trim($this->GetActionParam('Email', '')); $sPassword = $this->GetActionParam('Password', ''); $sLanguage = $this->GetActionParam('Language', ''); $bSignMe = '1' === (string) $this->GetActionParam('SignMe', '0'); @@ -1624,11 +2091,33 @@ class Actions $oAccount = null; $sPassword = $this->clientRsaDecryptHelper($sPassword); + $this->Logger()->AddSecret($sPassword); + + if (0 < \strlen($sEmail) && 0 < \strlen($sPassword) && + $this->Config()->Get('security', 'allow_universal_login', true) && + $this->Config()->Get('security', 'allow_admin_panel', true) && + $sEmail === $this->Config()->Get('security', 'admin_login', '') + ) + { + if ($this->Config()->ValidatePassword($sPassword)) + { + $this->setAdminAuthToken($this->getAdminToken()); + + return $this->DefaultResponse(__FUNCTION__, true, array( + 'Admin' => true + )); + } + else + { + $this->loginErrorDelay(); + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); + } + } try { $oAccount = $this->LoginProcess($sEmail, $sPassword, - $bSignMe ? \md5(\microtime(true).APP_SALT.\rand(10000, 99999).$sEmail) : '', + $bSignMe ? $this->generateSignMeToken($sEmail) : '', $sAdditionalCode, $bAdditionalCodeSignMe); } catch (\RainLoop\Exceptions\ClientException $oException) @@ -1646,15 +2135,21 @@ class Actions } } - $this->AuthProcess($oAccount); + $this->AuthToken($oAccount); if ($oAccount && 0 < \strlen($sLanguage)) { $oSettings = $this->SettingsProvider()->Load($oAccount); if ($oSettings) { - $oSettings->SetConf('Language', $this->ValidateLanguage($sLanguage)); - $this->SettingsProvider()->Save($oAccount, $oSettings); + $sLanguage = $this->ValidateLanguage($sLanguage); + $sCurrentLanguage = $oSettings->GetConf('Language', ''); + + if ($sCurrentLanguage !== $sLanguage) + { + $oSettings->SetConf('Language', $sLanguage); + $this->SettingsProvider()->Save($oAccount, $oSettings); + } } } @@ -1662,28 +2157,48 @@ class Actions } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return array */ public function GetAccounts($oAccount) { - $sParentEmail = $oAccount->ParentEmailHelper(); - - if ($this->Config()->Get('webmail', 'allow_additional_accounts', true)) + if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) { - $sAccounts = $this->StorageProvider()->Get(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::WebmailAccounts($sParentEmail), - null + $sAccounts = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'accounts' ); - $aAccounts = $sAccounts ? @\unserialize($sAccounts) : array(); + $aAccounts = array(); + if ('' !== $sAccounts && '{' === \substr($sAccounts, 0, 1)) + { + $aAccounts = @\json_decode($sAccounts, true); + } + if (\is_array($aAccounts) && 0 < \count($aAccounts)) { if (1 === \count($aAccounts)) { $this->SetAccounts($oAccount, array()); + + } + else if (1 < \count($aAccounts)) + { + $sOrder = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'accounts_identities_order' + ); + + $aOrder = empty($sOrder) ? array() : @\json_decode($sOrder, true); + if (isset($aOrder['Accounts']) && \is_array($aOrder['Accounts']) && + 1 < \count($aOrder['Accounts'])) + { + $aAccounts = \array_merge(\array_flip($aOrder['Accounts']), $aAccounts); + $aAccounts = \array_filter($aAccounts, function ($sHash) { + return 5 < \strlen($sHash); + }); + } } return $aAccounts; @@ -1691,7 +2206,7 @@ class Actions } $aAccounts = array(); - if ($sParentEmail === $oAccount->Email()) + if (!$oAccount->IsAdditionalAccount()) { $aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken(); } @@ -1700,7 +2215,86 @@ class Actions } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount + * + * @return array + */ + public function GetTemplates($oAccount) + { + $aTemplates = array(); + if ($oAccount) + { + $aData = array(); + + $sData = $this->StorageProvider(true)->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'templates' + ); + + if ('' !== $sData && '[' === \substr($sData, 0, 1)) + { + $aData = @\json_decode($sData, true); + } + + if (\is_array($aData) && 0 < \count($aData)) + { + foreach ($aData as $aItem) + { + $oItem = \RainLoop\Model\Template::NewInstance(); + $oItem->FromJSON($aItem); + + if ($oItem && $oItem->Validate()) + { + \array_push($aTemplates, $oItem); + } + } + } + + if (1 < \count($aTemplates)) + { + $sOrder = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'templates_order' + ); + + $aOrder = empty($sOrder) ? array() : @\json_decode($sOrder, true); + if (\is_array($aOrder) && 1 < \count($aOrder)) + { + \usort($aTemplates, function ($a, $b) use ($aOrder) { + return \array_search($a->Id(), $aOrder) < \array_search($b->Id(), $aOrder) ? -1 : 1; + }); + } + } + } + + return $aTemplates; + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param string $sID + * + * @return \RainLoop\Model\Identity + */ + public function GetTemplateByID($oAccount, $sID) + { + $aTemplates = $this->GetTemplates($oAccount); + if (\is_array($aTemplates)) + { + foreach ($aTemplates as $oIdentity) + { + if ($oIdentity && $sID === $oIdentity->Id()) + { + return $oIdentity; + } + } + } + + return isset($aTemplates[0]) ? $aTemplates[0] : null; + } + + /** + * @param \RainLoop\Model\Account $oAccount * * @return array */ @@ -1710,81 +2304,36 @@ class Actions if ($oAccount) { $aSubIdentities = array(); - $oSettings = $this->SettingsProvider()->Load($oAccount); - if ($oSettings) + + $sData = $this->StorageProvider(true)->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'identities' + ); + + if ('' !== $sData && '[' === \substr($sData, 0, 1)) { - $sData = $oSettings->GetConf('Identities', ''); - if ('' !== $sData && '[' === \substr($sData, 0, 1)) - { - $aSubIdentities = @\json_decode($sData, true); - $aSubIdentities = \is_array($aSubIdentities) ? $aSubIdentities : array(); - } + $aSubIdentities = @\json_decode($sData, true); } - if (0 < \count($aSubIdentities)) + $bHasAccountIdentity = false; + + if (\is_array($aSubIdentities) && 0 < \count($aSubIdentities)) { foreach ($aSubIdentities as $aItem) { - if (isset($aItem['Id'], $aItem['Email'], $aItem['Name'], $aItem['ReplyTo'], $aItem['Bcc']) && - $aItem['Id'] !== $oAccount->Email()) + $oItem = \RainLoop\Model\Identity::NewInstance(); + $oItem->FromJSON($aItem); + + if ($oItem && $oItem->Validate()) { - $oItem = \RainLoop\Identity::NewInstance($aItem['Id'], $aItem['Email'], - $aItem['Name'], $aItem['ReplyTo'], $aItem['Bcc']); - - $aIdentities[] = $oItem; - } - } - } - } - - return $aIdentities; - } - - /** - * @param \RainLoop\Account $oAccount - * - * @return array - */ - public function GetIdentitiesNew($oAccount) - { - $aIdentities = array(); - if ($oAccount) - { - $oAccountIdentity = \RainLoop\Identity::NewInstance('', $oAccount->Email()); - - $aSubIdentities = array(); - $oSettings = $this->SettingsProvider()->Load($oAccount); - if ($oSettings) - { - $sData = $oSettings->GetConf('Identities', ''); - if ('' !== $sData && '[' === \substr($sData, 0, 1)) - { - $aSubIdentities = @\json_decode($sData, true); - $aSubIdentities = \is_array($aSubIdentities) ? $aSubIdentities : array(); - } - } - - if (0 < \count($aSubIdentities)) - { - foreach ($aSubIdentities as $aItem) - { - if (isset($aItem['Id'], $aItem['Email'])) - { - if (0 < \strlen($aItem['Id'])) + if ('' === $oItem->Id()) { - $oItem = \RainLoop\Identity::NewInstance($aItem['Id'], $aItem['Email']); + $oItem->SetEmail($oAccount->Email()); + $bHasAccountIdentity = true; + + \array_unshift($aIdentities, $oItem); } else - { - $oItem = $oAccountIdentity; - } - - $oItem->SetName(isset($aItem['Name']) ? $aItem['Name'] : ''); - $oItem->SetReplyTo(isset($aItem['ReplyTo']) ? $aItem['ReplyTo'] : ''); - $oItem->SetBcc(isset($aItem['Bcc']) ? $aItem['Bcc'] : ''); - $oItem->SetSignature(isset($aItem['Signature']) ? $aItem['Signature'] : ''); - - if ('' !== $oItem->Id()) { \array_push($aIdentities, $oItem); } @@ -1792,14 +2341,69 @@ class Actions } } - \array_unshift($aIdentities, $oAccountIdentity); + if (!$bHasAccountIdentity) + { + \array_unshift($aIdentities, + \RainLoop\Model\Identity::NewInstanceFromAccount($oAccount)); + } + + if (1 < \count($aIdentities)) + { + $sOrder = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'accounts_identities_order' + ); + + $aOrder = empty($sOrder) ? array() : @\json_decode($sOrder, true); + if (isset($aOrder['Identities']) && \is_array($aOrder['Identities']) && + 1 < \count($aOrder['Identities'])) + { + $aList = $aOrder['Identities']; + \usort($aIdentities, function ($a, $b) use ($aList) { + return \array_search($a->Id(), $aList) < \array_search($b->Id(), $aList) ? -1 : 1; + }); + } + } } return $aIdentities; } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount + * @param string $sID + * @param bool $bFirstOnEmpty = false + * + * @return \RainLoop\Model\Identity + */ + public function GetIdentityByID($oAccount, $sID, $bFirstOnEmpty = false) + { + $aIdentities = $this->GetIdentities($oAccount); + if (\is_array($aIdentities)) + { + foreach ($aIdentities as $oIdentity) + { + if ($oIdentity && $sID === $oIdentity->Id()) + { + return $oIdentity; + } + } + } + + return $bFirstOnEmpty && \is_array($aIdentities) && isset($aIdentities[0]) ? $aIdentities[0] : null; + } + /** + * @param \RainLoop\Model\Account $oAccount + * + * @return \RainLoop\Model\Identity + */ + public function GetAccountIdentity($oAccount) + { + return $this->GetIdentityByID($oAccount, '', true); + } + + /** + * @param \RainLoop\Model\Account $oAccount * @param array $aAccounts = array() * * @return array @@ -1810,38 +2414,61 @@ class Actions if (!\is_array($aAccounts) || 0 >= \count($aAccounts) || (1 === \count($aAccounts) && !empty($aAccounts[$sParentEmail]))) { - $this->StorageProvider()->Clear(null, \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::WebmailAccounts($sParentEmail)); + $this->StorageProvider()->Clear($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'accounts' + ); } else { - $this->StorageProvider()->Put(null, \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::WebmailAccounts($sParentEmail), @\serialize($aAccounts)); + $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'accounts', + @\json_encode($aAccounts) + ); } } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param array $aIdentities = array() * * @return array */ public function SetIdentities($oAccount, $aIdentities = array()) { - $oSettings = $this->SettingsProvider()->Load($oAccount); - if ($oSettings) + $aResult = array(); + foreach ($aIdentities as $oItem) { - $aResult = array(); - foreach ($aIdentities as $oItem) - { - $aResult[] = $oItem->ToSimpleJSON(false); - } - - $oSettings->SetConf('Identities', @\json_encode($aResult)); - return $this->SettingsProvider()->Save($oAccount, $oSettings); + $aResult[] = $oItem->ToSimpleJSON(false); } - return false; + return $this->StorageProvider(true)->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'identities', + @\json_encode($aResult) + ); + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param array $aTemplates = array() + * + * @return array + */ + public function SetTemplates($oAccount, $aTemplates = array()) + { + $aResult = array(); + foreach ($aTemplates as $oItem) + { + $aResult[] = $oItem->ToSimpleJSON(false); + } + + return $this->StorageProvider(true)->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'templates', + @\json_encode($aResult) + ); } /** @@ -1849,46 +2476,110 @@ class Actions * * @throws \MailSo\Base\Exceptions\Exception */ - public function DoAccountAdd() + public function DoFilters() { - if (!$this->Config()->Get('webmail', 'allow_additional_accounts', true)) + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::FILTERS, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } + $aFakeFilters = null; + + $this->Plugins() + ->RunHook('filter.filters-fake', array($oAccount, &$aFakeFilters)) + ; + + if ($aFakeFilters) + { + return $this->DefaultResponse(__FUNCTION__, $aFakeFilters); + } + + return $this->DefaultResponse(__FUNCTION__, + $this->FiltersProvider()->Load($oAccount, $oAccount->DomainSieveAllowRaw())); + } + + /** + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoFiltersSave() + { $oAccount = $this->getAccountFromToken(); - $sEmail = \trim($this->GetActionParam('Email', '')); - $sPassword = $this->GetActionParam('Password', ''); + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::FILTERS, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + $aIncFilters = $this->GetActionParam('Filters', array()); + + $sRaw = $this->GetActionParam('Raw', ''); + $bRawIsActive = '1' === (string) $this->GetActionParam('RawIsActive', '0'); + + $aFilters = array(); + foreach ($aIncFilters as $aFilter) + { + if ($aFilter) + { + $oFilter = new \RainLoop\Providers\Filters\Classes\Filter(); + if ($oFilter->FromJSON($aFilter)) + { + $aFilters[] = $oFilter; + } + } + } + + $this->Plugins() + ->RunHook('filter.filters-save', array($oAccount, &$aFilters, &$sRaw, &$bRawIsActive)) + ; + + return $this->DefaultResponse(__FUNCTION__, $this->FiltersProvider()->Save($oAccount, + $aFilters, $sRaw, $bRawIsActive)); + } + + /** + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoAccountSetup() + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } $sParentEmail = $oAccount->ParentEmailHelper(); + $aAccounts = $this->GetAccounts($oAccount); + if (!\is_array($aAccounts)) + { + $aAccounts = array(); + } + + $sEmail = \trim($this->GetActionParam('Email', '')); + $sPassword = $this->GetActionParam('Password', ''); + $bNew = '1' === (string) $this->GetActionParam('New', '1'); + $sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail, true); - if ($oAccount->Email() === $sEmail || $sParentEmail === $sEmail) + if ($bNew && ($oAccount->Email() === $sEmail || $sParentEmail === $sEmail || isset($aAccounts[$sEmail]))) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountAlreadyExists); } + else if (!$bNew && !isset($aAccounts[$sEmail])) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountDoesNotExist); + } $oNewAccount = $this->LoginProcess($sEmail, $sPassword); $oNewAccount->SetParentEmail($sParentEmail); - $aAccounts = $this->GetAccounts($oAccount); - if (\is_array($aAccounts)) - { - if (isset($aAccounts[$oNewAccount->Email()])) - { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AccountAlreadyExists); - } - else - { - $aAccounts[$oNewAccount->Email()] = $oNewAccount->GetAuthToken(); - } - } - else - { - $aAccounts = array(); - } - + $aAccounts[$oNewAccount->Email()] = $oNewAccount->GetAuthToken(); if (0 === \strlen($oAccount->ParentEmail())) { $aAccounts[$oAccount->Email()] = $oAccount->GetAuthToken(); @@ -1905,13 +2596,13 @@ class Actions */ public function DoAccountDelete() { - if (!$this->Config()->Get('webmail', 'allow_additional_accounts', true)) + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } - $oAccount = $this->getAccountFromToken(); - $sParentEmail = $oAccount->ParentEmailHelper(); $sEmailToDelete = \trim($this->GetActionParam('EmailToDelete', '')); $sEmailToDelete = \MailSo\Base\Utils::IdnToAscii($sEmailToDelete, true); @@ -1928,7 +2619,7 @@ class Actions $oAccountToChange = $this->GetAccountFromCustomToken($aAccounts[$sParentEmail], false, false); if ($oAccountToChange) { - $this->AuthProcess($oAccountToChange); + $this->AuthToken($oAccountToChange); } } @@ -1939,6 +2630,17 @@ class Actions return $this->FalseResponse(__FUNCTION__); } + /** + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoAttachmentsActions() + { + \sleep(1); + return $this->TrueResponse(__FUNCTION__); + } + /** * @return array * @@ -1946,57 +2648,28 @@ class Actions */ public function DoIdentityUpdate() { - if (!$this->Config()->Get('webmail', 'allow_identities', true)) - { - return $this->FalseResponse(__FUNCTION__); - } - $oAccount = $this->getAccountFromToken(); - $sId = \trim($this->GetActionParam('Id', '')); - $sEmail = \trim($this->GetActionParam('Email', '')); - $sName = \trim($this->GetActionParam('Name', '')); - $sReplyTo = \trim($this->GetActionParam('ReplyTo', '')); - $sBcc = \trim($this->GetActionParam('Bcc', '')); - - if (empty($sEmail)) + $oIdentity = \RainLoop\Model\Identity::NewInstance(); + if (!$oIdentity->FromJSON($this->GetActionParams(), true)) { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::UnknownError); + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument); } - $oEditIdentity = null; + $aIdentitiesForSave = array(); + $aIdentities = $this->GetIdentities($oAccount); - if (0 < \strlen($sId)) + foreach ($aIdentities as $oItem) { - foreach ($aIdentities as &$oItem) + if ($oItem && $oItem->Id() !== $oIdentity->Id()) { - if ($oItem && $sId === $oItem->Id()) - { - $oEditIdentity =& $oItem; - break; - } + $aIdentitiesForSave[] = $oItem; } } - else - { - $sId = \md5($sEmail.\microtime(true)); - } - if (!$oEditIdentity) - { - $aIdentities[] = \RainLoop\Identity::NewInstance($sId, $sEmail, $sName, $sReplyTo, $sBcc); - } - else - { - $oEditIdentity - ->SetEmail($sEmail) - ->SetName($sName) - ->SetReplyTo($sReplyTo) - ->SetBcc($sBcc) - ; - } + $aIdentitiesForSave[] = $oIdentity; - return $this->DefaultResponse(__FUNCTION__, $this->SetIdentities($oAccount, $aIdentities)); + return $this->DefaultResponse(__FUNCTION__, $this->SetIdentities($oAccount, $aIdentitiesForSave)); } /** @@ -2006,11 +2679,6 @@ class Actions */ public function DoIdentityDelete() { - if (!$this->Config()->Get('webmail', 'allow_identities', true)) - { - return $this->FalseResponse(__FUNCTION__); - } - $oAccount = $this->getAccountFromToken(); $sId = \trim($this->GetActionParam('IdToDelete', '')); @@ -2037,31 +2705,135 @@ class Actions * * @throws \MailSo\Base\Exceptions\Exception */ - public function DoAccountsAndIdentities() + public function DoTemplateSetup() { $oAccount = $this->getAccountFromToken(); - $mAccounts = false; - if ($this->Config()->Get('webmail', 'allow_additional_accounts', true)) + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::TEMPLATES, $oAccount)) { - $mAccounts = $this->GetAccounts($oAccount); - $mAccounts = \array_keys($mAccounts); + return $this->FalseResponse(__FUNCTION__); + } - foreach ($mAccounts as $iIndex => $sName) + $oTemplate = \RainLoop\Model\Template::NewInstance(); + if (!$oTemplate->FromJSON($this->GetActionParams(), true)) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument); + } + + if ('' === $oTemplate->Id()) + { + $oTemplate->GenerateID(); + } + + $aTemplatesForSave = array(); + $aTemplates = $this->GetTemplates($oAccount); + + + foreach ($aTemplates as $oItem) + { + if ($oItem && $oItem->Id() !== $oTemplate->Id()) { - $mAccounts[$iIndex] = \MailSo\Base\Utils::IdnToUtf8($sName); + $aTemplatesForSave[] = $oItem; } } - $mIdentities = false; - if ($this->Config()->Get('webmail', 'allow_identities', true)) + $aTemplatesForSave[] = $oTemplate; + + return $this->DefaultResponse(__FUNCTION__, $this->SetTemplates($oAccount, $aTemplatesForSave)); + } + + /** + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoTemplateDelete() + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::TEMPLATES, $oAccount)) { - $mIdentities = $this->GetIdentities($oAccount); + return $this->FalseResponse(__FUNCTION__); } - return $this->DefaultResponse(__FUNCTION__, array( - 'Accounts' => $mAccounts, - 'Identities' => $mIdentities + $sId = \trim($this->GetActionParam('IdToDelete', '')); + if (empty($sId)) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::UnknownError); + } + + $aNew = array(); + $aTemplates = $this->GetTemplates($oAccount); + foreach ($aTemplates as $oItem) + { + if ($oItem && $sId !== $oItem->Id()) + { + $aNew[] = $oItem; + } + } + + return $this->DefaultResponse(__FUNCTION__, $this->SetTemplates($oAccount, $aNew)); + } + + /** + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoTemplateGetByID() + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::TEMPLATES, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + $sId = \trim($this->GetActionParam('ID', '')); + if (empty($sId)) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::UnknownError); + } + + $oTemplate = false; + $aTemplates = $this->GetTemplates($oAccount); + + foreach ($aTemplates as $oItem) + { + if ($oItem && $sId === $oItem->Id()) + { + $oTemplate = $oItem; + break; + } + } + + $oTemplate->SetPopulateAlways(true); + return $this->DefaultResponse(__FUNCTION__, $oTemplate); + } + + /** + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoAccountsAndIdentitiesSortOrder() + { + $oAccount = $this->getAccountFromToken(); + + $aAccounts = $this->GetActionParam('Accounts', null); + $aIdentities = $this->GetActionParam('Identities', null); + + if (!\is_array($aAccounts) || !\is_array($aIdentities)) + { + return $this->FalseResponse(__FUNCTION__); + } + + return $this->DefaultResponse(__FUNCTION__, $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'accounts_identities_order', + \json_encode(array( + 'Accounts' => $aAccounts, + 'Identities' => $aIdentities + )) )); } @@ -2070,12 +2842,12 @@ class Actions * * @throws \MailSo\Base\Exceptions\Exception */ - public function DoAccountsAndIdentitiesNew() + public function DoAccountsAndIdentities() { $oAccount = $this->getAccountFromToken(); $mAccounts = false; - if ($this->Config()->Get('webmail', 'allow_additional_accounts', true)) + if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) { $mAccounts = $this->GetAccounts($oAccount); $mAccounts = \array_keys($mAccounts); @@ -2088,12 +2860,113 @@ class Actions return $this->DefaultResponse(__FUNCTION__, array( 'Accounts' => $mAccounts, - 'Identities' => $this->GetIdentitiesNew($oAccount) + 'Identities' => $this->GetIdentities($oAccount) )); } /** - * @param \RainLoop\Account $oAccount + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoTemplates() + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::TEMPLATES, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + return $this->DefaultResponse(__FUNCTION__, array( + 'Templates' => $this->GetTemplates($oAccount) + )); + } + + /** + * @param string $sHash + * + * @return int + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function getAccountUnredCountFromHash($sHash) + { + $iResult = 0; + + $oAccount = $this->GetAccountFromCustomToken($sHash, false); + if ($oAccount) + { + try + { + $oMailClient = \MailSo\Mail\MailClient::NewInstance(); + $oMailClient->SetLogger($this->Logger()); + + $oAccount->IncConnectAndLoginHelper($this->Plugins(),$oMailClient, $this->Config()); + + $iResult = $oMailClient->InboxUnreadCount(); + + $oMailClient->LogoutAndDisconnect(); + } + catch (\Exception $oException) + { + $this->Logger()->WriteException($oException); + } + } + + return $iResult; + } + + /** + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoAccountsCounts() + { + $oAccount = $this->getAccountFromToken(); + + $bComplete = true; + $aCounts = array(); + + if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) + { + $iLimit = 7; + $mAccounts = $this->GetAccounts($oAccount); + if (\is_array($mAccounts) && 0 < \count($mAccounts)) + { + if ($iLimit > \count($mAccounts)) + { + $mAccounts = \array_slice($mAccounts, 0, $iLimit); + } + else + { + $bComplete = false; + } + + if (0 < \count($mAccounts)) + { + foreach ($mAccounts as $sEmail => $sHash) + { + $aCounts[] = array(\MailSo\Base\Utils::IdnToUtf8($sEmail), + $oAccount->Email() === $sEmail ? 0 : $this->getAccountUnredCountFromHash($sHash)); + } + } + } + } + else + { + $aCounts[] = array(\MailSo\Base\Utils::IdnToUtf8($oAccount->Email()), 0); + } + + return $this->DefaultResponse(__FUNCTION__, array( + 'Complete' => $bComplete, + 'Counts' => $aCounts + )); + } + + /** + * @param \RainLoop\Model\Account $oAccount */ public function ClearSignMeData($oAccount) { @@ -2101,9 +2974,9 @@ class Actions { \RainLoop\Utils::ClearCookie(\RainLoop\Actions::AUTH_SIGN_ME_TOKEN_KEY); - $this->StorageProvider()->Clear(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::SignMeUserToken($oAccount->SignMeToken()) + $this->StorageProvider()->Clear($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'sign_me' ); } } @@ -2114,9 +2987,17 @@ class Actions public function DoLogout() { $oAccount = $this->getAccountFromToken(false); - if ($oAccount && $oAccount->SignMe()) + if ($oAccount) { - $this->ClearSignMeData($oAccount); + if ($oAccount->SignMe()) + { + $this->ClearSignMeData($oAccount); + } + + if (!$oAccount->IsAdditionalAccount()) + { + \RainLoop\Utils::ClearCookie(\RainLoop\Actions::AUTH_SPEC_TOKEN_KEY); + } } return $this->TrueResponse(__FUNCTION__); @@ -2133,9 +3014,11 @@ class Actions $bMainCache = false; $bFilesCache = false; + $bVersionsCache = false; $iOneDay1 = 60 * 60 * 23; $iOneDay2 = 60 * 60 * 25; + $iOneDay3 = 60 * 60 * 30; $sTimers = $this->StorageProvider()->Get(null, \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, 'Cache/Timers', ''); @@ -2144,6 +3027,7 @@ class Actions $iMainCacheTime = !empty($aTimers[0]) && \is_numeric($aTimers[0]) ? (int) $aTimers[0] : 0; $iFilesCacheTime = !empty($aTimers[1]) && \is_numeric($aTimers[1]) ? (int) $aTimers[1] : 0; + $iVersionsCacheTime = !empty($aTimers[2]) && \is_numeric($aTimers[2]) ? (int) $aTimers[2] : 0; if (0 === $iMainCacheTime || $iMainCacheTime + $iOneDay1 < \time()) { @@ -2157,13 +3041,19 @@ class Actions $iFilesCacheTime = \time(); } - if ($bMainCache || $bFilesCache) + if (0 === $iVersionsCacheTime || $iVersionsCacheTime + $iOneDay3 < \time()) + { + $bVersionsCache = true; + $iVersionsCacheTime = \time(); + } + + if ($bMainCache || $bFilesCache || $bVersionsCache) { if (!$this->StorageProvider()->Put(null, \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, 'Cache/Timers', - \implode(',', array($iMainCacheTime, $iFilesCacheTime)))) + \implode(',', array($iMainCacheTime, $iFilesCacheTime, $iVersionsCacheTime)))) { - $bMainCache = $bFilesCache = false; + $bMainCache = $bFilesCache = $bVersionsCache = false; } } @@ -2173,13 +3063,16 @@ class Actions $this->Cacher()->GC(48); $this->Logger()->Write('Cacher GC: End'); } - - if ($bFilesCache) + else if ($bFilesCache) { $this->Logger()->Write('Files GC: Begin'); $this->FilesProvider()->GC(48); $this->Logger()->Write('Files GC: End'); } + else if ($bVersionsCache) + { + $this->removeOldVersion(); + } $this->Plugins()->RunHook('service.app-delay-start-end'); @@ -2193,17 +3086,17 @@ class Actions { $oAccount = $this->getAccountFromToken(); - $oSettings = $this->SettingsProvider()->Load($oAccount); + $oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount); - $oSettings->SetConf('SentFolder', $this->GetActionParam('SentFolder', '')); - $oSettings->SetConf('DraftFolder', $this->GetActionParam('DraftFolder', '')); - $oSettings->SetConf('SpamFolder', $this->GetActionParam('SpamFolder', '')); - $oSettings->SetConf('TrashFolder', $this->GetActionParam('TrashFolder', '')); - $oSettings->SetConf('ArchiveFolder', $this->GetActionParam('ArchiveFolder', '')); - $oSettings->SetConf('NullFolder', $this->GetActionParam('NullFolder', '')); + $oSettingsLocal->SetConf('SentFolder', $this->GetActionParam('SentFolder', '')); + $oSettingsLocal->SetConf('DraftFolder', $this->GetActionParam('DraftFolder', '')); + $oSettingsLocal->SetConf('SpamFolder', $this->GetActionParam('SpamFolder', '')); + $oSettingsLocal->SetConf('TrashFolder', $this->GetActionParam('TrashFolder', '')); + $oSettingsLocal->SetConf('ArchiveFolder', $this->GetActionParam('ArchiveFolder', '')); + $oSettingsLocal->SetConf('NullFolder', $this->GetActionParam('NullFolder', '')); return $this->DefaultResponse(__FUNCTION__, - $this->SettingsProvider()->Save($oAccount, $oSettings)); + $this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal)); } /** @@ -2264,18 +3157,27 @@ class Actions case \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS: $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_additional_accounts', 'bool'); break; - case \RainLoop\Enumerations\Capa::ADDITIONAL_IDENTITIES: - $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_identities', 'bool'); + case \RainLoop\Enumerations\Capa::TEMPLATES: + $this->setConfigFromParams($oConfig, $sParamName, 'capa', 'templates', 'bool'); break; case \RainLoop\Enumerations\Capa::TWO_FACTOR: $this->setConfigFromParams($oConfig, $sParamName, 'security', 'allow_two_factor_auth', 'bool'); break; + case \RainLoop\Enumerations\Capa::TWO_FACTOR_FORCE: + $this->setConfigFromParams($oConfig, $sParamName, 'security', 'force_two_factor_auth', 'bool'); + break; case \RainLoop\Enumerations\Capa::GRAVATAR: $this->setConfigFromParams($oConfig, $sParamName, 'labs', 'allow_gravatar', 'bool'); break; + case \RainLoop\Enumerations\Capa::ATTACHMENT_THUMBNAILS: + $this->setConfigFromParams($oConfig, $sParamName, 'interface', 'show_attachment_thumbnail', 'bool'); + break; case \RainLoop\Enumerations\Capa::THEMES: $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_themes', 'bool'); break; + case \RainLoop\Enumerations\Capa::USER_BACKGROUND: + $this->setConfigFromParams($oConfig, $sParamName, 'webmail', 'allow_user_background', 'bool'); + break; case \RainLoop\Enumerations\Capa::OPEN_PGP: $this->setConfigFromParams($oConfig, $sParamName, 'security', 'openpgp', 'bool'); break; @@ -2323,6 +3225,7 @@ class Actions */ public function DoAdminSettingsUpdate() { +// sleep(3); // return $this->DefaultResponse(__FUNCTION__, false); $this->IsAdminLoggined(); @@ -2332,13 +3235,20 @@ class Actions $self = $this; $this->setConfigFromParams($oConfig, 'Language', 'webmail', 'language', 'string', function ($sLanguage) use ($self) { - return $self->ValidateLanguage($sLanguage); + return $self->ValidateLanguage($sLanguage, '', false); + }); + + $this->setConfigFromParams($oConfig, 'LanguageAdmin', 'webmail', 'language_admin', 'string', function ($sLanguage) use ($self) { + return $self->ValidateLanguage($sLanguage, '', true); }); $this->setConfigFromParams($oConfig, 'Theme', 'webmail', 'theme', 'string', function ($sTheme) use ($self) { return $self->ValidateTheme($sTheme); }); + $this->setConfigFromParams($oConfig, 'VerifySslCertificate', 'ssl', 'verify_certificate', 'bool'); + $this->setConfigFromParams($oConfig, 'AllowSelfSigned', 'ssl', 'allow_self_signed', 'bool'); + $this->setConfigFromParams($oConfig, 'UseLocalProxyForExternalImages', 'labs', 'use_local_proxy_for_external_images', 'bool'); $this->setConfigFromParams($oConfig, 'AllowLanguagesOnSettings', 'webmail', 'allow_languages_on_settings', 'bool'); @@ -2359,29 +3269,47 @@ class Actions }); $this->setCapaFromParams($oConfig, 'CapaAdditionalAccounts', \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS); - $this->setCapaFromParams($oConfig, 'CapaAdditionalIdentities', \RainLoop\Enumerations\Capa::ADDITIONAL_IDENTITIES); + $this->setCapaFromParams($oConfig, 'CapaTemplates', \RainLoop\Enumerations\Capa::TEMPLATES); $this->setCapaFromParams($oConfig, 'CapaTwoFactorAuth', \RainLoop\Enumerations\Capa::TWO_FACTOR); + $this->setCapaFromParams($oConfig, 'CapaTwoFactorAuthForce', \RainLoop\Enumerations\Capa::TWO_FACTOR_FORCE); $this->setCapaFromParams($oConfig, 'CapaOpenPGP', \RainLoop\Enumerations\Capa::OPEN_PGP); $this->setCapaFromParams($oConfig, 'CapaGravatar', \RainLoop\Enumerations\Capa::GRAVATAR); $this->setCapaFromParams($oConfig, 'CapaThemes', \RainLoop\Enumerations\Capa::THEMES); + $this->setCapaFromParams($oConfig, 'CapaUserBackground', \RainLoop\Enumerations\Capa::USER_BACKGROUND); + $this->setCapaFromParams($oConfig, 'CapaAttachmentThumbnails', \RainLoop\Enumerations\Capa::ATTACHMENT_THUMBNAILS); $this->setConfigFromParams($oConfig, 'DetermineUserLanguage', 'login', 'determine_user_language', 'bool'); $this->setConfigFromParams($oConfig, 'DetermineUserDomain', 'login', 'determine_user_domain', 'bool'); - if ($this->GetCapa(true, \RainLoop\Enumerations\Capa::PREM)) - { - $this->setConfigFromParams($oConfig, 'Title', 'webmail', 'title', 'string'); - $this->setConfigFromParams($oConfig, 'LoadingDescription', 'webmail', 'loading_description', 'string'); + $this->setConfigFromParams($oConfig, 'Title', 'webmail', 'title', 'string'); + $this->setConfigFromParams($oConfig, 'LoadingDescription', 'webmail', 'loading_description', 'string'); + if ($this->HasOneOfActionParams(array( + 'LoginLogo', 'LoginBackground', 'LoginDescription', 'LoginCss', 'LoginPowered', + 'UserLogo', 'UserCss', + 'WelcomePageUrl', 'WelcomePageDisplay' + )) && $this->PremType()) + { $this->setConfigFromParams($oConfig, 'LoginLogo', 'branding', 'login_logo', 'string'); + $this->setConfigFromParams($oConfig, 'LoginBackground', 'branding', 'login_background', 'string'); $this->setConfigFromParams($oConfig, 'LoginDescription', 'branding', 'login_desc', 'string'); $this->setConfigFromParams($oConfig, 'LoginCss', 'branding', 'login_css', 'string'); + $this->setConfigFromParams($oConfig, 'LoginPowered', 'branding', 'login_powered', 'bool'); + + $this->setConfigFromParams($oConfig, 'UserLogo', 'branding', 'user_logo', 'string'); + $this->setConfigFromParams($oConfig, 'UserCss', 'branding', 'user_css', 'string'); + + $this->setConfigFromParams($oConfig, 'WelcomePageUrl', 'branding', 'welcome_page_url', 'string'); + $this->setConfigFromParams($oConfig, 'WelcomePageDisplay', 'branding', 'welcome_page_display', 'string'); } $this->setConfigFromParams($oConfig, 'TokenProtection', 'security', 'csrf_protection', 'bool'); $this->setConfigFromParams($oConfig, 'EnabledPlugins', 'plugins', 'enable', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleEnable', 'social', 'google_enable', 'bool'); + $this->setConfigFromParams($oConfig, 'GoogleEnableAuth', 'social', 'google_enable_auth', 'bool'); + $this->setConfigFromParams($oConfig, 'GoogleEnableDrive', 'social', 'google_enable_drive', 'bool'); + $this->setConfigFromParams($oConfig, 'GoogleEnablePreview', 'social', 'google_enable_preview', 'bool'); $this->setConfigFromParams($oConfig, 'GoogleClientID', 'social', 'google_client_id', 'string'); $this->setConfigFromParams($oConfig, 'GoogleClientSecret', 'social', 'google_client_secret', 'string'); $this->setConfigFromParams($oConfig, 'GoogleApiKey', 'social', 'google_api_key', 'string'); @@ -2421,9 +3349,10 @@ class Actions throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::AuthError); } - $this->setAdminAuthToken($this->getAdminToken()); + $sToken = $this->getAdminToken(); + $this->setAdminAuthToken($sToken); - return $this->TrueResponse(__FUNCTION__); + return $this->DefaultResponse(__FUNCTION__, $sToken ? true : false); } /** @@ -2470,6 +3399,144 @@ class Actions )); } + /** + * @param string $sDomain + * + * @return string + */ +// public function domainPathHelper($sDomain) +// { +// $sDomain = \strtolower(\trim($sDomain)); +// +// $sTempDomain = \preg_replace('/^(webmail|email|mail)\./', '', $sDomain); +// if (false === \strpos($sTempDomain, '.')) +// { +// $sTempDomain = $sDomain; +// } +// +// $sDomainPrefix = $sTempDomain; +// if (false === \strpos($sDomainPrefix, '.')) +// { +// $sDomainPrefix = $sTempDomain; +// } +// +// $sDomainPrefix = \substr(\preg_replace('/[^a-z0-9]+/', '', $sDomainPrefix), 0, 2); +// $sDomainPrefix = \str_pad($sDomainPrefix, 2, '_'); +// +// return 'd/'.\substr($sDomainPrefix, 0, 1).'/'.$sDomainPrefix.'/'.\urlencode($sTempDomain); +// } + + /** + * @return string + */ + public function licenseHelper($sForce = false, $bLongCache = false, $iFastCacheTimeInMin = 10, $iLongCacheTimeInDays = 3) + { + $sDomain = \trim(APP_SITE); + + $oCacher = $this->Cacher(null, true); + $oHttp = \MailSo\Base\Http::SingletonInstance(); + + if (0 === \strlen($sDomain) || $oHttp->CheckLocalhost($sDomain) || !$oCacher || !$oCacher->Verify(true)) + { + return 'NO'; + } + + $sDomainKeyValue = \RainLoop\KeyPathHelper::LicensingDomainKeyValue($sDomain); + $sDomainLongKeyValue = \RainLoop\KeyPathHelper::LicensingDomainKeyOtherValue($sDomain); + + $sValue = ''; + if (!$sForce) + { + if ($bLongCache) + { + $bLock = $oCacher->GetLock($sDomainLongKeyValue); + $iTime = $bLock ? 0 : $oCacher->GetTimer($sDomainLongKeyValue); + + if ($bLock || (0 < $iTime && \time() < $iTime + (60 * 60 * 24) * $iLongCacheTimeInDays)) + { + $sValue = $oCacher->Get($sDomainLongKeyValue); + } + } + else + { + $iTime = $oCacher->GetTimer($sDomainKeyValue); + if (0 < $iTime && \time() < $iTime + 60 * $iFastCacheTimeInMin) + { + $sValue = $oCacher->Get($sDomainKeyValue); + } + } + } + + if (0 === \strlen($sValue)) + { + if ($bLongCache) + { + if (!$oCacher->SetTimer($sDomainLongKeyValue)) + { + return 'NO'; + } + + $oCacher->SetLock($sDomainLongKeyValue); + } + + $iCode = 0; + $sContentType = ''; + +// $sValue = $oHttp->GetUrlAsString(APP_STATUS_PATH.$this->domainPathHelper($sDomain), +// 'RainLoop/'.APP_VERSION, $sContentType, $iCode, $this->Logger(), 5, +// $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''), +// array(), false +// ); + + $sValue = $oHttp->GetUrlAsString(APP_API_PATH.'status/'.\urlencode($sDomain), + 'RainLoop/'.APP_VERSION, $sContentType, $iCode, $this->Logger(), 5, + $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''), + array(), false + ); + + $this->Logger()->Write($sValue); + + /*if (404 === $iCode) + { + $sValue = 'NO'; + } + else */if (200 !== $iCode) + { + $sValue = ''; + } + + $oCacher->SetTimer($sDomainKeyValue); + + $oCacher->Set($sDomainKeyValue, $sValue); + $oCacher->Set($sDomainLongKeyValue, $sValue); + + if ($bLongCache) + { + $oCacher->RemoveLock($sDomainLongKeyValue); + } + } + + return $sValue; + } + + /** + * @param string $sInput + * @param int $iExpired = 0 + * + * @return bool + */ + public function licenseParser($sInput, &$iExpired = 0) + { + $aMatch = array(); + if (\preg_match('/^EXPIRED:([\d]+)$/', $sInput, $aMatch)) + { + $iExpired = (int) $aMatch[1]; + return \time() < $iExpired; + } + + return false; + } + /** * @return array */ @@ -2478,61 +3545,29 @@ class Actions $iStart = \time(); $this->IsAdminLoggined(); - $sForce = '1' === (string) $this->GetActionParam('Force', '0'); + $bForce = '1' === (string) $this->GetActionParam('Force', '0'); $mResult = false; $iErrorCode = -1; - $oHttp = \MailSo\Base\Http::SingletonInstance(); - if ($oHttp->CheckLocalhost(APP_SITE)) + if (2 < \strlen(APP_SITE)) { - return $this->DefaultResponse(__FUNCTION__, $mResult); - } + $sValue = $this->licenseHelper($bForce); - $sDomain = APP_SITE; - if (2 < \strlen($sDomain)) - { - $sValue = ''; - $iTime = $this->Cacher()->GetTimer(\RainLoop\KeyPathHelper::LicensingDomainKeyValue($sDomain)); - if (!$sForce && $iTime + 60 * 5 > \time()) + if ($iStart === \time()) { - $sValue = $this->Cacher()->Get(\RainLoop\KeyPathHelper::LicensingDomainKeyValue($sDomain)); + \sleep(1); } - if (0 === \strlen($sValue)) - { - $iCode = 0; - $sContentType = ''; - - $sValue = $oHttp->GetUrlAsString(APP_API_PATH.'status/'.\urlencode($sDomain), - 'RainLoop', $sContentType, $iCode, $this->Logger(), 10, - $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''), - array(), false - ); - - if (200 !== $iCode) - { - $sValue = ''; - } - - if ($iStart === \time()) - { - \sleep(1); - } - - $this->Cacher()->Set(\RainLoop\KeyPathHelper::LicensingDomainKeyValue($sDomain), $sValue); - $this->Cacher()->SetTimer(\RainLoop\KeyPathHelper::LicensingDomainKeyValue($sDomain)); - } - - $aMatch = array(); - if (5 < \strlen($sValue) && \preg_match('/^EXPIRED:([\d]+)$/', $sValue, $aMatch)) + $iExpired = 0; + if ($this->licenseParser($sValue, $iExpired)) { $mResult = array( 'Banned' => false, - 'Expired' => (int) $aMatch[1], + 'Expired' => $iExpired, ); } - else if ($sValue === 'NO') + else if ($sValue === 'NO' || \preg_match('/^EXPIRED:[\d]+$/', $sValue)) { $iErrorCode = -1; } @@ -2577,7 +3612,7 @@ class Actions \sleep(1); $sValue = $oHttp->GetUrlAsString(APP_API_PATH.'activate/'.\urlencode($sDomain).'/'.\urlencode($sKey), - 'RainLoop', $sContentType, $iCode, $this->Logger(), 10, + 'RainLoop/'.APP_VERSION, $sContentType, $iCode, $this->Logger(), 10, $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''), array(), false ); @@ -2633,23 +3668,31 @@ class Actions $sLogin = \trim($this->GetActionParam('Login', '')); $sPassword = $this->GetActionParam('Password', ''); - $sNewPassword = $this->GetActionParam('NewPassword', ''); - $this->Logger()->AddSecret($sPassword); - $this->Logger()->AddSecret($sNewPassword); + + $sNewPassword = $this->GetActionParam('NewPassword', ''); + if (0 < \strlen(\trim($sNewPassword))) + { + $this->Logger()->AddSecret($sNewPassword); + } if ($oConfig->ValidatePassword($sPassword)) { - if (0 < strlen($sLogin)) + if (0 < \strlen($sLogin)) { $oConfig->Set('security', 'admin_login', $sLogin); } - $oConfig->SetPassword($sNewPassword); + if (0 < \strlen(\trim($sNewPassword))) + { + $oConfig->SetPassword($sNewPassword); + } + $bResult = true; } - return $this->DefaultResponse(__FUNCTION__, $bResult ? $oConfig->Save() : false); + return $this->DefaultResponse(__FUNCTION__, $bResult ? + ($oConfig->Save() ? array('Weak' => $oConfig->ValidatePassword('12345')) : false) : false); } /** @@ -2716,7 +3759,7 @@ class Actions $oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this); return $this->DefaultResponse(__FUNCTION__, - $oDomain instanceof \RainLoop\Domain ? $this->DomainProvider()->Save($oDomain) : false); + $oDomain instanceof \RainLoop\Model\Domain ? $this->DomainProvider()->Save($oDomain) : false); } /** @@ -2730,9 +3773,14 @@ class Actions $sImapErrorDesc = ''; $bSmtpResult = false; $sSmtpErrorDesc = ''; + $bSieveResult = false; + $sSieveErrorDesc = ''; $iImapTime = 0; $iSmtpTime = 0; + $iSieveTime = 0; + + $iConnectionTimeout = 5; $oDomain = $this->DomainProvider()->LoadOrCreateNewFromAction($this, 'domain-test-connection.de'); if ($oDomain) @@ -2740,10 +3788,14 @@ class Actions try { $oImapClient = \MailSo\Imap\ImapClient::NewInstance()->SetLogger($this->Logger()); - $oImapClient->SetTimeOuts(5); + $oImapClient->SetTimeOuts($iConnectionTimeout); $iTime = \microtime(true); - $oImapClient->Connect($oDomain->IncHost($oDomain->Name()), $oDomain->IncPort(), $oDomain->IncSecure()); + $oImapClient->Connect($oDomain->IncHost(), $oDomain->IncPort(), $oDomain->IncSecure(), + !!$this->Config()->Get('ssl', 'verify_certificate', false), + !!$this->Config()->Get('ssl', 'allow_self_signed', true) + ); + $iImapTime = \microtime(true) - $iTime; $oImapClient->Disconnect(); $bImapResult = true; @@ -2763,36 +3815,90 @@ class Actions $sImapErrorDesc = $oException->getMessage(); } - try + if ($oDomain->OutUsePhpMail()) { - $oSmtpClient = \MailSo\Smtp\SmtpClient::NewInstance()->SetLogger($this->Logger()); - $oSmtpClient->SetTimeOuts(5); - - $iTime = \microtime(true); - $oSmtpClient->Connect($oDomain->OutHost($oDomain->Name()), $oDomain->OutPort(), '127.0.0.1', $oDomain->OutSecure()); - $iSmtpTime = \microtime(true) - $iTime; - $oSmtpClient->Disconnect(); - $bSmtpResult = true; - } - catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) - { - $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); - $sSmtpErrorDesc = $oException->getSocketMessage(); - if (empty($sSmtpErrorDesc)) + $bSmtpResult = \MailSo\Base\Utils::FunctionExistsAndEnabled('mail'); + if (!$bSmtpResult) { + $sSmtpErrorDesc = 'PHP: mail() function is undefined'; + } + } + else + { + try + { + $oSmtpClient = \MailSo\Smtp\SmtpClient::NewInstance()->SetLogger($this->Logger()); + $oSmtpClient->SetTimeOuts($iConnectionTimeout); + + $iTime = \microtime(true); + $oSmtpClient->Connect($oDomain->OutHost(), $oDomain->OutPort(), + \MailSo\Smtp\SmtpClient::EhloHelper(), $oDomain->OutSecure(), + !!$this->Config()->Get('ssl', 'verify_certificate', false), + !!$this->Config()->Get('ssl', 'allow_self_signed', true) + ); + + $iSmtpTime = \microtime(true) - $iTime; + $oSmtpClient->Disconnect(); + $bSmtpResult = true; + } + catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); + $sSmtpErrorDesc = $oException->getSocketMessage(); + if (empty($sSmtpErrorDesc)) + { + $sSmtpErrorDesc = $oException->getMessage(); + } + } + catch (\Exception $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); $sSmtpErrorDesc = $oException->getMessage(); } } - catch (\Exception $oException) + + if ($oDomain->UseSieve()) { - $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); - $sSmtpErrorDesc = $oException->getMessage(); + try + { + $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->Logger()); + $oSieveClient->SetTimeOuts($iConnectionTimeout); + + $iTime = \microtime(true); + $oSieveClient->Connect($oDomain->SieveHost(), $oDomain->SievePort(), $oDomain->SieveSecure(), + !!$this->Config()->Get('ssl', 'verify_certificate', false), + !!$this->Config()->Get('ssl', 'allow_self_signed', true) + ); + + $iSieveTime = \microtime(true) - $iTime; + $oSieveClient->Disconnect(); + $bSieveResult = true; + } + catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); + $sSieveErrorDesc = $oException->getSocketMessage(); + if (empty($sSieveErrorDesc)) + { + $sSieveErrorDesc = $oException->getMessage(); + } + } + catch (\Exception $oException) + { + $this->Logger()->WriteException($oException, \MailSo\Log\Enumerations\Type::ERROR); + $sSieveErrorDesc = $oException->getMessage(); + } + } + else + { + $bSieveResult = true; } } return $this->DefaultResponse(__FUNCTION__, array( 'Imap' => $bImapResult ? true : $sImapErrorDesc, - 'Smtp' => $bSmtpResult ? true : $sSmtpErrorDesc + 'Smtp' => $bSmtpResult ? true : $sSmtpErrorDesc, + 'Sieve' => $bSieveResult ? true : $sSieveErrorDesc )); } @@ -2830,10 +3936,10 @@ class Actions /** * @param string $sRepo * @param bool $bReal = false - * @param bool $bMain = true + * * @return array */ - private function getRepositoryDataByUrl($sRepo, &$bReal = false, $bMain = true) + private function getRepositoryDataByUrl($sRepo, &$bReal = false) { $bReal = false; $aRep = null; @@ -2910,7 +4016,6 @@ class Actions 'version' => $oItem->version, 'file' => $oItem->file, 'release' => $oItem->release, - 'release_notes' => isset($oItem->{'release_notes'}) ? $oItem->{'release_notes'} : '', 'desc' => $oItem->description ); } @@ -2921,10 +4026,27 @@ class Actions return $aResult; } + /** + * @return string + */ + private function getCoreChannel() + { + $sChannel = \trim(\strtolower($this->Config()->Get('labs', 'update_channel', 'stable'))); + if (empty($sChannel) || !\in_array($sChannel, array('stable', 'beta'))) + { + $sChannel = 'stable'; + } + + return $sChannel; + } + private function getCoreData(&$bReal) { $bReal = false; - $sRepo = APP_REPO_CORE_FILE; + + $sChannel = $this->getCoreChannel(); + + $sRepo = \str_replace('{{channel}}', $sChannel, APP_REPO_CORE_FILE); $oHttp = \MailSo\Base\Http::SingletonInstance(); @@ -3019,7 +4141,6 @@ class Actions 'version' => '', 'file' => '', 'release' => '', - 'release_notes' => '', 'desc' => '' )); } @@ -3074,15 +4195,40 @@ class Actions } $sVersion = empty($aData['version']) ? '' : $aData['version']; + $sType = empty($aData['channel']) ? 'stable' : $aData['channel']; + + $sWarnings = empty($aData['warnings']) ? '' : $aData['warnings']; + $aWarnings = $sWarnings ? explode('|', $sWarnings) : array(); + + $sCurrentVersion = APP_VERSION; + + $bShowWarning = false; + if ($sCurrentVersion !== APP_DEV_VERSION) + { + foreach ($aWarnings as $sWarningVersion) + { + $sWarningVersion = \trim($sWarningVersion); + + if (\version_compare($sCurrentVersion, $sWarningVersion, '<') && + \version_compare($sVersion, $sWarningVersion, '>=')) + { + $bShowWarning = true; + break; + } + } + } return $this->DefaultResponse(__FUNCTION__, array( 'Real' => $bReal, 'Access' => $bRainLoopAccess, 'Updatable' => $bRainLoopUpdatable, - 'Version' => APP_VERSION, + 'Warning' => $bShowWarning, + 'Channel' => $this->getCoreChannel(), + 'Type' => $sType, + 'Version' => $sCurrentVersion, 'RemoteVersion' => $sVersion, 'RemoteRelease' => empty($aData['release']) ? '' : $aData['release'], - 'VersionCompare' => \version_compare(APP_VERSION, $sVersion) + 'VersionCompare' => \version_compare($sCurrentVersion, $sVersion) )); } @@ -3094,6 +4240,7 @@ class Actions $this->IsAdminLoggined(); $bReal = false; + $sNewVersion = ''; $bRainLoopUpdatable = $this->rainLoopUpdatable(); $bRainLoopAccess = $this->rainLoopCoreAccess(); @@ -3130,7 +4277,6 @@ class Actions \is_dir($sTmpFolder.'/rainloop/')) { $aMatch = array(); - $sNewVersion = ''; $sIndexFile = \file_get_contents($sTmpFolder.'/index.php'); if (\preg_match('/\'APP_VERSION\', \'([^\']+)\'/', $sIndexFile, $aMatch) && !empty($aMatch[1])) { @@ -3149,6 +4295,19 @@ class Actions \is_file(APP_INDEX_ROOT_PATH.'rainloop/v/'.$sNewVersion.'/index.php')) { $bResult = \copy($sTmpFolder.'/index.php', APP_INDEX_ROOT_PATH.'index.php'); + + if ($bResult) + { + if (\MailSo\Base\Utils::FunctionExistsAndEnabled('opcache_invalidate')) + { + @\opcache_invalidate(APP_INDEX_ROOT_PATH.'index.php', true); + } + + if (\MailSo\Base\Utils::FunctionExistsAndEnabled('apc_delete_file')) + { + @\apc_delete_file(APP_INDEX_ROOT_PATH.'index.php'); + } + } } else { @@ -3180,6 +4339,45 @@ class Actions return $this->DefaultResponse(__FUNCTION__, $bResult); } + public function removeOldVersion() + { + $sVPath = APP_INDEX_ROOT_PATH.'rainloop/v/'; + + $this->Logger()->Write('Versions GC: Begin'); + + $aDirs = @\array_map('basename', @\array_filter(@\glob($sVPath.'*'), 'is_dir')); + + $this->Logger()->Write('Versions GC: Count:'.(\is_array($aDirs) ? \count($aDirs) : 0)); + + if (\is_array($aDirs) && 5 < \count($aDirs)) + { + \uasort($aDirs, 'version_compare'); + + foreach ($aDirs as $sName) + { + if (APP_DEV_VERSION !== $sName && APP_VERSION !== $sName) + { + $this->Logger()->Write('Versions GC: Begin to remove "'.$sVPath.$sName.'" version'); + + if (@\unlink($sVPath.$sName.'/index.php')) + { + @\MailSo\Base\Utils::RecRmDir($sVPath.$sName); + } + else + { + $this->Logger()->Write('Versions GC (Error): index file cant be removed from"'.$sVPath.$sName.'"', + \MailSo\Log\Enumerations\Type::ERROR); + } + + $this->Logger()->Write('Versions GC: End to remove "'.$sVPath.$sName.'" version'); + break; + } + } + } + + $this->Logger()->Write('Versions GC: End'); + } + /** * @return array */ @@ -3539,13 +4737,13 @@ class Actions */ public function DoSettingsUpdate() { + $self = $this; + $oConfig = $this->Config(); + $oAccount = $this->getAccountFromToken(); $oSettings = $this->SettingsProvider()->Load($oAccount); - - $self = $this; - - $oConfig = $this->Config(); + $oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount); if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) { @@ -3558,15 +4756,15 @@ class Actions $oSettings->SetConf('Language', $this->ValidateLanguage($oConfig->Get('webmail', 'language', 'en'))); } - if ($oConfig->Get('webmail', 'allow_themes', true)) + if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::THEMES, $oAccount)) { - $this->setSettingsFromParams($oSettings, 'Theme', 'string', function ($sTheme) use ($self) { + $this->setSettingsFromParams($oSettingsLocal, 'Theme', 'string', function ($sTheme) use ($self) { return $self->ValidateTheme($sTheme); }); } else { - $oSettings->SetConf('Theme', $this->ValidateLanguage($oConfig->Get('webmail', 'theme', 'Default'))); + $oSettingsLocal->SetConf('Theme', $this->ValidateTheme($oConfig->Get('webmail', 'theme', 'Default'))); } $this->setSettingsFromParams($oSettings, 'MPP', 'int', function ($iValue) { @@ -3582,27 +4780,19 @@ class Actions $this->setSettingsFromParams($oSettings, 'EditorDefaultType', 'string'); $this->setSettingsFromParams($oSettings, 'ShowImages', 'bool'); $this->setSettingsFromParams($oSettings, 'ContactsAutosave', 'bool'); - $this->setSettingsFromParams($oSettings, 'InterfaceAnimation', 'string', function ($sValue) { - return (\in_array($sValue, - array(\RainLoop\Enumerations\InterfaceAnimation::NONE, - \RainLoop\Enumerations\InterfaceAnimation::NORMAL, - \RainLoop\Enumerations\InterfaceAnimation::FULL)) ? $sValue : - \RainLoop\Enumerations\InterfaceAnimation::FULL); - }); $this->setSettingsFromParams($oSettings, 'DesktopNotifications', 'bool'); - $this->setSettingsFromParams($oSettings, 'UseThreads', 'bool'); - $this->setSettingsFromParams($oSettings, 'ReplySameFolder', 'bool'); + $this->setSettingsFromParams($oSettings, 'SoundNotification', 'bool'); $this->setSettingsFromParams($oSettings, 'UseCheckboxesInList', 'bool'); + $this->setSettingsFromParams($oSettings, 'AutoLogout', 'int'); - $this->setSettingsFromParams($oSettings, 'DefaultIdentityID', 'string'); - $this->setSettingsFromParams($oSettings, 'DisplayName', 'string'); - $this->setSettingsFromParams($oSettings, 'ReplyTo', 'string'); - $this->setSettingsFromParams($oSettings, 'Signature', 'string'); - $this->setSettingsFromParams($oSettings, 'SignatureToAll', 'bool'); $this->setSettingsFromParams($oSettings, 'EnableTwoFactor', 'bool'); + $this->setSettingsFromParams($oSettingsLocal, 'UseThreads', 'bool'); + $this->setSettingsFromParams($oSettingsLocal, 'ReplySameFolder', 'bool'); + return $this->DefaultResponse(__FUNCTION__, - $this->SettingsProvider()->Save($oAccount, $oSettings)); + $this->SettingsProvider()->Save($oAccount, $oSettings) && + $this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal)); } /** @@ -3664,6 +4854,25 @@ class Actions return $this->DefaultResponse(__FUNCTION__, true); } + /** + * @return array + */ + public function DoWelcomeClose() + { + $oAccount = $this->getAccountFromToken(); + if ($oAccount && !$oAccount->IsAdditionalAccount()) + { + $oSettings = $this->SettingsProvider()->Load($oAccount); + $oSettings->SetConf('LastWelcomePage', + $this->Config()->Get('branding', 'welcome_page_url', '')); + + return $this->DefaultResponse(__FUNCTION__, + $this->SettingsProvider()->Save($oAccount, $oSettings)); + } + + return $this->FalseResponse(__FUNCTION__); + } + /** * @return array */ @@ -3732,7 +4941,7 @@ class Actions /** * @staticvar array $aCache - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return array */ @@ -3756,7 +4965,7 @@ class Actions 'Send Mails' => \MailSo\Imap\Enumerations\FolderType::SENT, 'Drafts' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, - + 'Draft' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, 'Draft Mail' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, 'Draft Mails' => \MailSo\Imap\Enumerations\FolderType::DRAFTS, @@ -3789,7 +4998,7 @@ class Actions } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param \MailSo\Mail\FolderCollection $oFolders * @param array $aResult * @param bool $bListFolderTypes = true @@ -3801,7 +5010,7 @@ class Actions $aFolders =& $oFolders->GetAsArray(); if (\is_array($aFolders) && 0 < \count($aFolders)) { - if ($bListFolderTypes && false) + if ($bListFolderTypes) { foreach ($aFolders as $oFolder) { @@ -3817,7 +5026,7 @@ class Actions $aResult[$iFolderListType] = $oFolder->FullNameRaw(); } } - + foreach ($aFolders as $oFolder) { $oSub = $oFolder->SubFolders(); @@ -3833,8 +5042,8 @@ class Actions { $sName = $oFolder->Name(); $sFullName = $oFolder->FullName(); - - if (isset($aMap[$sName], $aMap[$sFullName])) + + if (isset($aMap[$sName]) || isset($aMap[$sFullName])) { $iFolderType = isset($aMap[$sName]) ? $aMap[$sName] : $aMap[$sFullName]; if (!isset($aResult[$iFolderType]) && \in_array($iFolderType, array( @@ -3849,7 +5058,7 @@ class Actions } } } - + foreach ($aFolders as $oFolder) { $oSub = $oFolder->SubFolders(); @@ -3867,6 +5076,9 @@ class Actions */ public function DoFolders() { +// \sleep(1); +// throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::MailServerError); + $sRawKey = (string) $this->GetActionParam('RawKey', ''); if (0 < \strlen($sRawKey)) { @@ -3881,7 +5093,8 @@ class Actions if (null === $oFolderCollection) { $oFolderCollection = $this->MailClient()->Folders('', '*', - !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true) + !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true), + (int) $this->Config()->Get('labs', 'imap_folder_list_limit', 200) ); } @@ -3900,8 +5113,7 @@ class Actions $sNamespace = $oFolderCollection->GetNamespace(); $sParent = empty($sNamespace) ? '' : \substr($sNamespace, 0, -1); - $oInboxFolder = $oFolderCollection->GetByFullNameRaw('INBOX'); - $sDelimiter = $oInboxFolder ? $oInboxFolder->Delimiter() : '/'; + $sDelimiter = $oFolderCollection->FindDelimiter(); $aList = array(); $aMap = $this->systemFoldersNames($oAccount); @@ -3951,16 +5163,27 @@ class Actions } } - try + $sFullNameToCheck = \MailSo\Base\Utils::ConvertEncoding($mFolderNameToCreate, + \MailSo\Base\Enumerations\Charset::UTF_8, \MailSo\Base\Enumerations\Charset::UTF_7_IMAP); + + if (0 < \strlen(\trim($sParent))) { - if ($this->MailClient()->FolderCreate($mFolderNameToCreate, $sParent)) - { - $bDoItAgain = true; - } + $sFullNameToCheck = $sParent.$sDelimiter.$sFullNameToCheck; } - catch (\Exception $oException) + + if (!$oFolderCollection->GetByFullNameRaw($sFullNameToCheck)) { - $this->Logger()->WriteException($oException); + try + { + if ($this->MailClient()->FolderCreate($mFolderNameToCreate, $sParent, true, $sDelimiter)) + { + $bDoItAgain = true; + } + } + catch (\Exception $oException) + { + $this->Logger()->WriteException($oException); + } } } } @@ -3969,7 +5192,8 @@ class Actions if ($bDoItAgain) { $oFolderCollection = $this->MailClient()->Folders('', '*', - !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true) + !!$this->Config()->Get('labs', 'use_imap_list_subscribe', true), + (int) $this->Config()->Get('labs', 'imap_folder_list_limit', 200) ); if ($oFolderCollection) @@ -3999,6 +5223,9 @@ class Actions */ public function DoFolderCreate() { +// \sleep(1); +// throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantCreateFolder); + $this->initMailClientConnection(); try @@ -4078,6 +5305,9 @@ class Actions */ public function DoFolderDelete() { +// \sleep(1); +// throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantRenameFolder); + $this->initMailClientConnection(); $sFolderFullNameRaw = $this->GetActionParam('Folder', ''); @@ -4151,8 +5381,9 @@ class Actions try { $aInboxInformation = $this->MailClient()->FolderInformation( - $sFolder, $sPrevUidNext, $aFlagsFilteredUids, $this->cacherForUids() + $sFolder, $sPrevUidNext, $aFlagsFilteredUids ); + if (\is_array($aInboxInformation) && isset($aInboxInformation['Flags']) && \is_array($aInboxInformation['Flags'])) { foreach ($aInboxInformation['Flags'] as $iUid => $aFlags) @@ -4162,6 +5393,7 @@ class Actions 'IsSeen' => in_array('\\seen', $aLowerFlags), 'IsFlagged' => in_array('\\flagged', $aLowerFlags), 'IsAnswered' => in_array('\\answered', $aLowerFlags), + 'IsDeleted' => in_array('\\deleted', $aLowerFlags), 'IsForwarded' => 0 < strlen($sForwardedFlag) && in_array(strtolower($sForwardedFlag), $aLowerFlags), 'IsReadReceipt' => 0 < strlen($sReadReceiptFlag) && in_array(strtolower($sReadReceiptFlag), $aLowerFlags) ); @@ -4205,10 +5437,7 @@ class Actions { try { - $aInboxInformation = $this->MailClient()->FolderInformation( - $sFolder, '', array(), $this->cacherForUids() - ); - + $aInboxInformation = $this->MailClient()->FolderInformation($sFolder, '', array()); if (\is_array($aInboxInformation) && isset($aInboxInformation['Folder'])) { $aResult['List'][] = $aInboxInformation; @@ -4225,6 +5454,45 @@ class Actions return $this->DefaultResponse(__FUNCTION__, $aResult); } + /** + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoMessageThreadsFromCache() + { + $sFolder = $this->GetActionParam('Folder', ''); + $sFolderHash = $this->GetActionParam('FolderHash', ''); + $sUid = (string) $this->GetActionParam('Uid', ''); + + if (0 === \strlen($sFolder) || 0 === \strlen($sUid)) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument); + } + + $aResult = array( + 'Folder' => $sFolder, + 'Uid' => $sUid, + 'FolderHash' => $sFolderHash, + 'ThreadUids' => null + ); + + $oCache = $this->cacherForUids(); + if ($oCache && $this->Config()->Get('labs', 'use_imap_thread', false)) + { + $aThreadUids = $this->MailClient()->MessageThreadUidsFromCache( + $sFolder, $sFolderHash, $sUid, $oCache + ); + + if (\is_array($aThreadUids) && 1 < \count($aThreadUids)) + { + $aResult['ThreadUids'] = $aThreadUids; + } + } + + return $this->DefaultResponse(__FUNCTION__, $aResult); + } + /** * @return array * @@ -4232,7 +5500,7 @@ class Actions */ public function DoMessageList() { -// sleep(5); +// \sleep(1); // throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList); $sFolder = ''; @@ -4241,12 +5509,11 @@ class Actions $sSearch = ''; $sUidNext = ''; $bUseThreads = false; - $sExpandedThreadUid = ''; $sRawKey = $this->GetActionParam('RawKey', ''); $aValues = $this->getDecodedClientRawKeyValue($sRawKey, 9); - - if (is_array($aValues) && 9 === count($aValues)) + + if (\is_array($aValues) && 9 === \count($aValues)) { $sFolder =(string) $aValues[0]; $iOffset = (int) $aValues[1]; @@ -4254,7 +5521,6 @@ class Actions $sSearch = (string) $aValues[3]; $sUidNext = (string) $aValues[6]; $bUseThreads = (bool) $aValues[7]; - $sExpandedThreadUid = (string) $aValues[8]; $this->verifyCacheByKey($sRawKey); } @@ -4266,7 +5532,6 @@ class Actions $sSearch = $this->GetActionParam('Search', ''); $sUidNext = $this->GetActionParam('UidNext', ''); $bUseThreads = '1' === (string) $this->GetActionParam('UseThreads', '0'); - $sExpandedThreadUid = (string) $this->GetActionParam('ExpandedThreadUid', ''); } if (0 === strlen($sFolder)) @@ -4281,29 +5546,13 @@ class Actions if (!$this->Config()->Get('labs', 'use_imap_thread', false)) { $bUseThreads = false; - $sExpandedThreadUid = ''; - } - - $aExpandedThreadUid = array(); - if (0 < \strlen($sExpandedThreadUid)) - { - $aExpandedThreadUid = \explode(',', $sExpandedThreadUid); - - $aExpandedThreadUid = \array_map(function ($sValue) { - $sValue = \trim($sValue); - return is_numeric($sValue) ? (int) $sValue : 0; - }, $aExpandedThreadUid); - - $aExpandedThreadUid = \array_filter($aExpandedThreadUid, function ($iValue) { - return 0 < $iValue; - }); } $oMessageList = $this->MailClient()->MessageList( $sFolder, $iOffset, $iLimit, $sSearch, $sUidNext, $this->cacherForUids(), !!$this->Config()->Get('labs', 'use_imap_sort', false), - $bUseThreads, $aExpandedThreadUid, + $bUseThreads, !!$this->Config()->Get('labs', 'use_imap_esearch_esort', false) ); } @@ -4321,20 +5570,56 @@ class Actions } /** - * @param \RainLoop\Account $oAccount + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoMessageListSimple() + { +// \sleep(2); +// throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList); + + $sFolder = $this->GetActionParam('Folder', ''); + $aUids = $this->GetActionParam('Uids', null); + + if (0 === \strlen($sFolder) || !\is_array($aUids)) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument); + } + + $this->initMailClientConnection(); + + $aMessageList = array(); + + try + { + $aMessageList = $this->MailClient()->MessageListSimple($sFolder, $aUids); + } + catch (\Exception $oException) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList, $oException); + } + + return $this->DefaultResponse(__FUNCTION__, $aMessageList); + } + + /** + * @param \RainLoop\Model\Account $oAccount * @param bool $bWithDraftInfo = true * * @return \MailSo\Mime\Message */ private function buildMessage($oAccount, $bWithDraftInfo = true) { - $sFrom = $this->GetActionParam('From', ''); + $sIdentityID = $this->GetActionParam('IdentityID', ''); $sTo = $this->GetActionParam('To', ''); $sCc = $this->GetActionParam('Cc', ''); $sBcc = $this->GetActionParam('Bcc', ''); + $sReplyTo = $this->GetActionParam('ReplyTo', ''); $sSubject = $this->GetActionParam('Subject', ''); $bTextIsHtml = '1' === $this->GetActionParam('TextIsHtml', '0'); $bReadReceiptRequest = '1' === $this->GetActionParam('ReadReceiptRequest', '0'); + $bMarkAsImportant = '1' === $this->GetActionParam('MarkAsImportant', '0'); $sText = $this->GetActionParam('Text', ''); $aAttachments = $this->GetActionParam('Attachments', null); @@ -4347,26 +5632,23 @@ class Actions $oMessage->SetXMailer('RainLoop/'.APP_VERSION); - $oSettings = $this->SettingsProvider()->Load($oAccount); - - if ($this->Config()->Get('webmail', 'allow_identities', true)) + $oFromIdentity = $this->GetIdentityByID($oAccount, $sIdentityID); + if ($oFromIdentity) { - $oMessage->SetFrom(\MailSo\Mime\Email::Parse($sFrom)); + $oMessage->SetFrom(\MailSo\Mime\Email::NewInstance( + $oFromIdentity->Email(), $oFromIdentity->Name())); } else { - $sDisplayName = \trim($oSettings->GetConf('DisplayName', '')); - $sReplyTo = \trim($oSettings->GetConf('ReplyTo', '')); + $oMessage->SetFrom(\MailSo\Mime\Email::Parse($oAccount->Email())); + } - $oMessage->SetFrom(\MailSo\Mime\Email::NewInstance($oAccount->Email(), $sDisplayName)); - - if (!empty($sReplyTo)) + if (!empty($sReplyTo)) + { + $oReplyTo = \MailSo\Mime\EmailCollection::NewInstance($sReplyTo); + if ($oReplyTo && 0 < $oReplyTo->Count()) { - $oReplyTo = \MailSo\Mime\EmailCollection::NewInstance($sReplyTo); - if ($oReplyTo && $oReplyTo->Count()) - { - $oMessage->SetReplyTo($oReplyTo); - } + $oMessage->SetReplyTo($oReplyTo); } } @@ -4375,6 +5657,11 @@ class Actions $oMessage->SetReadReceipt($oAccount->Email()); } + if ($bMarkAsImportant) + { + $oMessage->SetPriority(\MailSo\Mime\Enumerations\MessagePriority::HIGH); + } + $oMessage->SetSubject($sSubject); $oToEmails = \MailSo\Mime\EmailCollection::NewInstance($sTo); @@ -4488,7 +5775,28 @@ class Actions } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount + * + * @return void + */ + private function deleteMessageAttachmnets($oAccount) + { + $aAttachments = $this->GetActionParam('Attachments', null); + + if (\is_array($aAttachments)) + { + foreach (\array_keys($aAttachments) as $sTempName) + { + if ($this->FilesProvider()->FileExists($oAccount, $sTempName)) + { + $this->FilesProvider()->Clear($oAccount, $sTempName); + } + } + } + } + + /** + * @param \RainLoop\Model\Account $oAccount * * @return \MailSo\Mime\Message */ @@ -4498,7 +5806,9 @@ class Actions $sSubject = $this->GetActionParam('Subject', ''); $sText = $this->GetActionParam('Text', ''); - if (empty($sReadReceipt) || empty($sSubject) || empty($sText)) + $oIdentity = $this->GetAccountIdentity($oAccount); + + if (empty($sReadReceipt) || empty($sSubject) || empty($sText) || !$oIdentity) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::UnknownError); } @@ -4508,13 +5818,9 @@ class Actions $oMessage->SetXMailer('RainLoop/'.APP_VERSION); - $oSettings = $this->SettingsProvider()->Load($oAccount); - - $sDisplayName = \trim($oSettings->GetConf('DisplayName', '')); - $sReplyTo = \trim($oSettings->GetConf('ReplyTo', '')); - - $oMessage->SetFrom(\MailSo\Mime\Email::NewInstance($oAccount->Email(), $sDisplayName)); + $oMessage->SetFrom(\MailSo\Mime\Email::NewInstance($oIdentity->Email(), $oIdentity->Name())); + $sReplyTo = $oIdentity->ReplyTo(); if (!empty($sReplyTo)) { $oReplyTo = \MailSo\Mime\EmailCollection::NewInstance($sReplyTo); @@ -4609,74 +5915,99 @@ class Actions return $this->DefaultResponse(__FUNCTION__, $mResult); } - private function smtpSendMessage($oAccount, $oMessage, $rMessageStream, $bAddHiddenRcpt = true) + /** + * + * @param \RainLoop\Model\Account $oAccount + * @param \MailSo\Mime\Message $oMessage + * @param resource $rMessageStream + * @param bool $bDsn = false + * @param bool $bAddHiddenRcpt = true + * + * @throws \RainLoop\Exceptions\ClientException + * @throws \MailSo\Net\Exceptions\ConnectionException + */ + private function smtpSendMessage($oAccount, $oMessage, + &$rMessageStream, &$iMessageStreamSize, $bDsn = false, $bAddHiddenRcpt = true) { $oRcpt = $oMessage->GetRcpt(); if ($oRcpt && 0 < $oRcpt->Count()) { + $this->Plugins()->RunHook('filter.smtp-message-stream', + array($oAccount, &$rMessageStream, &$iMessageStreamSize)); + $this->Plugins()->RunHook('filter.message-rcpt', array($oAccount, &$oRcpt)); try { - $oSmtpClient = \MailSo\Smtp\SmtpClient::NewInstance()->SetLogger($this->Logger()); - $oFrom = $oMessage->GetFrom(); $sFrom = $oFrom instanceof \MailSo\Mime\Email ? $oFrom->GetEmail() : ''; + $sFrom = empty($sFrom) ? $oAccount->Email() : $sFrom; - $aSmtpCredentials = array( - 'Ehlo' => \MailSo\Smtp\SmtpClient::EhloHelper(), - 'Host' => $oAccount->Domain()->OutHost(\MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email())), - 'Port' => $oAccount->Domain()->OutPort(), - 'Secure' => $oAccount->Domain()->OutSecure(), - 'UseAuth' => $oAccount->Domain()->OutAuth(), - 'From' => empty($sFrom) ? $oAccount->Email() : $sFrom, - 'Login' => $oAccount->OutLogin(), - 'Password' => $oAccount->Password(), - 'HiddenRcpt' => array() - ); + $this->Plugins()->RunHook('filter.smtp-from', array($oAccount, $oMessage, &$sFrom)); - $this->Plugins()->RunHook('filter.smtp-credentials', array($oAccount, &$aSmtpCredentials)); - - if (!$bAddHiddenRcpt) + $aHiddenRcpt = array(); + if ($bAddHiddenRcpt) { - $aSmtpCredentials['HiddenRcpt'] = array(); + $this->Plugins()->RunHook('filter.smtp-hidden-rcpt', array($oAccount, $oMessage, &$aHiddenRcpt)); } - $bHookConnect = $bHookAuth = $bHookFrom = $bHookFrom = $bHookTo = $bHookData = $bHookLogoutAndDisconnect = false; - $this->Plugins()->RunHook('filter.smtp-connect', array($oAccount, $aSmtpCredentials, - &$oSmtpClient, $oMessage, &$oRcpt, - &$bHookConnect, &$bHookAuth, &$bHookFrom, &$bHookTo, &$bHookData, &$bHookLogoutAndDisconnect)); + $bUsePhpMail = $oAccount->Domain()->OutUsePhpMail(); - if (!$bHookConnect) - { - $oSmtpClient->Connect($aSmtpCredentials['Host'], $aSmtpCredentials['Port'], - $aSmtpCredentials['Ehlo'], $aSmtpCredentials['Secure']); - } + $oSmtpClient = \MailSo\Smtp\SmtpClient::NewInstance()->SetLogger($this->Logger()); - if (!$bHookAuth) + $bLoggined = $oAccount->OutConnectAndLoginHelper($this->Plugins(), $oSmtpClient, $this->Config(), $bUsePhpMail); + + if ($bUsePhpMail) { - if ($aSmtpCredentials['UseAuth']) + if (\MailSo\Base\Utils::FunctionExistsAndEnabled('mail')) { - $oSmtpClient->Login($aSmtpCredentials['Login'], $aSmtpCredentials['Password']); + $aToCollection = $oMessage->GetTo(); + if ($aToCollection && $oFrom) + { + $sRawBody = @\stream_get_contents($rMessageStream); + if (!empty($sRawBody)) + { + $sMailTo = \trim($aToCollection->ToString(true)); + $sMailSubject = \trim($oMessage->GetSubject()); + $sMailSubject = 0 === \strlen($sMailSubject) ? '' : \MailSo\Base\Utils::EncodeUnencodedValue( + \MailSo\Base\Enumerations\Encoding::BASE64_SHORT, $sMailSubject); + + $sMailHeaders = $sMailBody = ''; + list($sMailHeaders, $sMailBody) = \explode("\r\n\r\n", $sRawBody, 2); + unset($sRawBody); + +// $this->Logger()->WriteDump(array( +// $sMailTo, $sMailSubject, $sMailBody, $sMailHeaders +// )); + + if (!\mail($sMailTo, $sMailSubject, $sMailBody, $sMailHeaders/*, '-f'.$oFrom->GetEmail()*/)) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSendMessage); + } + } + } + } + else + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSendMessage); } } - - if (!$bHookFrom) + else if ($oSmtpClient->IsConnected()) { - $oSmtpClient->MailFrom($aSmtpCredentials['From']); - } + if (!empty($sFrom)) + { + $oSmtpClient->MailFrom($sFrom, '', $bDsn); + } - if (!$bHookTo) - { $aRcpt =& $oRcpt->GetAsArray(); foreach ($aRcpt as /* @var $oEmail \MailSo\Mime\Email */ $oEmail) { - $oSmtpClient->Rcpt($oEmail->GetEmail()); + $oSmtpClient->Rcpt($oEmail->GetEmail(), $bDsn); } - if (isset($aSmtpCredentials['HiddenRcpt']) && is_array($aSmtpCredentials['HiddenRcpt'])) + if ($bAddHiddenRcpt && \is_array($aHiddenRcpt) && 0 < \count($aHiddenRcpt)) { - foreach ($aSmtpCredentials['HiddenRcpt'] as $sEmail) + foreach ($aHiddenRcpt as $sEmail) { if (\preg_match('/^[^@\s]+@[^@\s]+$/', $sEmail)) { @@ -4684,16 +6015,15 @@ class Actions } } } - } - if (!$bHookData) - { $oSmtpClient->DataWithStream($rMessageStream); - } - if (!$bHookLogoutAndDisconnect) - { - $oSmtpClient->LogoutAndDisconnect(); + if ($bLoggined) + { + $oSmtpClient->Logout(); + } + + $oSmtpClient->Disconnect(); } } catch (\MailSo\Net\Exceptions\ConnectionException $oException) @@ -4736,10 +6066,13 @@ class Actions { $oAccount = $this->initMailClientConnection(); + $oConfig = $this->Config(); + $sDraftFolder = $this->GetActionParam('MessageFolder', ''); $sDraftUid = $this->GetActionParam('MessageUid', ''); $sSentFolder = $this->GetActionParam('SentFolder', ''); $aDraftInfo = $this->GetActionParam('DraftInfo', null); + $bDsn = '1' === (string) $this->GetActionParam('Dsn', '0'); $oMessage = $this->buildMessage($oAccount, false); @@ -4760,9 +6093,9 @@ class Actions if (false !== $iMessageStreamSize) { - $this->smtpSendMessage($oAccount, $oMessage, $rMessageStream); + $this->smtpSendMessage($oAccount, $oMessage, $rMessageStream, $iMessageStreamSize, $bDsn, true); - if (is_array($aDraftInfo) && 3 === count($aDraftInfo)) + if (\is_array($aDraftInfo) && 3 === \count($aDraftInfo)) { $sDraftInfoType = $aDraftInfo[0]; $sDraftInfoUid = $aDraftInfo[1]; @@ -4804,10 +6137,14 @@ class Actions \rewind($rMessageStream); } + $this->Plugins()->RunHook('filter.sent-message-stream', + array($oAccount, &$rMessageStream, &$iMessageStreamSize)); + $this->MailClient()->MessageAppendStream( $rMessageStream, $iMessageStreamSize, $sSentFolder, array( \MailSo\Imap\Enumerations\MessageFlag::SEEN - )); + ) + ); } else { @@ -4816,12 +6153,15 @@ class Actions $iAppendMessageStreamSize = \MailSo\Base\Utils::MultipleStreamWriter( $oMessage->ToStream(false), array($rAppendMessageStream), 8192, true, true, true); + $this->Plugins()->RunHook('filter.sent-message-stream', + array($oAccount, &$rAppendMessageStream, &$iAppendMessageStreamSize)); + $this->MailClient()->MessageAppendStream( $rAppendMessageStream, $iAppendMessageStreamSize, $sSentFolder, array( \MailSo\Imap\Enumerations\MessageFlag::SEEN )); - if (is_resource($rAppendMessageStream)) + if (\is_resource($rAppendMessageStream)) { @fclose($rAppendMessageStream); } @@ -4838,7 +6178,9 @@ class Actions @\fclose($rMessageStream); } - if (0 < strlen($sDraftFolder) && 0 < strlen($sDraftUid)) + $this->deleteMessageAttachmnets($oAccount); + + if (0 < \strlen($sDraftFolder) && 0 < \strlen($sDraftUid)) { try { @@ -4888,7 +6230,9 @@ class Actions $oSettings = $this->SettingsProvider()->Load($oAccount); $this->AddressBookProvider($oAccount)->IncFrec( - $oAccount->ParentEmailHelper(), \array_values($aArrayToFrec), !!$oSettings->GetConf('ContactsAutosave', true)); + $oAccount->ParentEmailHelper(), \array_values($aArrayToFrec), + !!$oSettings->GetConf('ContactsAutosave', + !!$oConfig->Get('defaults', 'contacts_autosave', true))); } } } @@ -4923,7 +6267,7 @@ class Actions if (false !== $iMessageStreamSize) { - $this->smtpSendMessage($oAccount, $oMessage, $rMessageStream); + $this->smtpSendMessage($oAccount, $oMessage, $rMessageStream, $iMessageStreamSize, false, false); if (\is_resource($rMessageStream)) { @@ -4938,7 +6282,7 @@ class Actions $sFolderFullName = $this->GetActionParam('MessageFolder', ''); $sUid = $this->GetActionParam('MessageUid', ''); - $this->Cacher()->Set(\RainLoop\KeyPathHelper::ReadReceiptCache($oAccount->Email(), $sFolderFullName, $sUid), '1'); + $this->Cacher($oAccount)->Set(\RainLoop\KeyPathHelper::ReadReceiptCache($oAccount->Email(), $sFolderFullName, $sUid), '1'); if (0 < \strlen($sFolderFullName) && 0 < \strlen($sUid)) { @@ -4992,8 +6336,7 @@ class Actions { $mResult = null; - $sData = $this->StorageProvider()->Get( - $oAccount->ParentEmailHelper(), + $sData = $this->StorageProvider()->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'contacts_sync' ); @@ -5035,8 +6378,7 @@ class Actions $mData = $this->getContactsSyncData($oAccount); - $bResult = $this->StorageProvider()->Put( - $oAccount->ParentEmailHelper(), + $bResult = $this->StorageProvider()->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'contacts_sync', \RainLoop\Utils::EncodeKeyValues(array( @@ -5079,8 +6421,10 @@ class Actions return $this->TrueResponse(__FUNCTION__); } - private function getTwoFactorInfo($sEmail, $bRemoveSecret = false) + private function getTwoFactorInfo($oAccount, $bRemoveSecret = false) { + $sEmail = $oAccount->ParentEmailHelper(); + $mData = null; $aResult = array( @@ -5096,9 +6440,9 @@ class Actions { $aResult['User'] = $sEmail; - $sData = $this->StorageProvider()->Get(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::TwoFactorAuthUserData($sEmail) + $sData = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor' ); if ($sData) @@ -5142,21 +6486,21 @@ class Actions } /** - * @param string $sEmail + * @param \RainLoop\Model\Account $oAccount * @param string $sCode * * @return bool */ - private function removeBackupCodeFromTwoFactorInfo($sEmail, $sCode) + private function removeBackupCodeFromTwoFactorInfo($oAccount, $sCode) { - if (empty($sEmail) || empty($sCode)) + if (!$oAccount || empty($sCode)) { return false; } - $sData = $this->StorageProvider()->Get(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::TwoFactorAuthUserData($sEmail) + $sData = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor' ); if ($sData) @@ -5170,9 +6514,9 @@ class Actions $mData['BackupCodes'] = \trim(\preg_replace('/[^\d]+/', ' ', $sBackupCodes)); - return $this->StorageProvider()->Put(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::TwoFactorAuthUserData($sEmail), + return $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor', \RainLoop\Utils::EncodeKeyValues($mData) ); } @@ -5186,15 +6530,16 @@ class Actions */ public function DoGetTwoFactorInfo() { - if (!$this->TwoFactorAuthProvider()->IsActive()) + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider()->IsActive() || + !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } - $oAccount = $this->getAccountFromToken(); - return $this->DefaultResponse(__FUNCTION__, - $this->getTwoFactorInfo($oAccount->ParentEmailHelper(), true)); + $this->getTwoFactorInfo($oAccount, true)); } /** @@ -5202,12 +6547,14 @@ class Actions */ public function DoCreateTwoFactorSecret() { - if (!$this->TwoFactorAuthProvider()->IsActive()) + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider()->IsActive() || + !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } - $oAccount = $this->getAccountFromToken(); $sEmail = $oAccount->ParentEmailHelper(); $sSecret = $this->TwoFactorAuthProvider()->CreateSecret(); @@ -5218,9 +6565,9 @@ class Actions $aCodes[] = \rand(100000000, 900000000); } - $this->StorageProvider()->Put(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::TwoFactorAuthUserData($sEmail), + $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor', \RainLoop\Utils::EncodeKeyValues(array( 'User' => $sEmail, 'Enable' => false, @@ -5231,7 +6578,7 @@ class Actions \sleep(1); return $this->DefaultResponse(__FUNCTION__, - $this->getTwoFactorInfo($sEmail)); + $this->getTwoFactorInfo($oAccount)); } /** @@ -5239,15 +6586,15 @@ class Actions */ public function DoShowTwoFactorSecret() { - if (!$this->TwoFactorAuthProvider()->IsActive()) + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider()->IsActive() || + !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } - $oAccount = $this->getAccountFromToken(); - $sEmail = $oAccount->ParentEmailHelper(); - - $aResult = $this->getTwoFactorInfo($sEmail); + $aResult = $this->getTwoFactorInfo($oAccount); if (\is_array($aResult)) { unset($aResult['BackupCodes']); @@ -5261,21 +6608,23 @@ class Actions */ public function DoEnableTwoFactor() { - if (!$this->TwoFactorAuthProvider()->IsActive()) + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider()->IsActive() || + !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } - $oAccount = $this->getAccountFromToken(); $sEmail = $oAccount->ParentEmailHelper(); $bResult = false; - $mData = $this->getTwoFactorInfo($sEmail); + $mData = $this->getTwoFactorInfo($oAccount); if (isset($mData['Secret'], $mData['BackupCodes'])) { - $bResult = $this->StorageProvider()->Put(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::TwoFactorAuthUserData($sEmail), + $bResult = $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor', \RainLoop\Utils::EncodeKeyValues(array( 'User' => $sEmail, 'Enable' => '1' === \trim($this->GetActionParam('Enable', '0')), @@ -5293,17 +6642,23 @@ class Actions */ public function DoTestTwoFactorInfo() { - if (!$this->TwoFactorAuthProvider()->IsActive()) + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider()->IsActive() || + !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } - $oAccount = $this->getAccountFromToken(); - $sCode = \trim($this->GetActionParam('Code', '')); - $oData = $this->getTwoFactorInfo($oAccount->ParentEmailHelper()); - $sSecret = !empty($oData['Secret']) ? $oData['Secret'] : ''; + $aData = $this->getTwoFactorInfo($oAccount); + $sSecret = !empty($aData['Secret']) ? $aData['Secret'] : ''; + +// $this->Logger()->WriteDump(array( +// $sCode, $sSecret, $aData, +// $this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode) +// )); \sleep(1); return $this->DefaultResponse(__FUNCTION__, @@ -5315,20 +6670,21 @@ class Actions */ public function DoClearTwoFactorInfo() { - if (!$this->TwoFactorAuthProvider()->IsActive()) + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider()->IsActive() || + !$this->GetCapa(false, \RainLoop\Enumerations\Capa::TWO_FACTOR, $oAccount)) { return $this->FalseResponse(__FUNCTION__); } - $oAccount = $this->getAccountFromToken(); - - $this->StorageProvider()->Clear(null, - \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, - \RainLoop\KeyPathHelper::TwoFactorAuthUserData($oAccount->ParentEmailHelper()) + $this->StorageProvider()->Clear($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor' ); return $this->DefaultResponse(__FUNCTION__, - $this->getTwoFactorInfo($oAccount->ParentEmailHelper(), true)); + $this->getTwoFactorInfo($oAccount, true)); } /** @@ -5346,7 +6702,6 @@ class Actions $iResultCount = 0; $mResult = array(); - $aTags = array(); $oAbp = $this->AddressBookProvider($oAccount); if ($oAbp->IsActive()) @@ -5354,8 +6709,6 @@ class Actions $iResultCount = 0; $mResult = $oAbp->GetContacts($oAccount->ParentEmailHelper(), $iOffset, $iLimit, $sSearch, $iResultCount); - - $aTags = $oAbp->GetContactTags($oAccount->ParentEmailHelper()); } return $this->DefaultResponse(__FUNCTION__, array( @@ -5363,8 +6716,7 @@ class Actions 'Limit' => $iLimit, 'Count' => $iResultCount, 'Search' => $sSearch, - 'List' => $mResult, - 'Tags' => $aTags + 'List' => $mResult )); } @@ -5404,10 +6756,6 @@ class Actions if ($oAddressBookProvider && $oAddressBookProvider->IsActive() && 0 < \strlen($sRequestUid)) { $sUid = \trim($this->GetActionParam('Uid', '')); - $sTags = \trim($this->GetActionParam('Tags', '')); - $aTags = \explode(',', $sTags); - $aTags = \array_map('trim', $aTags); - $aTags = \array_unique($aTags); $oContact = null; if (0 < \strlen($sUid)) @@ -5424,7 +6772,6 @@ class Actions } } - $oContact->Tags = $aTags; $oContact->Properties = array(); $aProperties = $this->GetActionParam('Properties', array()); if (\is_array($aProperties)) @@ -5482,18 +6829,39 @@ class Actions if ($iLimit > \count($aResult) && 0 < \strlen($sQuery)) { - // Address Book - $oAddressBookProvider = $this->AddressBookProvider($oAccount); - if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) + try { - $aSuggestions = $oAddressBookProvider->GetSuggestions($oAccount->ParentEmailHelper(), $sQuery, $iLimit); - if (0 === \count($aResult)) + // Address Book + $oAddressBookProvider = $this->AddressBookProvider($oAccount); + if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) { - $aResult = $aSuggestions; + $aSuggestions = $oAddressBookProvider->GetSuggestions($oAccount->ParentEmailHelper(), $sQuery, $iLimit); + if (0 === \count($aResult)) + { + $aResult = $aSuggestions; + } + else + { + $aResult = \array_merge($aResult, $aSuggestions); + } } - else + } + catch (\Exception $oException) + { + $this->Logger()->WriteException($oException); + } + } + + if ($iLimit > \count($aResult) && 0 < \strlen($sQuery)) + { + $oSuggestionsProvider = $this->SuggestionsProvider(); + if ($oSuggestionsProvider && $oSuggestionsProvider->IsActive()) + { + $iSuggestionLimit = $iLimit - \count($aResult); + $aSuggestionsProviderResult = $oSuggestionsProvider->Process($oAccount, $sQuery, $iSuggestionLimit); + if (\is_array($aSuggestionsProviderResult) && 0 < \count($aSuggestionsProviderResult)) { - $aResult = \array_merge($aResult, $aSuggestions); + $aResult = \array_merge($aResult, $aSuggestionsProviderResult); } } } @@ -5530,7 +6898,7 @@ class Actions try { - $this->MailClient()->{$sActionFunction}($sFolder, $aFilteredUids, true, $bSetAction); + $this->MailClient()->{$sActionFunction}($sFolder, $aFilteredUids, true, $bSetAction, true); } catch (\Exception $oException) { @@ -5650,9 +7018,10 @@ class Actions try { - $this->MailClient()->MessageDelete($sFolder, $aFilteredUids, true); - - $sHash = $this->MailClient()->FolderHash($sFolder, $this->cacherForUids()); + $this->MailClient()->MessageDelete($sFolder, $aFilteredUids, true, true, + !!$this->Config()->Get('labs', 'use_imap_expunge_all_on_delete', false)); + + $sHash = $this->MailClient()->FolderHash($sFolder); } catch (\Exception $oException) { @@ -5682,10 +7051,12 @@ class Actions try { - $this->MailClient()->MessageMove($sFromFolder, $sToFolder, - $aFilteredUids, true, $this->Config()->Get('labs', 'use_imap_move', true)); + $this->MailClient()->MessageMove($sFromFolder, $sToFolder, $aFilteredUids, true, + !!$this->Config()->Get('labs', 'use_imap_move', true), + !!$this->Config()->Get('labs', 'use_imap_expunge_all_on_delete', false) + ); - $sHash = $this->MailClient()->FolderHash($sFromFolder, $this->cacherForUids()); + $sHash = $this->MailClient()->FolderHash($sFromFolder); } catch (\Exception $oException) { @@ -5719,7 +7090,7 @@ class Actions $this->MailClient()->MessageCopy($sFromFolder, $sToFolder, $aFilteredUids, true); - $sHash = $this->MailClient()->FolderHash($sFromFolder, $this->cacherForUids()); + $sHash = $this->MailClient()->FolderHash($sFromFolder); } catch (\Exception $oException) { @@ -5734,19 +7105,19 @@ class Actions * @param string $sFileName * @param string $sContentType * @param string $sMimeIndex + * @param int $iMaxLength = 250 * * @return string */ - public function MainClearFileName($sFileName, $sContentType, $sMimeIndex) + public function MainClearFileName($sFileName, $sContentType, $sMimeIndex, $iMaxLength = 250) { - $sFileName = 0 === strlen($sFileName) ? preg_replace('/[^a-zA-Z0-9]/', '.', (empty($sMimeIndex) ? '' : $sMimeIndex.'.').$sContentType) : $sFileName; - $sClearedFileName = preg_replace('/[\s]+/', ' ', preg_replace('/[\.]+/', '.', $sFileName)); + $sFileName = 0 === \strlen($sFileName) ? \preg_replace('/[^a-zA-Z0-9]/', '.', (empty($sMimeIndex) ? '' : $sMimeIndex.'.').$sContentType) : $sFileName; + $sClearedFileName = \preg_replace('/[\s]+/', ' ', \preg_replace('/[\.]+/', '.', $sFileName)); $sExt = \MailSo\Base\Utils::GetFileExtension($sClearedFileName); - $iSize = 50; - if ($iSize < strlen($sClearedFileName) - strlen($sExt)) + if (10 < $iMaxLength && $iMaxLength < \strlen($sClearedFileName) - \strlen($sExt)) { - $sClearedFileName = substr($sClearedFileName, 0, $iSize).(empty($sExt) ? '' : '.'.$sExt); + $sClearedFileName = \substr($sClearedFileName, 0, $iMaxLength).(empty($sExt) ? '' : '.'.$sExt); } return \MailSo\Base\Utils::ClearFileName(\MailSo\Base\Utils::Utf8Clear($sClearedFileName)); @@ -6007,39 +7378,24 @@ class Actions public function Upload() { $oAccount = $this->getAccountFromToken(); - $oConfig = $this->Config(); - $sInputName = 'uploader'; $aResponse = array(); - $iError = UploadError::UNKNOWN; - $iSizeLimit = ((int) $oConfig->Get('webmail', 'attachment_size_limit', 0)) * 1024 * 1024; - if ($oAccount) + $aFile = $this->GetActionParam('File', null); + $iError = $this->GetActionParam('Error', \RainLoop\Enumerations\UploadError::UNKNOWN); + + if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) { - $iError = UPLOAD_ERR_OK; - $_FILES = isset($_FILES) ? $_FILES : null; - if (isset($_FILES, $_FILES[$sInputName], $_FILES[$sInputName]['name'], $_FILES[$sInputName]['tmp_name'], $_FILES[$sInputName]['size'])) + $sSavedName = 'upload-post-'.\md5($aFile['name'].$aFile['tmp_name']); + if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) { - $iError = (isset($_FILES[$sInputName]['error'])) ? (int) $_FILES[$sInputName]['error'] : UPLOAD_ERR_OK; - - if (UPLOAD_ERR_OK === $iError && 0 < $iSizeLimit && $iSizeLimit < (int) $_FILES[$sInputName]['size']) - { - $iError = UploadError::CONFIG_SIZE; - } - - $sSavedName = 'upload-post-'.md5($_FILES[$sInputName]['name'].$_FILES[$sInputName]['tmp_name']); - - if (UPLOAD_ERR_OK === $iError) - { - if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $_FILES[$sInputName]['tmp_name'])) - { - $iError = UploadError::ON_SAVING; - } - } - - $sUploadName = $_FILES[$sInputName]['name']; - $iSize = $_FILES[$sInputName]['size']; - $sMimeType = $_FILES[$sInputName]['type']; + $iError = \RainLoop\Enumerations\UploadError::ON_SAVING; + } + else + { + $sUploadName = $aFile['name']; + $iSize = $aFile['size']; + $sMimeType = $aFile['type']; $aResponse['Attachment'] = array( 'Name' => $sUploadName, @@ -6048,20 +7404,13 @@ class Actions 'Size' => (int) $iSize ); } - else if (!isset($_FILES) || !is_array($_FILES) || 0 === count($_FILES)) - { - $iError = UPLOAD_ERR_INI_SIZE; - } - else - { - $iError = UploadError::EMPTY_FILES_DATA; - } } if (UPLOAD_ERR_OK !== $iError) { - $iClientError = UploadClientError::NORMAL; + $iClientError = \RainLoop\Enumerations\UploadClientError::NORMAL; $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); + if (!empty($sError)) { $aResponse['ErrorCode'] = $iClientError; @@ -6073,7 +7422,203 @@ class Actions } /** - * @param \RainLoop\Account $oAccount + * @return array + */ + public function DoClearUserBackground() + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::USER_BACKGROUND, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + $oSettings = $this->SettingsProvider()->Load($oAccount); + if ($oAccount && $oSettings) + { + $this->StorageProvider()->Clear($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'background' + ); + + $oSettings->SetConf('UserBackgroundName', ''); + $oSettings->SetConf('UserBackgroundHash', ''); + } + + return $this->DefaultResponse(__FUNCTION__, $oAccount && $oSettings ? + $this->SettingsProvider()->Save($oAccount, $oSettings) : false); + } + + /** + * @return array + */ + public function UploadBackground() + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->GetCapa(false, \RainLoop\Enumerations\Capa::USER_BACKGROUND, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + $sName = ''; + $sHash = ''; + + $aFile = $this->GetActionParam('File', null); + $iError = $this->GetActionParam('Error', \RainLoop\Enumerations\UploadError::UNKNOWN); + + if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) + { + $sMimeType = \strtolower(\MailSo\Base\Utils::MimeContentType($aFile['name'])); + if (\in_array($sMimeType, array('image/png', 'image/jpg', 'image/jpeg'))) + { + $sSavedName = 'upload-post-'.\md5($aFile['name'].$aFile['tmp_name']); + if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) + { + $iError = \RainLoop\Enumerations\UploadError::ON_SAVING; + } + else + { + $rData = $this->FilesProvider()->GetFile($oAccount, $sSavedName); + if (@\is_resource($rData)) + { + $sData = @\stream_get_contents($rData); + if (!empty($sData) && 0 < \strlen($sData)) + { + $sName = $aFile['name']; + if (empty($sName)) + { + $sName = '_'; + } + + if ($this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'background', + \json_encode(array( + 'Name' => $aFile['name'], + 'ContentType' => $sMimeType, + 'Raw' => \base64_encode($sData) + )) + )) + { + $oSettings = $this->SettingsProvider()->Load($oAccount); + if ($oSettings) + { + $sHash = \MailSo\Base\Utils::Md5Rand($sName.APP_VERSION.APP_SALT); + + $oSettings->SetConf('UserBackgroundName', $sName); + $oSettings->SetConf('UserBackgroundHash', $sHash); + $this->SettingsProvider()->Save($oAccount, $oSettings); + } + } + } + + unset($sData); + } + + if (@\is_resource($rData)) + { + @\fclose($rData); + } + + unset($rData); + } + + $this->FilesProvider()->Clear($oAccount, $sSavedName); + } + else + { + $iError = \RainLoop\Enumerations\UploadError::FILE_TYPE; + } + } + + if (UPLOAD_ERR_OK !== $iError) + { + $iClientError = \RainLoop\Enumerations\UploadClientError::NORMAL; + $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); + + if (!empty($sError)) + { + return $this->FalseResponse(__FUNCTION__, $iClientError, $sError); + } + } + + return $this->DefaultResponse(__FUNCTION__, !empty($sName) && !empty($sHash) ? array( + 'Name' => $sName, + 'Hash' => $sHash + ) : false); + } + + /** + * @return array + */ + public function UploadContacts() + { + $oAccount = $this->getAccountFromToken(); + + $mResponse = false; + + $aFile = $this->GetActionParam('File', null); + $iError = $this->GetActionParam('Error', \RainLoop\Enumerations\UploadError::UNKNOWN); + + if ($oAccount && UPLOAD_ERR_OK === $iError && \is_array($aFile)) + { + $sSavedName = 'upload-post-'.\md5($aFile['name'].$aFile['tmp_name']); + if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $aFile['tmp_name'])) + { + $iError = \RainLoop\Enumerations\UploadError::ON_SAVING; + } + else + { + @\ini_set('auto_detect_line_endings', true); + $mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName); + if ($mData) + { + $sFileStart = @\fread($mData, 20); + \rewind($mData); + + if (false !== $sFileStart) + { + $sFileStart = \trim($sFileStart); + if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) + { + $mResponse = $this->importContactsFromVcfFile($oAccount, $mData); + } + else if (false !== \strpos($sFileStart, ',') || false !== \strpos($sFileStart, ';')) + { + $mResponse = $this->importContactsFromCsvFile($oAccount, $mData, $sFileStart); + } + } + } + + if (\is_resource($mData)) + { + @\fclose($mData); + } + + unset($mData); + $this->FilesProvider()->Clear($oAccount, $sSavedName); + + @\ini_set('auto_detect_line_endings', false); + } + } + + if (UPLOAD_ERR_OK !== $iError) + { + $iClientError = \RainLoop\Enumerations\UploadClientError::NORMAL; + $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); + + if (!empty($sError)) + { + return $this->FalseResponse(__FUNCTION__, $iClientError, $sError); + } + } + + return $this->DefaultResponse(__FUNCTION__, $mResponse); + } + + /** + * @param \RainLoop\Model\Account $oAccount * @param resource $rFile * @param string $sFileStart * @@ -6133,13 +7678,12 @@ class Actions } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param resource $rFile - * @param string $sFileStart * * @return int */ - private function importContactsFromVcfFile($oAccount, $rFile, $sFileStart) + private function importContactsFromVcfFile($oAccount, $rFile) { $iCount = 0; if ($oAccount && \is_resource($rFile)) @@ -6164,102 +7708,6 @@ class Actions return $iCount; } - /** - * @return array - */ - public function UploadContacts() - { - $oAccount = $this->getAccountFromToken(); - $oConfig = $this->Config(); - - $sInputName = 'uploader'; - $mResponse = false; - - $iError = UploadError::UNKNOWN; - $iSizeLimit = ((int) $oConfig->Get('webmail', 'attachment_size_limit', 0)) * 1024 * 1024; - - if ($oAccount) - { - $oAddressBookProvider = $this->AddressBookProvider($oAccount); - if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) - { - $iError = UPLOAD_ERR_OK; - $_FILES = isset($_FILES) ? $_FILES : null; - if (isset($_FILES, $_FILES[$sInputName], $_FILES[$sInputName]['name'], $_FILES[$sInputName]['tmp_name'], $_FILES[$sInputName]['size'])) - { - $iError = (isset($_FILES[$sInputName]['error'])) ? (int) $_FILES[$sInputName]['error'] : UPLOAD_ERR_OK; - - if (UPLOAD_ERR_OK === $iError && 0 < $iSizeLimit && $iSizeLimit < (int) $_FILES[$sInputName]['size']) - { - $iError = UploadError::CONFIG_SIZE; - } - - $sSavedName = 'upload-post-'.md5($_FILES[$sInputName]['name'].$_FILES[$sInputName]['tmp_name']); - - if (UPLOAD_ERR_OK === $iError) - { - if (!$this->FilesProvider()->MoveUploadedFile($oAccount, $sSavedName, $_FILES[$sInputName]['tmp_name'])) - { - $iError = UploadError::ON_SAVING; - } - - @\ini_set('auto_detect_line_endings', true); - $mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName); - if ($mData) - { - $sFileStart = @\fread($mData, 20); - \rewind($mData); - - if (false !== $sFileStart) - { - $sFileStart = \trim($sFileStart); - if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) - { - $mResponse = $this->importContactsFromVcfFile($oAccount, $mData, $sFileStart); - } - else if (false !== \strpos($sFileStart, ',') || false !== \strpos($sFileStart, ';')) - { - $mResponse = $this->importContactsFromCsvFile($oAccount, $mData, $sFileStart); - } - } - } - - if (\is_resource($mData)) - { - @\fclose($mData); - } - - unset($mData); - $this->FilesProvider()->Clear($oAccount, $sSavedName); - - @\ini_set('auto_detect_line_endings', false); - } - } - else if (!isset($_FILES) || !is_array($_FILES) || 0 === count($_FILES)) - { - $iError = UPLOAD_ERR_INI_SIZE; - } - else - { - $iError = UploadError::EMPTY_FILES_DATA; - } - } - } - - if (UPLOAD_ERR_OK !== $iError) - { - $iClientError = UploadClientError::NORMAL; - $sError = $this->getUploadErrorMessageByCode($iError, $iClientError); - - if (!empty($sError)) - { - return $this->FalseResponse(__FUNCTION__, $iClientError, $sError); - } - } - - return $this->DefaultResponse(__FUNCTION__, $mResponse); - } - /** * @return bool * @@ -6286,6 +7734,51 @@ class Actions }, $sFolder, $iUid, true, $sMimeIndex); } + /** + * @return string + */ + public function RawFramedView() + { + $oAccount = $this->getAccountFromToken(false); + if ($oAccount) + { + $sRawKey = (string) $this->GetActionParam('RawKey', ''); + $aParams = $this->GetActionParam('Params', null); + $this->Http()->ServerNoCache(); + + $aData = \RainLoop\Utils::DecodeKeyValues($sRawKey); + if (isset($aParams[0], $aParams[1], $aParams[2]) && + 'Raw' === $aParams[0] && 'FramedView' === $aParams[2] && isset($aData['Framed']) && $aData['Framed'] && $aData['FileName']) + { + if ($this->isFileHasFramedPreview($aData['FileName'])) + { + $sNewSpecAuthToken = $this->GetShortLifeSpecAuthToken(); + if (!empty($sNewSpecAuthToken)) + { + $aParams[1] = '_'.$sNewSpecAuthToken; + $aParams[2] = 'View'; + + \array_shift($aParams); + $sLast = \array_pop($aParams); + + $sUrl = $this->Http()->GetFullUrl().'?/Raw/&q[]=/'.\implode('/', $aParams).'/&q[]=/'.$sLast; + $sFullUrl = 'https://docs.google.com/viewer?embedded=true&url='.\urlencode($sUrl); + + @\header('Content-Type: text/html; charset=utf-8'); + echo ''. + ''. + ''. + ''; + } + } + } + } + + + return true; + + } + /** * @return bool * @@ -6298,7 +7791,7 @@ class Actions $sFolderFullNameRaw = $this->GetActionParam('Folder', ''); $_FILES = isset($_FILES) ? $_FILES : null; - if ($oAccount instanceof \RainLoop\Account && + if ($oAccount instanceof \RainLoop\Model\Account && $this->Config()->Get('labs', 'allow_message_append', false) && isset($_FILES, $_FILES['AppendFile'], $_FILES['AppendFile']['name'], $_FILES['AppendFile']['tmp_name'], $_FILES['AppendFile']['size'])) @@ -6316,6 +7809,8 @@ class Actions $rMessageStream = $this->FilesProvider()->GetFile($oAccount, $sSavedName); $this->MailClient()->MessageAppendStream($rMessageStream, $iMessageStreamSize, $sFolderFullNameRaw); + + $this->FilesProvider()->Clear($oAccount, $sSavedName); } } } @@ -6326,7 +7821,7 @@ class Actions /** * @param bool $bAdmin - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount = null * * @return array */ @@ -6334,24 +7829,37 @@ class Actions { $oConfig = $this->Config(); - $aResult = array( - \RainLoop\Enumerations\Capa::PREM, -// \RainLoop\Enumerations\Capa::FILTERS - ); + $aResult = array(); + + if ($oConfig->Get('capa', 'filters', false)) + { + $aResult[] = \RainLoop\Enumerations\Capa::FILTERS; + if ($bAdmin || ($oAccount && $oAccount->Domain()->UseSieve())) + { + $aResult[] = \RainLoop\Enumerations\Capa::SIEVE; + } + } + +// if ($oConfig->Get('capa', 'templates', true)) +// { +// $aResult[] = \RainLoop\Enumerations\Capa::TEMPLATES; +// } if ($oConfig->Get('webmail', 'allow_additional_accounts', false)) { $aResult[] = \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS; } - if ($oConfig->Get('webmail', 'allow_identities', true)) - { - $aResult[] = \RainLoop\Enumerations\Capa::ADDITIONAL_IDENTITIES; - } - - if ($oConfig->Get('security', 'allow_two_factor_auth', false)) + if ($oConfig->Get('security', 'allow_two_factor_auth', false) && + ($bAdmin || ($oAccount && !$oAccount->IsAdditionalAccount()))) { $aResult[] = \RainLoop\Enumerations\Capa::TWO_FACTOR; + + if ($oConfig->Get('security', 'force_two_factor_auth', false) && + ($bAdmin || ($oAccount && !$oAccount->IsAdditionalAccount()))) + { + $aResult[] = \RainLoop\Enumerations\Capa::TWO_FACTOR_FORCE; + } } if ($oConfig->Get('labs', 'allow_gravatar', false)) @@ -6359,6 +7867,11 @@ class Actions $aResult[] = \RainLoop\Enumerations\Capa::GRAVATAR; } + if ($oConfig->Get('interface', 'show_attachment_thumbnail', true)) + { + $aResult[] = \RainLoop\Enumerations\Capa::ATTACHMENT_THUMBNAILS; + } + if ($oConfig->Get('labs', 'allow_prefetch', false)) { $aResult[] = \RainLoop\Enumerations\Capa::PREFETCH; @@ -6369,23 +7882,34 @@ class Actions $aResult[] = \RainLoop\Enumerations\Capa::THEMES; } + if ($oConfig->Get('webmail', 'allow_user_background', false)) + { + $aResult[] = \RainLoop\Enumerations\Capa::USER_BACKGROUND; + } + if ($oConfig->Get('security', 'openpgp', false)) { $aResult[] = \RainLoop\Enumerations\Capa::OPEN_PGP; } + if (!\RainLoop\Utils::IsOwnCloud()) + { + $aResult[] = \RainLoop\Enumerations\Capa::AUTOLOGOUT; + } + return $aResult; } /** * @param bool $bAdmin * @param string $sName + * @param \RainLoop\Model\Account $oAccount = null * * @return bool */ - public function GetCapa($bAdmin, $sName) + public function GetCapa($bAdmin, $sName, $oAccount = null) { - return \in_array($sName, $this->Capa($bAdmin)); + return \in_array($sName, $this->Capa($bAdmin, $oAccount)); } /** @@ -6399,14 +7923,9 @@ class Actions $bResult = false; if (!empty($sKey) && ($bForce || $this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'http', true))) { - $iLast = 1382478804; - $iExpires = 2002478804; - - header('Cache-Control: private', true); - header('ETag: '.\md5('Etag:'.\md5($sKey.\md5($this->Config()->Get('cache', 'index', '')))), true); - header('Last-Modified: '.\gmdate('D, d M Y H:i:s', $iLast).' UTC', true); - header('Expires: '.\gmdate('D, j M Y H:i:s', $iExpires).' UTC', true); - header('Connection: close'); + $this->oHttp->ServerUseCache( + \md5('Etag:'.\md5($sKey.\md5($this->Config()->Get('cache', 'index', '')))), + 1382478804, 2002478804); $bResult = true; } @@ -6438,10 +7957,11 @@ class Actions /** * @param bool $bDownload + * @param bool $bThumbnail = false * * @return bool */ - private function rawSmart($bDownload) + private function rawSmart($bDownload, $bThumbnail = false) { $sRawKey = (string) $this->GetActionParam('RawKey', ''); $aValues = $this->getDecodedRawKeyValue($sRawKey); @@ -6458,12 +7978,12 @@ class Actions $this->verifyCacheByKey($sRawKey); } - $this->initMailClientConnection(); + $oAccount = $this->initMailClientConnection(); $self = $this; return $this->MailClient()->MessageMimeStream( - function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ($self, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload) { - if (\is_resource($rResource)) + function($rResource, $sContentType, $sFileName, $sMimeIndex = '') use ($self, $oAccount, $sRawKey, $sContentTypeIn, $sFileNameIn, $bDownload, $bThumbnail) { + if ($oAccount && \is_resource($rResource)) { $sContentTypeOut = $sContentTypeIn; if (empty($sContentTypeOut)) @@ -6483,16 +8003,41 @@ class Actions $sFileNameOut = $self->MainClearFileName($sFileNameOut, $sContentTypeOut, $sMimeIndex); - \header('Content-Type: '.$sContentTypeOut); - \header('Content-Disposition: '.($bDownload ? 'attachment' : 'inline').'; '. - \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); - - \header('Accept-Ranges: none', true); - \header('Content-Transfer-Encoding: binary'); - $self->cacheByKey($sRawKey); - \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); + $bDone = false; + if ($bThumbnail && !$bDownload) + { + \MailSo\Base\StreamWrappers\TempFile::Reg(); + + $sFileName = 'mailsotempfile://'.\MailSo\Base\Utils::Md5Rand($sFileNameOut); + + $rTempResource = @\fopen($sFileName, 'r+b'); + if (@\is_resource($rTempResource)) + { + \MailSo\Base\Utils::MultipleStreamWriter($rResource, array($rTempResource)); + @\fclose($rTempResource); + + $oThumb =@ new \PHPThumb\GD($sFileName); + if ($oThumb) + { + $oThumb->adaptiveResize(60, 60)->show(); + $bDone = true; + } + } + } + + if (!$bDone) + { + \header('Content-Type: '.$sContentTypeOut); + \header('Content-Disposition: '.($bDownload ? 'attachment' : 'inline').'; '. + \trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true); + + \header('Accept-Ranges: none', true); + \header('Content-Transfer-Encoding: binary'); + + \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); + } } }, $sFolder, $iUid, true, $sMimeIndex); } @@ -6513,6 +8058,139 @@ class Actions return $this->rawSmart(false); } + /** + * @return bool + */ + public function RawViewThumbnail() + { + return $this->rawSmart(false, true); + } + + /** + * @return bool + */ + public function RawUserBackground() + { + $sRawKey = (string) $this->GetActionParam('RawKey', ''); + $this->verifyCacheByKey($sRawKey); + + $oAccount = $this->getAccountFromToken(); + + $sData = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'background' + ); + + if (!empty($sData)) + { + $aData = \json_decode($sData, true); + unset($sData); + + if (!empty($aData['ContentType']) && !empty($aData['Raw']) && + \in_array($aData['ContentType'], array('image/png', 'image/jpg', 'image/jpeg'))) + { + $this->cacheByKey($sRawKey); + + @\header('Content-Type: '.$aData['ContentType']); + echo \base64_decode($aData['Raw']); + unset($aData); + + return true; + } + } + + return false; + } + + /** + * @return bool + */ + public function RawPublic() + { + $sRawKey = (string) $this->GetActionParam('RawKey', ''); + $this->verifyCacheByKey($sRawKey); + + $sHash = $sRawKey; + $sData = ''; + + if (!empty($sHash)) + { + $sData = $this->StorageProvider()->Get(null, + \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY, + \RainLoop\KeyPathHelper::PublicFile($sHash) + ); + } + + $aMatch = array(); + if (!empty($sData) && 0 === \strpos($sData, 'data:') && + \preg_match('/^data:([^:]+):/', $sData, $aMatch) && !empty($aMatch[1])) + { + $sContentType = \trim($aMatch[1]); + if (\in_array($sContentType, array('image/png', 'image/jpg', 'image/jpeg'))) + { + $this->cacheByKey($sRawKey); + + @\header('Content-Type: '.$sContentType); + echo \preg_replace('/^data:[^:]+:/', '', $sData); + unset($sData); + + return true; + } + } + + return false; + } + + /** + * @param string $sFileName + * + * @return bool + */ + public function isFileHasFramedPreview($sFileName) + { + $sExt = \MailSo\Base\Utils::GetFileExtension($sFileName); + return \in_array($sExt, array('doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx')); + } + + /** + * @param string $sFileName + * + * @return bool + */ + public function isFileHasThumbnail($sFileName) + { + static $aCache = array(); + + $sExt = \MailSo\Base\Utils::GetFileExtension($sFileName); + if (isset($aCache[$sExt])) + { + return $aCache[$sExt]; + } + + $bResult = \function_exists('gd_info'); + if ($bResult) + { + $bResult = false; + switch ($sExt) + { + case 'png': + $bResult = \function_exists('imagecreatefrompng'); + break; + case 'gif': + $bResult = \function_exists('imagecreatefromgif'); + break; + case 'jpg': + case 'jpeg': + $bResult = \function_exists('imagecreatefromjpeg'); + break; + } + } + + $aCache[$sExt] = $bResult; + + return $bResult; + } + /** * @return bool */ @@ -6574,7 +8252,7 @@ class Actions { $oAccount = $this->getAccountFromToken(); - \header('Content-Type: text/directory; charset=UTF-8'); + \header('Content-Type: text/x-vcard; charset=UTF-8'); \header('Content-Disposition: attachment; filename="contacts.vcf"', true); \header('Accept-Ranges: none', true); \header('Content-Transfer-Encoding: binary'); @@ -6592,7 +8270,7 @@ class Actions { $oAccount = $this->getAccountFromToken(); - \header('Content-Type: ext/csv; charset=UTF-8'); + \header('Content-Type: text/csv; charset=UTF-8'); \header('Content-Disposition: attachment; filename="contacts.csv"', true); \header('Accept-Ranges: none', true); \header('Content-Transfer-Encoding: binary'); @@ -6604,7 +8282,7 @@ class Actions } /** - * @return \RainLoop\Account|bool + * @return \RainLoop\Model\Account|bool */ private function initMailClientConnection() { @@ -6616,11 +8294,7 @@ class Actions try { - $this->MailClient() - ->Connect($oAccount->Domain()->IncHost(\MailSo\Base\Utils::GetDomainFromEmail($oAccount->Email())), - $oAccount->Domain()->IncPort(), $oAccount->Domain()->IncSecure()) - ->Login($oAccount->IncLogin(), $oAccount->Password(), !!$this->Config()->Get('labs', 'use_imap_auth_plain')) - ; + $oAccount->IncConnectAndLoginHelper($this->Plugins(), $this->MailClient(), $this->Config()); } catch (\MailSo\Net\Exceptions\ConnectionException $oException) { @@ -6692,13 +8366,46 @@ class Actions /** * @param string $sLanguage + * @param string $sDefault = '' + * @param bool $bAdmin = false + * @param bool $bSearchShortName = false * * @return string */ - public function ValidateLanguage($sLanguage) + public function ValidateLanguage($sLanguage, $sDefault = '', $bAdmin = false, $bSearchShortName = false) { - return \in_array($sLanguage, $this->GetLanguages()) ? - $sLanguage : $this->Config()->Get('i18n', 'default', 'en'); + $sResult = ''; + $aLang = $this->GetLanguages($bAdmin); + + if (\is_array($aLang)) + { + if (\in_array($sLanguage, $aLang)) + { + $sResult = $sLanguage; + } + + if ($bSearchShortName && empty($sResult) && 2 < \strlen($sLanguage)) + { + $sLanguage = \substr($sLanguage, 0, 2); + if (\in_array($sLanguage, $aLang)) + { + $sResult = $sLanguage; + } + } + + if (empty($sResult) && !empty($sDefault) && \in_array($sDefault, $aLang)) + { + $sResult = $sDefault; + } + + if (empty($sResult)) + { + $sResult = $this->Config()->Get('webmail', $bAdmin ? 'language_admin' : 'language', 'en'); + $sResult = \in_array($sResult, $aLang) ? $sResult : 'en'; + } + } + + return $sResult; } /** @@ -6795,23 +8502,33 @@ class Actions /** * @staticvar array $aCache + * @param bool $bAdmin = false * * @return array */ - public function GetLanguages() + public function GetLanguages($bAdmin = false) { - static $aCache = null; - if (\is_array($aCache)) + static $aCache = array(); + $sDir = APP_VERSION_ROOT_PATH.'langs/'.($bAdmin ? 'admin/' : ''); + + if (isset($aCache[$sDir])) { - return $aCache; + return $aCache[$sDir]; } - $bEn = false; - $sList = array(); - $sDir = APP_VERSION_ROOT_PATH.'langs/'; +// $aTopper = array('en'); +// $sUserLanguage = $this->detectUserLanguage(); +// if (!empty($sUserLanguage) && 'en' !== $sUserLanguage) +// { +// $aTopper[] = $sUserLanguage; +// } + + $aTop = array(); + $aList = array(); + if (@\is_dir($sDir)) { - $rDirH = opendir($sDir); + $rDirH = \opendir($sDir); if ($rDirH) { while (($sFile = \readdir($rDirH)) !== false) @@ -6821,14 +8538,14 @@ class Actions $sLang = \strtolower(\substr($sFile, 0, -4)); if (0 < \strlen($sLang) && 'always' !== $sLang) { - if (\in_array($sLang, array('en'))) - { - $bEn = true; - } - else - { - \array_push($sList, $sLang); - } +// if (\in_array(\substr($sLang, 0, 2), $aTopper)) +// { +// \array_push($aTop, $sLang); +// } +// else +// { + \array_push($aList, $sLang); +// } } } } @@ -6837,14 +8554,11 @@ class Actions } } - \sort($sList); - if ($bEn) - { - \array_unshift($sList, 'en'); - } + \sort($aTop); + \sort($aList); - $aCache = $sList; - return $sList; + $aCache[$sDir] = \array_merge($aTop, $aList); + return $aCache[$sDir]; } /** @@ -6858,6 +8572,9 @@ class Actions $sHtml = $this->Plugins()->ProcessTemplate($sName, $sHtml); $sHtml = \preg_replace('/\{\{INCLUDE\/([a-zA-Z]+)\/PLACE\}\}/', '', $sHtml); + $sHtml = \preg_replace('/'; - } - } - } - - return $sResult; - } - - /** - * @param bool $bAdmin - * @param array $aAppData - * @param \RainLoop\Account|null $oAccount = null - * - * @return \RainLoop\PluginsNext\Manager - */ - public function InitAppData($bAdmin, &$aAppData, $oAccount = null) - { - if ($this->bIsEnabled && isset($aAppData['Plugins']) && \is_array($aAppData['Plugins'])) - { - $bAuth = isset($aAppData['Auth']) && !!$aAppData['Auth']; - foreach ($this->aPlugins as $oPlugin) - { - if ($oPlugin) - { - $aConfig = array(); - $aMap = $oPlugin->ConfigMap(); - if (\is_array($aMap)) - { - foreach ($aMap as /* @var $oPluginProperty \RainLoop\PluginsNext\Property */$oPluginProperty) - { - if ($oPluginProperty && $oPluginProperty->AllowedInJs()) - { - $aConfig[$oPluginProperty->Name()] = - $oPlugin->Config()->Get('plugin', - $oPluginProperty->Name(), - $oPluginProperty->DefaultValue()); - } - } - } - - $oPlugin->FilterAppDataPluginSection($bAdmin, $bAuth, $aConfig); - - if (0 < \count($aConfig)) - { - $aAppData['Plugins'][$oPlugin->Name()] = $aConfig; - } - } - } - - $this->RunHook('filter.app-data', array( - 'IsAdmin' => $bAdmin, - 'AppData' => &$aAppData - ), $oAccount); - } - - return $this; - } - - /** - * @param string $sHookName - * @param mixed $mCallbak - * - * @return \RainLoop\PluginsNext\Manager - */ - public function AddHook($sHookName, $mCallbak) - { - if ($this->bIsEnabled && \is_callable($mCallbak)) - { - if (!isset($this->aHooks[$sHookName])) - { - $this->aHooks[$sHookName] = array(); - } - - $this->aHooks[$sHookName][] = $mCallbak; - } - - return $this; - } - - /** - * @param string $sFile - * @param bool $bAdminScope = false - * - * @return \RainLoop\PluginsNext\Manager - */ - public function AddJs($sFile, $bAdminScope = false) - { - if ($this->bIsEnabled) - { - if ($bAdminScope) - { - $this->aAdminJs[$sFile] = $sFile; - } - else - { - $this->aJs[$sFile] = $sFile; - } - } - - return $this; - } - - /** - * @param string $sFile - * @param bool $bAdminScope = false - * - * @return \RainLoop\PluginsNext\Manager - */ - public function AddTemplate($sFile, $bAdminScope = false) - { - if ($this->bIsEnabled) - { - if ($bAdminScope) - { - $this->aAdminTemplates[$sFile] = $sFile; - } - else - { - $this->aTemplates[$sFile] = $sFile; - } - } - - return $this; - } - - /** - * @param string $sHookName - * @param array $aArg = array() - * @param \RainLoop\Account|null $oAccount = null - * @param bool $bLogHook = true - * - * @return \RainLoop\PluginsNext\Manager - */ - public function RunHook($sHookName, $aArg = array(), $oAccount = null, $bLogHook = true) - { - if ($this->bIsEnabled) - { - if (isset($this->aHooks[$sHookName])) - { - if ($bLogHook) - { - $this->WriteLog('Hook: '.$sHookName, \MailSo\Log\Enumerations\Type::NOTE); - } - - $bArgArray = 0 < \count($aArg); - foreach ($this->aHooks[$sHookName] as $mCallbak) - { - if ($bArgArray) - { - \call_user_func_array($mCallbak, array($aArg)); - } - else - { - \call_user_func($mCallbak); - } - } - } - } - - return $this; - } - - /** - * @param string $sActionName - * @param mixed $mCallbak - * - * @return \RainLoop\PluginsNext\Manager - */ - public function AddAdditionalPartAction($sActionName, $mCallbak) - { - if ($this->bIsEnabled && \is_callable($mCallbak)) - { - $sActionName = \strtolower($sActionName); - if (!isset($this->aAdditionalParts[$sActionName])) - { - $this->aAdditionalParts[$sActionName] = array(); - } - - $this->aAdditionalParts[$sActionName][] = $mCallbak; - } - - return $this; - } - - /** - * @param string $sActionName - * @param array $aParts = array() - * - * @return \RainLoop\PluginsNext\Manager - */ - public function RunAdditionalPart($sActionName, $aParts = array()) - { - $bResult = false; - if ($this->bIsEnabled) - { - $sActionName = \strtolower($sActionName); - if (isset($this->aAdditionalParts[$sActionName])) - { - foreach ($this->aAdditionalParts[$sActionName] as $mCallbak) - { - $bCallResult = \call_user_func_array($mCallbak, $aParts); - if ($bCallResult && !$bResult) - { - $bResult = true; - } - } - } - } - - return $bResult; - } - - /** - * @param string $sName - * @param string $sPlace - * @param string $sHtml - * @param bool $bPrepend = false - * - * @return \RainLoop\PluginsNext\Manager - */ - public function AddProcessTemplateAction($sName, $sPlace, $sHtml, $bPrepend = false) - { - if ($this->bIsEnabled) - { - if (!isset($this->aProcessTemplate[$sName])) - { - $this->aProcessTemplate[$sName] = array(); - } - - if (!isset($this->aProcessTemplate[$sName][$sPlace])) - { - $this->aProcessTemplate[$sName][$sPlace] = array(); - } - - if ($bPrepend) - { - \array_unshift($this->aProcessTemplate[$sName][$sPlace], $sHtml); - } - else - { - \array_push($this->aProcessTemplate[$sName][$sPlace], $sHtml); - } - } - - return $this; - } - - /** - * @param string $sActionName - * @param mixed $mCallbak - * - * @return \RainLoop\Plugins\Manager - */ - public function AddAdditionalAjaxAction($sActionName, $mCallbak) - { - if ($this->bIsEnabled && \is_callable($mCallbak) && 0 < \strlen($sActionName)) - { - $sActionName = 'Do'.$sActionName; - - if (!isset($this->aAdditionalAjax[$sActionName])) - { - $this->aAdditionalAjax[$sActionName] = $mCallbak; - } - } - - return $this; - } - - /** - * @param string $sActionName - * - * @return bool - */ - public function HasAdditionalAjax($sActionName) - { - return $this->bIsEnabled && isset($this->aAdditionalAjax[$sActionName]); - } - - /** - * @param string $sActionName - * - * @return mixed - */ - public function RunAdditionalAjax($sActionName) - { - if ($this->bIsEnabled) - { - if (isset($this->aAdditionalAjax[$sActionName])) - { - return \call_user_func($this->aAdditionalAjax[$sActionName]); - } - } - - return false; - } - - /** - * @param string $sLang - * @param array $sActionName - * - * @return \RainLoop\PluginsNext\Manager - */ - public function ReadLang($sLang, &$aLang) - { - if ($this->bIsEnabled) - { - foreach ($this->aPlugins as $oPlugin) - { - if ($oPlugin->UseLangs()) - { - $sPath = $oPlugin->Path(); - - \RainLoop\Utils::ReadAndAddLang($sPath.'/langs/en.ini', $aLang); - if ('en' !== $sLang) - { - \RainLoop\Utils::ReadAndAddLang($sPath.'/langs/'.$sLang.'.ini', $aLang); - } - } - } - } - - return $this; - } - - /** - * @param string $sName - * @param string $sHtml - * - * @return string - */ - public function ProcessTemplate($sName, $sHtml) - { - if (isset($this->aProcessTemplate[$sName])) - { - foreach ($this->aProcessTemplate[$sName] as $sPlace => $aAddHtml) - { - if (\is_array($aAddHtml) && 0 < \count($aAddHtml)) - { - foreach ($aAddHtml as $sAddHtml) - { - $sHtml = \str_replace('{{INCLUDE/'.$sPlace.'/PLACE}}', $sAddHtml.'{{INCLUDE/'.$sPlace.'/PLACE}}', $sHtml); - } - } - } - } - - return $sHtml; - } - - /** - * @return bool - */ - public function bIsEnabled() - { - return $this->bIsEnabled; - } - - /** - * @return int - */ - public function Count() - { - return $this->bIsEnabled ? \count($this->aPlugins) : 0; - } - - /** - * @param \MailSo\Log\Logger $oLogger - * - * @return \RainLoop\PluginsNext\Manager - * - * @throws \MailSo\Base\Exceptions\InvalidArgumentException - */ - public function SetLogger($oLogger) - { - if (!($oLogger instanceof \MailSo\Log\Logger)) - { - throw new \MailSo\Base\Exceptions\InvalidArgumentException(); - } - - $this->oLogger = $oLogger; - - return $this; - } - - /** - * @param string $sDesc - * @param int $iType = \MailSo\Log\Enumerations\Type::INFO - * - * @return void - */ - public function WriteLog($sDesc, $iType = \MailSo\Log\Enumerations\Type::INFO) - { - if ($this->oLogger) - { - $this->oLogger->Write($sDesc, $iType, 'PLUGIN'); - } - } - - /** - * @param string $sDesc - * @param int $iType = \MailSo\Log\Enumerations\Type::INFO - * - * @return void - */ - public function WriteException($sDesc, $iType = \MailSo\Log\Enumerations\Type::INFO) - { - if ($this->oLogger) - { - $this->oLogger->WriteException($sDesc, $iType, 'PLUGIN'); - } - } -} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/PluginsNext/Property.php b/rainloop/v/0.0.0/app/libraries/RainLoop/PluginsNext/Property.php deleted file mode 100644 index e77770d14..000000000 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/PluginsNext/Property.php +++ /dev/null @@ -1,179 +0,0 @@ -sName = $sName; - $this->iType = \RainLoop\Enumerations\PluginPropertyType::STRING; - $this->mDefaultValue = ''; - $this->sLabel = ''; - $this->sDesc = ''; - $this->bAllowedInJs = false; - } - - /** - * @param string $sName - * - * @return \RainLoop\PluginsNext\Property - */ - public static function NewInstance($sName) - { - return new self($sName); - } - - /** - * @param int $iType - * - * @return \RainLoop\PluginsNext\Property - */ - public function SetType($iType) - { - $this->iType = (int) $iType; - - return $this; - } - - /** - * @param mixed $mDefaultValue - * - * @return \RainLoop\PluginsNext\Property - */ - public function SetDefaultValue($mDefaultValue) - { - $this->mDefaultValue = $mDefaultValue; - - return $this; - } - - /** - * @param string $sLabel - * - * @return \RainLoop\PluginsNext\Property - */ - public function SetLabel($sLabel) - { - $this->sLabel = $sLabel; - - return $this; - } - - /** - * @param string $sDesc - * - * @return \RainLoop\PluginsNext\Property - */ - public function SetDescription($sDesc) - { - $this->sDesc = $sDesc; - - return $this; - } - - /** - * @param bool $bValue = true - * - * @return \RainLoop\PluginsNext\Property - */ - public function SetAllowedInJs($bValue = true) - { - $this->bAllowedInJs = !!$bValue; - - return $this; - } - - /** - * @return string - */ - public function Name() - { - return $this->sName; - } - - /** - * @return bool - */ - public function AllowedInJs() - { - return $this->bAllowedInJs; - } - - /** - * @return string - */ - public function Description() - { - return $this->sDesc; - } - - /** - * @return string - */ - public function Label() - { - return $this->sLabel; - } - - /** - * @return int - */ - public function Type() - { - return $this->iType; - } - - /** - * @return mixed - */ - public function DefaultValue() - { - return $this->mDefaultValue; - } - - /** - * @return array - */ - public function ToArray() - { - return array( - '', - $this->sName, - $this->iType, - $this->sLabel, - $this->mDefaultValue, - $this->sDesc - ); - } -} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AbstractProvider.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AbstractProvider.php index a5de37385..4b8c3596f 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AbstractProvider.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AbstractProvider.php @@ -5,7 +5,7 @@ namespace RainLoop\Providers; abstract class AbstractProvider { /** - * @var \RainLoop\Account + * @var \RainLoop\Model\Account */ protected $oAccount; @@ -23,7 +23,7 @@ abstract class AbstractProvider } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount */ public function SetAccount($oAccount) { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php index b62a2b356..8ea5a138e 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook.php @@ -113,9 +113,9 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider * * @return bool */ - public function DeleteAllContactsAndTags($sEmail) + public function DeleteAllContacts($sEmail) { - return $this->IsActive() ? $this->oDriver->DeleteAllContactsAndTags($sEmail) : false; + return $this->IsActive() ? $this->oDriver->DeleteAllContacts($sEmail) : false; } /** @@ -133,17 +133,6 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider $iOffset, $iLimit, $sSearch, $iResultCount) : array(); } - /** - * @param string $sEmail - * @param bool $bCache = true - * - * @return array - */ - public function GetContactTags($sEmail, $bCache = true) - { - return $this->IsActive() ? $this->oDriver->GetContactTags($sEmail, $bCache) : array(); - } - /** * @param string $sEmail * @param string $mID @@ -328,7 +317,8 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider public function ImportVcfFile($sEmail, $sVcfData) { $iCount = 0; - if ($this->IsActive() && \is_string($sVcfData)) + + if (\class_exists('Sabre\DAV\Client') && $this->IsActive() && \is_string($sVcfData)) { $sVcfData = \trim($sVcfData); if ("\xef\xbb\xbf" === \substr($sVcfData, 0, 3)) @@ -363,7 +353,7 @@ class AddressBook extends \RainLoop\Providers\AbstractProvider $oVCard->UID = \Sabre\DAV\UUIDUtil::getUUID(); } - $oContact->PopulateByVCard($oVCard->serialize()); + $oContact->PopulateByVCard($oVCard->UID, $oVCard->serialize()); if (0 < \count($oContact->Properties)) { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Contact.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Contact.php index fe9ec1590..7fd425139 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Contact.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/Classes/Contact.php @@ -34,11 +34,6 @@ class Contact */ public $Properties; - /** - * @var array - */ - public $Tags; - /** * @var bool */ @@ -66,7 +61,6 @@ class Contact $this->Display = ''; $this->Changed = \time(); $this->Properties = array(); - $this->Tags = array(); $this->ReadOnly = false; $this->IdPropertyFromSearch = 0; $this->Etag = ''; @@ -149,8 +143,6 @@ class Contact { $this->Properties[] = new \RainLoop\Providers\AddressBook\Classes\Property(PropertyType::FULLNAME, $this->Display); } - - $this->Tags = \array_map('trim', $this->Tags); } /** @@ -158,7 +150,8 @@ class Contact */ public function RegenerateContactStr() { - $this->IdContactStr = \Sabre\DAV\UUIDUtil::getUUID(); + $this->IdContactStr = \class_exists('Sabre\DAV\Client') ? + \Sabre\DAV\UUIDUtil::getUUID() : \MailSo\Base\Utils::Md5Rand(); } /** @@ -189,10 +182,20 @@ class Contact /** * @return string */ - public function ToVCard($sPreVCard = '') + public function ToVCard($sPreVCard = '', $oLogger = null) { $this->UpdateDependentValues(); + if (!\class_exists('Sabre\DAV\Client')) + { + return ''; + } + + if ("\xef\xbb\xbf" === \substr($sPreVCard, 0, 3)) + { + $sPreVCard = \substr($sPreVCard, 3); + } + $oVCard = null; if (0 < \strlen($sPreVCard)) { @@ -200,7 +203,14 @@ class Contact { $oVCard = \Sabre\VObject\Reader::read($sPreVCard); } - catch (\Exception $oExc) {}; + catch (\Exception $oExc) + { + if ($oLogger) + { + $oLogger->WriteException($oExc); + $oLogger->WriteDump($sPreVCard); + } + } } if (!$oVCard) @@ -211,7 +221,7 @@ class Contact $oVCard->VERSION = '3.0'; $oVCard->PRODID = '-//RainLoop//'.APP_VERSION.'//EN'; - unset($oVCard->FN, $oVCard->EMAIL, $oVCard->TEL, $oVCard->URL, $oVCard->NICKNAME, $oVCard->CATEGORIES, $oVCard->{'X-RL-TAGS'}); + unset($oVCard->FN, $oVCard->EMAIL, $oVCard->TEL, $oVCard->URL, $oVCard->NICKNAME); $sFirstName = $sLastName = $sMiddleName = $sSuffix = $sPrefix = ''; foreach ($this->Properties as /* @var $oProperty \RainLoop\Providers\AddressBook\Classes\Property */ &$oProperty) @@ -272,11 +282,6 @@ class Contact $oVCard->N = array($sLastName, $sFirstName, $sMiddleName, $sPrefix, $sSuffix); $oVCard->REV = \gmdate('Ymd', $this->Changed).'T'.\gmdate('His', $this->Changed).'Z'; - if (0 < \count($this->Tags)) - { - $oVCard->CATEGORIES = $this->Tags; - } - return (string) $oVCard->serialize(); } @@ -491,10 +496,20 @@ class Contact } } - public function PopulateByVCard($sVCard, $sEtag = '') + public function PopulateByVCard($sUid, $sVCard, $sEtag = '', $oLogger = null) { + if ("\xef\xbb\xbf" === \substr($sVCard, 0, 3)) + { + $sVCard = \substr($sVCard, 3); + } + $this->Properties = array(); + if (!\class_exists('Sabre\DAV\Client')) + { + return false; + } + if (!empty($sEtag)) { $this->Etag = $sEtag; @@ -504,7 +519,16 @@ class Contact { $oVCard = \Sabre\VObject\Reader::read($sVCard); } - catch (\Exception $oExc) {}; + catch (\Exception $oExc) + { + if ($oLogger) + { + $oLogger->WriteException($oExc); + $oLogger->WriteDump($sVCard); + } + + $this->IdContactStr = $sUid; + } $aProperties = array(); if ($oVCard) @@ -589,15 +613,10 @@ class Contact } $this->Properties = $aProperties; - - if (isset($oVCard->CATEGORIES)) - { - $this->Tags = (array) $oVCard->CATEGORIES->getParts(); - $this->Tags = \is_array($this->Tags) ? $this->Tags : array(); - $this->Tags = \array_map('trim', $this->Tags); - } } $this->UpdateDependentValues(); + + return true; } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php index 5614b15f4..b2100bc05 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/AddressBook/PdoAddressBook.php @@ -28,11 +28,6 @@ class PdoAddressBook */ private $sPassword; - /** - * @var array - */ - private $aTagsCache; - public function __construct($sDsn, $sUser = '', $sPassword = '', $sDsnType = 'mysql') { $this->sDsn = $sDsn; @@ -41,7 +36,6 @@ class PdoAddressBook $this->sDsnType = $sDsnType; $this->bExplain = false; // debug - $this->aTagsCache = array(); // debug } /** @@ -97,12 +91,17 @@ class PdoAddressBook if ($aItem && isset($aItem['id_contact'], $aItem['id_contact_str'], $aItem['changed'], $aItem['deleted'], $aItem['etag']) && !empty($aItem['id_contact_str'])) { - $aResult[$aItem['id_contact_str']] = array( + $sKeyID = $aItem['id_contact_str']; + + $aResult[$sKeyID] = array( + 'deleted' => '1' === (string) $aItem['deleted'], 'id_contact' => $aItem['id_contact'], + 'uid' => $sKeyID, 'etag' => $aItem['etag'], 'changed' => (int) $aItem['changed'], - 'deleted' => '1' === (string) $aItem['deleted'] ); + + $aResult[$sKeyID]['changed_'] = \gmdate('c', $aResult[$sKeyID]['changed']); } } } @@ -147,12 +146,15 @@ class PdoAddressBook $sKeyID = \preg_replace('/\.vcf$/i', '', $sVcfFileName); $mResult[$sKeyID] = array( + 'deleted' => false, + 'uid' => $sKeyID, 'vcf' => $sVcfFileName, 'etag' => \trim(\trim($aItem['{dav:}getetag']), '"\''), 'lastmodified' => $aItem['{dav:}getlastmodified'], - 'changed' => \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($aItem['{dav:}getlastmodified']), - 'deleted' => false + 'changed' => \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($aItem['{dav:}getlastmodified']) ); + + $mResult[$sKeyID]['changed_'] = \gmdate('c', $mResult[$sKeyID]['changed']); } } } @@ -238,6 +240,11 @@ class PdoAddressBook $sPath = $aUrl['path']; + if (!\class_exists('Sabre\DAV\Client')) + { + return false; + } + $oClient = new \Sabre\DAV\Client($aSettings); $oClient->setVerifyPeer(false); @@ -311,7 +318,7 @@ class PdoAddressBook } $oResponse = $this->davClientRequest($oClient, 'PUT', - $sPath.$oContact->CardDavNameUri(), $oContact->ToVCard($sExsistensBody)); + $sPath.$oContact->CardDavNameUri(), $oContact->ToVCard($sExsistensBody, $this->oLogger)."\r\n\r\n"); if ($oResponse && isset($oResponse['headers'], $oResponse['headers']['etag'])) { @@ -359,8 +366,9 @@ class PdoAddressBook $oContact = new \RainLoop\Providers\AddressBook\Classes\Contact(); } - $oContact->PopulateByVCard($sBody, - !empty($oResponse['headers']['etag']) ? \trim(\trim($oResponse['headers']['etag']), '"\'') : ''); + $oContact->PopulateByVCard($aData['uid'], $sBody, + !empty($oResponse['headers']['etag']) ? \trim(\trim($oResponse['headers']['etag']), '"\'') : '', + $this->oLogger); $this->ContactSave($sEmail, $oContact); unset($oContact); @@ -470,19 +478,12 @@ class PdoAddressBook ':id_contact' => array($iIdContact, \PDO::PARAM_INT) ) ); - - // clear previos tags - $this->prepareAndExecute( - 'DELETE FROM rainloop_ab_tags_contacts WHERE id_contact = :id_contact', - array( - ':id_contact' => array($iIdContact, \PDO::PARAM_INT) - ) - ); } else { $sSql = 'INSERT INTO rainloop_ab_contacts '. - '( id_user, id_contact_str, display, changed, etag) VALUES '. + '( id_user, id_contact_str, display, changed, etag)'. + ' VALUES '. '(:id_user, :id_contact_str, :display, :changed, :etag)'; $this->prepareAndExecute($sSql, @@ -528,52 +529,12 @@ class PdoAddressBook if (0 < \count($aParams)) { $sSql = 'INSERT INTO rainloop_ab_properties '. - '( id_contact, id_user, prop_type, prop_type_str, prop_value, prop_value_custom, prop_frec) VALUES '. + '( id_contact, id_user, prop_type, prop_type_str, prop_value, prop_value_custom, prop_frec)'. + ' VALUES '. '(:id_contact, :id_user, :prop_type, :prop_type_str, :prop_value, :prop_value_custom, :prop_frec)'; $this->prepareAndExecute($sSql, $aParams, true); } - - $aTags = array(); - $aContactTags = $this->GetContactTags($sEmail); - - $fFindFunc = function ($sName) use ($aContactTags) { - - $iResult = 0; - foreach ($aContactTags as $oItem) - { - if ($oItem && $oItem->Name === $sName) - { - $iResult = (int) $oItem->IdContactTag; - break; - } - } - - return $iResult; - }; - - foreach ($oContact->Tags as $sTagName) - { - $iTagID = $fFindFunc($sTagName); - if (0 >= $iTagID) - { - $iTagID = $this->CreateTag($sEmail, $sTagName, false); - } - - if (\is_int($iTagID) && 0 < $iTagID) - { - $aTags[] = array( - ':id_tag' => array($iTagID, \PDO::PARAM_INT), - ':id_contact' => array($iIdContact, \PDO::PARAM_INT) - ); - } - } - - if (0 < \count($aTags)) - { - $sSql = 'INSERT INTO rainloop_ab_tags_contacts (id_tag, id_contact) VALUES (:id_tag, :id_contact)'; - $this->prepareAndExecute($sSql, $aTags, true); - } } } catch (\Exception $oException) @@ -584,51 +545,6 @@ class PdoAddressBook return 0 < $iIdContact; } - /** - * @param string $sEmail - * @param string $sName - * @param bool $bSyncDb = true - * - * @return bool|int - */ - public function CreateTag($sEmail, $sName, $bSyncDb = true) - { - if ($bSyncDb) - { - $this->SyncDatabase(); - } - - $iUserID = $this->getUserId($sEmail); - - $this->aTagsCache = array(); - - $mResult = false; - try - { - $sSql = 'INSERT INTO rainloop_ab_tags '. - '(id_user, tag_name) VALUES (:id_user, :tag_name)'; - - $this->prepareAndExecute($sSql, - array( - ':id_user' => array($iUserID, \PDO::PARAM_INT), - ':tag_name' => array($sName, \PDO::PARAM_STR) - ) - ); - - $sLast = $this->lastInsertId('rainloop_ab_tags', 'id_tag'); - if (\is_numeric($sLast) && 0 < (int) $sLast) - { - $mResult = (int) $sLast; - } - } - catch (\Exception $oException) - { - throw $oException; - } - - return $mResult; - } - /** * @param string $sEmail * @param array $aContactIds @@ -677,7 +593,7 @@ class PdoAddressBook * * @return bool */ - public function DeleteAllContactsAndTags($sEmail, $bSyncDb = true) + public function DeleteAllContacts($sEmail, $bSyncDb = true) { if ($bSyncDb) { @@ -690,136 +606,10 @@ class PdoAddressBook $this->prepareAndExecute('DELETE FROM rainloop_ab_properties WHERE id_user = :id_user', $aParams); $this->prepareAndExecute('DELETE FROM rainloop_ab_contacts WHERE id_user = :id_user', $aParams); - $this->prepareAndExecute('DELETE FROM rainloop_ab_tags WHERE id_user = :id_user', $aParams); return true; } - /** - * @param string $sEmail - * @param array $aTagsIds - * - * @return bool - */ - public function DeleteTags($sEmail, $aTagsIds) - { - $this->SyncDatabase(); - $iUserID = $this->getUserId($sEmail); - - $aTagsIds = \array_filter($aTagsIds, function (&$mItem) { - $mItem = (int) \trim($mItem); - return 0 < $mItem; - }); - - if (0 === \count($aTagsIds)) - { - return false; - } - - $this->aTagsCache = array(); - - $sIDs = \implode(',', $aTagsIds); - $aParams = array(':id_user' => array($iUserID, \PDO::PARAM_INT)); - - $this->prepareAndExecute('DELETE FROM rainloop_pab_tags_contacts WHERE id_tag IN ('.$sIDs.')'); - $this->prepareAndExecute('DELETE FROM rainloop_pab_tags WHERE id_user = :id_user AND id_tag IN ('.$sIDs.')', $aParams); - - return true; - } - - /** - * @param string $sEmail - * @param array|\RainLoop\Providers\AddressBook\Classes\Contact $mContactOrContacts - */ - private function populateContactsByTags($sEmail, &$mContactOrContacts) - { - $aContacts = array(); - if ($mContactOrContacts) - { - $aContacts = is_array($mContactOrContacts) ? $mContactOrContacts : array(&$mContactOrContacts); - } - - if (\is_array($aContacts) && 0 < \count($aContacts)) - { - $aIdContacts = array(); - $aIdTagsContacts = array(); - - $aTags = $this->GetContactTags($sEmail); - - if (\is_array($aTags) && 0 < \count($aTags)) - { - foreach ($aContacts as $oItem) - { - $aIdContacts[$oItem->IdContact] = true; - } - - if (0 < \count($aIdContacts)) - { - $sSql = 'SELECT id_tag, id_contact FROM rainloop_ab_tags_contacts WHERE id_contact IN ('.\implode(',', \array_keys($aIdContacts)).')'; - $oStmt = $this->prepareAndExecute($sSql); - - if ($oStmt) - { - $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); - if (\is_array($aFetch) && 0 < \count($aFetch)) - { - foreach ($aFetch as $aItem) - { - if ($aItem && isset($aItem['id_tag'], $aItem['id_contact'])) - { - $sID = (string) $aItem['id_contact']; - if (!isset($aIdTagsContacts[$sID])) - { - $aIdTagsContacts[$sID] = array(); - } - - $aIdTagsContacts[$sID][] = (string) $aItem['id_tag']; - } - } - } - - unset($aFetch); - } - - if (0 < \count($aIdTagsContacts)) - { - $fFindFunc = function ($sID) use ($aTags) { - - foreach ($aTags as $oItem) - { - if ($oItem && (string) $oItem->IdContactTag === $sID) - { - return\trim($oItem->Name); - } - } - - return ''; - }; - - foreach ($aContacts as &$oItem) - { - $sID = $oItem ? (string) $oItem->IdContact : ''; - if (0 < \strlen($sID) && isset($aIdTagsContacts[$sID]) && \is_array($aIdTagsContacts[$sID])) - { - $aNames = array(); - foreach ($aIdTagsContacts[$sID] as $sSubID) - { - $sName = $fFindFunc($sSubID); - if (0 < \strlen($sName)) - { - $aNames[] = $sName; - } - } - - $oItem->Tags = $aNames; - } - } - } - } - } - } - } - /** * @param string $sEmail * @param int $iOffset = 0 @@ -846,13 +636,8 @@ class PdoAddressBook if (0 < \strlen($sSearch)) { $sCustomSearch = $this->specialConvertSearchValueCustomPhone($sSearch); - if ('%%' === $sCustomSearch) - { - // TODO fix this - $sCustomSearch = ''; - } - $sSearchTypes = implode(',', array( + $sSearchTypes = \implode(',', array( PropertyType::EMAIl, PropertyType::FIRST_NAME, PropertyType::LAST_NAME, PropertyType::NICK_NAME, PropertyType::PHONE, PropertyType::WEB_PAGE )); @@ -1012,62 +797,6 @@ class PdoAddressBook } } - $this->populateContactsByTags($sEmail, $aResult); - return $aResult; - } - - /** - * @param string $sEmail - * @param bool $bCache = true - * - * @return array - */ - public function GetContactTags($sEmail, $bCache = true) - { - if ($bCache && isset($this->aTagsCache[$sEmail])) - { - return $this->aTagsCache[$sEmail]; - } - - $this->SyncDatabase(); - - $iUserID = $this->getUserId($sEmail); - - $sSql = 'SELECT id_tag, id_user, tag_name FROM rainloop_ab_tags WHERE id_user = :id_user ORDER BY tag_name ASC'; - - $aParams = array( - ':id_user' => array($iUserID, \PDO::PARAM_INT) - ); - - $aResult = array(); - $oStmt = $this->prepareAndExecute($sSql, $aParams); - if ($oStmt) - { - $aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC); - if (\is_array($aFetch) && 0 < \count($aFetch)) - { - foreach ($aFetch as $aItem) - { - $iIdContactTag = $aItem && isset($aItem['id_tag']) ? (int) $aItem['id_tag'] : 0; - if (0 < $iIdContactTag) - { - $oContactTag = new \RainLoop\Providers\AddressBook\Classes\Tag(); - - $oContactTag->IdContactTag = (string) $iIdContactTag; - $oContactTag->Name = isset($aItem['tag_name']) ? (string) $aItem['tag_name'] : ''; - $oContactTag->ReadOnly = $iUserID !== (isset($aItem['id_user']) ? (int) $aItem['id_user'] : 0); - - $aResult[] = $oContactTag; - } - } - } - } - - if ($bCache) - { - $this->aTagsCache[$sEmail] = $aResult; - } - return $aResult; } @@ -1171,11 +900,6 @@ class PdoAddressBook } } - if ($oContact) - { - $this->populateContactsByTags($sEmail, $oContact); - } - return $oContact; } @@ -1609,27 +1333,6 @@ CREATE TABLE IF NOT EXISTS rainloop_ab_properties ( )/*!40000 ENGINE=INNODB *//*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; -CREATE TABLE IF NOT EXISTS rainloop_ab_tags ( - - id_tag int UNSIGNED NOT NULL AUTO_INCREMENT, - id_user int UNSIGNED NOT NULL, - tag_name varchar(255) NOT NULL, - - PRIMARY KEY(id_tag), - INDEX id_user_rainloop_ab_tags_index (id_user), - INDEX id_user_name_rainloop_ab_tags_index (id_user, tag_name) - -)/*!40000 ENGINE=INNODB *//*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; - -CREATE TABLE IF NOT EXISTS rainloop_ab_tags_contacts ( - - id_tag int UNSIGNED NOT NULL, - id_contact bigint UNSIGNED NOT NULL, - - INDEX id_tag_rainloop_ab_tags_contacts_index (id_tag), - INDEX id_contact_rainloop_ab_tags_contacts_index (id_contact) - -)/*!40000 ENGINE=INNODB */; MYSQLINITIAL; break; @@ -1662,23 +1365,6 @@ CREATE TABLE rainloop_ab_properties ( CREATE INDEX id_user_rainloop_ab_properties_index ON rainloop_ab_properties (id_user); CREATE INDEX id_user_id_contact_rainloop_ab_properties_index ON rainloop_ab_properties (id_user, id_contact); -CREATE TABLE rainloop_ab_tags ( - id_tag serial PRIMARY KEY, - id_user integer NOT NULL, - tag_name varchar(255) NOT NULL -); - -CREATE INDEX id_user_rainloop_ab_tags_index ON rainloop_ab_tags (id_user); -CREATE INDEX id_user_name_rainloop_ab_tags_index ON rainloop_ab_tags (id_user, tag_name); - -CREATE TABLE rainloop_ab_tags_contacts ( - id_tag integer NOT NULL, - id_contact integer NOT NULL -); - -CREATE INDEX id_tag_rainloop_ab_tags_index ON rainloop_ab_tags_contacts (id_tag); -CREATE INDEX id_contact_rainloop_ab_tags_index ON rainloop_ab_tags_contacts (id_contact); - POSTGRESINITIAL; break; @@ -1711,23 +1397,6 @@ CREATE TABLE rainloop_ab_properties ( CREATE INDEX id_user_rainloop_ab_properties_index ON rainloop_ab_properties (id_user); CREATE INDEX id_user_id_contact_rainloop_ab_properties_index ON rainloop_ab_properties (id_user, id_contact); -CREATE TABLE rainloop_ab_tags ( - id_tag integer NOT NULL PRIMARY KEY, - id_user integer NOT NULL, - tag_name text NOT NULL -); - -CREATE INDEX id_user_rainloop_ab_tags_index ON rainloop_ab_tags (id_user); -CREATE INDEX id_user_name_rainloop_ab_tags_index ON rainloop_ab_tags (id_user, tag_name); - -CREATE TABLE rainloop_ab_tags_contacts ( - id_tag integer NOT NULL, - id_contact integer NOT NULL -); - -CREATE INDEX id_tag_rainloop_ab_tags_index ON rainloop_ab_tags_contacts (id_tag); -CREATE INDEX id_contact_rainloop_ab_tags_index ON rainloop_ab_tags_contacts (id_contact); - SQLITEINITIAL; break; } @@ -1836,7 +1505,8 @@ SQLITEINITIAL; */ private function specialConvertSearchValueCustomPhone($sSearch) { - return '%'.\preg_replace('/[^\d]/', '', $sSearch).'%'; + $sResult = '%'.\preg_replace('/[^\d]/', '', $sSearch).'%'; + return '%%' === $sResult ? '' : $sResult; } /** diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/ChangePassword.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/ChangePassword.php index 7a6d23e76..b344b227c 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/ChangePassword.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/ChangePassword.php @@ -22,11 +22,11 @@ class ChangePassword extends \RainLoop\Providers\AbstractProvider /** * @param \RainLoop\Actions $oActions * @param \RainLoop\Providers\ChangePassword\ChangePasswordInterface|null $oDriver = null - * @param bool $bCheckWeak = false + * @param bool $bCheckWeak = true * * @return void */ - public function __construct($oActions, $oDriver = null, $bCheckWeak = false) + public function __construct($oActions, $oDriver = null, $bCheckWeak = true) { $this->oActions = $oActions; $this->oDriver = $oDriver; @@ -61,17 +61,15 @@ class ChangePassword extends \RainLoop\Providers\AbstractProvider throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CurrentPasswordIncorrect); } - if (4 > \strlen($sNewPassword)) + $sPasswordForCheck = \trim($sNewPassword); + if (6 > \strlen($sPasswordForCheck)) { throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::NewPasswordShort); } - if ($this->bCheckWeak) + if (!\MailSo\Base\Utils::PasswordWeaknessCheck($sPasswordForCheck)) { - if (7 > \strlen($sNewPassword)) - { - throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::NewPasswordWeak); - } + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::NewPasswordWeak); } if (!$this->oDriver->ChangePassword($oAccount, $sPrevPassword, $sNewPassword)) @@ -95,20 +93,4 @@ class ChangePassword extends \RainLoop\Providers\AbstractProvider { return $this->oDriver instanceof \RainLoop\Providers\ChangePassword\ChangePasswordInterface; } - - /** - * @param string $sPassword - * @return bool - */ - public function weaknessCheck($sPassword) - { - $sPassword = \trim($sPassword); - if (4 > \strlen($sPassword)) - { - return false; - } - - $sLine = ' password 123.456 12345678 abc123 qwerty monkey letmein dragon 111.111 baseball iloveyou trustno1 1234567 sunshine master 123.123 welcome shadow ashley football jesus michael ninja mustang password1 123456 123456789 qwerty 111111 1234567 666666 12345678 7777777 123321 654321 1234567890 123123 555555 vkontakte gfhjkm 159753 777777 temppassword qazwsx 1q2w3e 1234 112233 121212 qwertyuiop qq18ww899 987654321 12345 zxcvbn zxcvbnm 999999 samsung ghbdtn 1q2w3e4r 1111111 123654 159357 131313 qazwsxedc 123qwe 222222 asdfgh 333333 9379992 asdfghjkl 4815162342 12344321 88888888 11111111 knopka 789456 qwertyu 1q2w3e4r5t iloveyou vfhbyf marina password qweasdzxc 10203 987654 yfnfif cjkysirj nikita 888888 vfrcbv k.,jdm qwertyuiop[] qwe123 qweasd natasha 123123123 fylhtq q1w2e3 stalker 1111111111 q1w2e3r4 nastya 147258369 147258 fyfcnfcbz 1234554321 1qaz2wsx andrey 111222 147852 genius sergey 7654321 232323 123789 fktrcfylh spartak admin test 123 azerty abc123 lol123 easytocrack1 hello saravn holysh!t test123 tundra_cool2 456 dragon thomas killer root 1111 pass master aaaaaa a monkey daniel asdasd e10adc3949ba59abbe56e057f20f883e changeme computer jessica letmein mirage loulou lol superman shadow admin123 secret administrator sophie kikugalanetroot doudou liverpool hallo sunshine charlie parola 100827092 michael andrew password1 fuckyou matrix cjmasterinf internet hallo123 eminem demo gewinner pokemon abcd1234 guest ngockhoa martin sandra asdf hejsan george qweqwe lollipop lovers q1q1q1 tecktonik naruto 12 password12 password123 password1234 password12345 password123456 password1234567 password12345678 password123456789 000000 maximius 123abc baseball1 football1 soccer princess slipknot 11111 nokia super star 666999 12341234 1234321 135790 159951 212121 zzzzzz 121314 134679 142536 19921992 753951 7007 1111114 124578 19951995 258456 qwaszx zaqwsx 55555 77777 54321 qwert 22222 33333 99999 88888 66666 '; - return false === \strpos($sLine, \strtolower($sPassword)); - } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/ChangePassword/ChangePasswordInterface.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/ChangePassword/ChangePasswordInterface.php index 4e1c20561..faecf5707 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/ChangePassword/ChangePasswordInterface.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/ChangePassword/ChangePasswordInterface.php @@ -1,22 +1,22 @@ -oDriver = $oDriver; + $this->oPlugins = $oPlugins; $this->bAdmin = $this->oDriver instanceof \RainLoop\Providers\Domain\DomainAdminInterface; } @@ -38,19 +45,25 @@ class Domain extends \RainLoop\Providers\AbstractProvider * @param bool $bFindWithWildCard = false * @param bool $bCheckDisabled = true * - * @return \RainLoop\Domain|null + * @return \RainLoop\Model\Domain|null */ public function Load($sName, $bFindWithWildCard = false, $bCheckDisabled = true) { - return $this->oDriver->Load($sName, $bFindWithWildCard, $bCheckDisabled); + $oDomain = $this->oDriver->Load($sName, $bFindWithWildCard, $bCheckDisabled); + if ($oDomain instanceof \RainLoop\Model\Domain) + { + $this->oPlugins->RunHook('filter.domain', array(&$oDomain)); + } + + return $oDomain; } /** - * @param \RainLoop\Domain $oDomain + * @param \RainLoop\Model\Domain $oDomain * * @return bool */ - public function Save(\RainLoop\Domain $oDomain) + public function Save(\RainLoop\Model\Domain $oDomain) { return $this->bAdmin ? $this->oDriver->Save($oDomain) : false; } @@ -102,7 +115,7 @@ class Domain extends \RainLoop\Providers\AbstractProvider * @param \RainLoop\Actions $oActions * @param string $sNameForTest = '' * - * @return \RainLoop\Domain | null + * @return \RainLoop\Model\Domain | null */ public function LoadOrCreateNewFromAction(\RainLoop\Actions $oActions, $sNameForTest = '') { @@ -116,11 +129,17 @@ class Domain extends \RainLoop\Providers\AbstractProvider $iIncPort = (int) $oActions->GetActionParam('IncPort', 143); $iIncSecure = (int) $oActions->GetActionParam('IncSecure', \MailSo\Net\Enumerations\ConnectionSecurityType::NONE); $bIncShortLogin = '1' === (string) $oActions->GetActionParam('IncShortLogin', '0'); + $bUseSieve = '1' === (string) $oActions->GetActionParam('UseSieve', '0'); + $bSieveAllowRaw = '1' === (string) $oActions->GetActionParam('SieveAllowRaw', '0'); + $sSieveHost = (string) $oActions->GetActionParam('SieveHost', ''); + $iSievePort = (int) $oActions->GetActionParam('SievePort', 4190); + $iSieveSecure = (int) $oActions->GetActionParam('SieveSecure', \MailSo\Net\Enumerations\ConnectionSecurityType::NONE); $sOutHost = (string) $oActions->GetActionParam('OutHost', ''); $iOutPort = (int) $oActions->GetActionParam('OutPort', 25); $iOutSecure = (int) $oActions->GetActionParam('OutSecure', \MailSo\Net\Enumerations\ConnectionSecurityType::NONE); $bOutShortLogin = '1' === (string) $oActions->GetActionParam('OutShortLogin', '0'); $bOutAuth = '1' === (string) $oActions->GetActionParam('OutAuth', '1'); + $bOutUsePhpMail = '1' === (string) $oActions->GetActionParam('OutUsePhpMail', '0'); $sWhiteList = (string) $oActions->GetActionParam('WhiteList', ''); if (0 < \strlen($sName) && 0 < strlen($sNameForTest) && false === \strpos($sName, '*')) @@ -131,7 +150,7 @@ class Domain extends \RainLoop\Providers\AbstractProvider if (0 < strlen($sName) || 0 < strlen($sNameForTest)) { $oDomain = 0 < strlen($sNameForTest) ? null : $this->Load($sName); - if ($oDomain instanceof \RainLoop\Domain) + if ($oDomain instanceof \RainLoop\Model\Domain) { if ($bCreate) { @@ -141,18 +160,25 @@ class Domain extends \RainLoop\Providers\AbstractProvider { $oDomain->UpdateInstance( $sIncHost, $iIncPort, $iIncSecure, $bIncShortLogin, - $sOutHost, $iOutPort, $iOutSecure, $bOutShortLogin, $bOutAuth, + $bUseSieve, $sSieveHost, $iSievePort, $iSieveSecure, + $sOutHost, $iOutPort, $iOutSecure, $bOutShortLogin, $bOutAuth, $bOutUsePhpMail, $sWhiteList); } } else { - $oDomain = \RainLoop\Domain::NewInstance(0 < strlen($sNameForTest) ? $sNameForTest : $sName, + $oDomain = \RainLoop\Model\Domain::NewInstance(0 < strlen($sNameForTest) ? $sNameForTest : $sName, $sIncHost, $iIncPort, $iIncSecure, $bIncShortLogin, - $sOutHost, $iOutPort, $iOutSecure, $bOutShortLogin, $bOutAuth, + $bUseSieve, $sSieveHost, $iSievePort, $iSieveSecure, + $sOutHost, $iOutPort, $iOutSecure, $bOutShortLogin, $bOutAuth, $bOutUsePhpMail, $sWhiteList); } } + + if ($oDomain) + { + $oDomain->SetSieveAllowRaw($bSieveAllowRaw); + } } return $oDomain; diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php index 57c69e3d5..3ce150c5e 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DefaultDomain.php @@ -147,7 +147,7 @@ class DefaultDomain implements \RainLoop\Providers\Domain\DomainAdminInterface * @param bool $bFindWithWildCard = false * @param bool $bCheckDisabled = true * - * @return \RainLoop\Domain|null + * @return \RainLoop\Model\Domain|null */ public function Load($sName, $bFindWithWildCard = false, $bCheckDisabled = true) { @@ -155,18 +155,18 @@ class DefaultDomain implements \RainLoop\Providers\Domain\DomainAdminInterface $sDisabled = ''; $sFoundedValue = ''; - + $sRealFileName = $this->codeFileName($sName); if (\file_exists($this->sDomainPath.'/disabled')) { $sDisabled = @\file_get_contents($this->sDomainPath.'/disabled'); } - + if (\file_exists($this->sDomainPath.'/'.$sRealFileName.'.ini') && (!$bCheckDisabled || 0 === \strlen($sDisabled) || false === \strpos(','.$sDisabled.',', ','.\MailSo\Base\Utils::IdnToAscii($sName, true).','))) { - $aDomain = @\parse_ini_file($this->sDomainPath.'/'.$sRealFileName.'.ini'); + $aDomain = \RainLoop\Utils::CustomParseIniFile($this->sDomainPath.'/'.$sRealFileName.'.ini'); // fix misspellings (#119) if (\is_array($aDomain)) { @@ -192,7 +192,7 @@ class DefaultDomain implements \RainLoop\Providers\Domain\DomainAdminInterface } //--- - $mResult = \RainLoop\Domain::NewInstanceFromDomainConfigArray($sName, $aDomain); + $mResult = \RainLoop\Model\Domain::NewInstanceFromDomainConfigArray($sName, $aDomain); } else if ($bFindWithWildCard) { @@ -214,11 +214,11 @@ class DefaultDomain implements \RainLoop\Providers\Domain\DomainAdminInterface } /** - * @param \RainLoop\Domain $oDomain + * @param \RainLoop\Model\Domain $oDomain * * @return bool */ - public function Save(\RainLoop\Domain $oDomain) + public function Save(\RainLoop\Model\Domain $oDomain) { $sRealFileName = $this->codeFileName($oDomain->Name()); diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DomainAdminInterface.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DomainAdminInterface.php index d1ed98d48..c33ba664a 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DomainAdminInterface.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DomainAdminInterface.php @@ -18,16 +18,16 @@ interface DomainAdminInterface extends DomainInterface * @param bool $bFindWithWildCard = false * @param bool $bCheckDisabled = true * - * @return \RainLoop\Domain|null + * @return \RainLoop\Model\Domain|null */ public function Load($sName, $bFindWithWildCard = false, $bCheckDisabled = true); /** - * @param \RainLoop\Domain $oDomain + * @param \RainLoop\Model\Domain $oDomain * * @return bool */ - public function Save(\RainLoop\Domain $oDomain); + public function Save(\RainLoop\Model\Domain $oDomain); /** * @param string $sName diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DomainInterface.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DomainInterface.php index 57042ca1d..97b0e3d89 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DomainInterface.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Domain/DomainInterface.php @@ -1,7 +1,7 @@ -oDriver = $oDriver; } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * @param resource $rSource * @@ -30,7 +30,7 @@ class Files extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * @param string $sSource * @@ -42,7 +42,7 @@ class Files extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * @param string $sOpenMode = 'rb' * @@ -54,7 +54,7 @@ class Files extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return string | bool @@ -65,7 +65,7 @@ class Files extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return bool @@ -76,7 +76,7 @@ class Files extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return int|bool @@ -87,7 +87,7 @@ class Files extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return bool @@ -107,11 +107,20 @@ class Files extends \RainLoop\Providers\AbstractProvider return $this->oDriver ? $this->oDriver->GC($iTimeToClearInHours) : false; } + /** + * @return bool + */ + public function CloseAllOpenedFiles() + { + return $this->oDriver && \method_exists($this->oDriver, 'CloseAllOpenedFiles') ? + $this->oDriver->CloseAllOpenedFiles() : false; + } + /** * @return bool */ public function IsActive() { - return $this->oDriver instanceof \RainLoop\Providers\Files\FilesInterface; + return $this->oDriver instanceof \RainLoop\Providers\Files\IFiles; } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/DefaultStorage.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FileStorage.php similarity index 66% rename from rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/DefaultStorage.php rename to rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FileStorage.php index 293813e33..bc2591523 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/DefaultStorage.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FileStorage.php @@ -2,8 +2,13 @@ namespace RainLoop\Providers\Files; -class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface +class FileStorage implements \RainLoop\Providers\Files\IFiles { + /** + * @var array + */ + private $aResources; + /** * @var string */ @@ -16,11 +21,12 @@ class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface */ public function __construct($sStoragePath) { + $this->aResources = array(); $this->sDataPath = \rtrim(\trim($sStoragePath), '\\/'); } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * @param resource $rSource * @@ -42,7 +48,7 @@ class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * @param string $sSource * @@ -55,7 +61,7 @@ class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * @param string $sOpenMode = 'rb' * @@ -70,13 +76,18 @@ class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface if ($bCreate || \file_exists($sFileName)) { $mResult = @\fopen($sFileName, $sOpenMode); + + if (\is_resource($mResult)) + { + $this->aResources[$sFileName] = $mResult; + } } - + return $mResult; } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return string|bool @@ -94,7 +105,7 @@ class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return bool @@ -105,14 +116,19 @@ class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface $sFileName = $this->generateFileName($oAccount, $sKey); if (\file_exists($sFileName)) { + if (isset($this->aResources[$sFileName]) && \is_resource($this->aResources[$sFileName])) + { + @\fclose($this->aResources[$sFileName]); + } + $mResult = @\unlink($sFileName); } - + return $mResult; } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return int|bool @@ -125,12 +141,12 @@ class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface { $mResult = \filesize($sFileName); } - + return $mResult; } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return bool @@ -157,7 +173,26 @@ class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface } /** - * @param \RainLoop\Account $oAccount + * @return bool + */ + public function CloseAllOpenedFiles() + { + if (\is_array($this->aResources) && 0 < \count($this->aResources)) + { + foreach ($this->aResources as $sFileName => $rFile) + { + if (!empty($sFileName) && \is_resource($rFile)) + { + @\fclose($rFile); + } + } + } + + return true; + } + + /** + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * @param bool $bMkDir = false * @@ -165,13 +200,28 @@ class DefaultStorage implements \RainLoop\Providers\Files\FilesInterface */ private function generateFileName($oAccount, $sKey, $bMkDir = false) { - $sEmail = \preg_replace('/[^a-z0-9\-\.@]+/', '_', - ('' === $oAccount->ParentEmail() ? '' : $oAccount->ParentEmail().'/').$oAccount->Email()); + $sEmail = $sSubEmail = ''; + if ($oAccount instanceof \RainLoop\Model\Account) + { + $sEmail = \preg_replace('/[^a-z0-9\-\.@]+/', '_', $oAccount->ParentEmailHelper()); + if ($oAccount->IsAdditionalAccount()) + { + $sSubEmail = \preg_replace('/[^a-z0-9\-\.@]+/', '_', $oAccount->Email()); + } + } + + if (empty($sEmail)) + { + $sEmail = '__unknown__'; + } $sKeyPath = \sha1($sKey); $sKeyPath = \substr($sKeyPath, 0, 2).'/'.\substr($sKeyPath, 2, 2).'/'.$sKeyPath; - $sFilePath = $this->sDataPath.'/'.rtrim(substr($sEmail, 0, 2), '@').'/'.$sEmail.'/'.$sKeyPath; + $sFilePath = $this->sDataPath.'/'. + \str_pad(\rtrim(\substr($sEmail, 0, 2), '@'), 2, '_').'/'.$sEmail.'/'. + (0 < \strlen($sSubEmail) ? $sSubEmail.'/' : ''). + $sKeyPath; if ($bMkDir && !empty($sFilePath) && !@\is_dir(\dirname($sFilePath))) { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FilesInterface.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/IFiles.php similarity index 78% rename from rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FilesInterface.php rename to rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/IFiles.php index a0a9e0a87..cb7aaceae 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FilesInterface.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/IFiles.php @@ -2,10 +2,10 @@ namespace RainLoop\Providers\Files; -interface FilesInterface +interface IFiles { /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * @param resource $rSource * @@ -23,7 +23,7 @@ interface FilesInterface public function MoveUploadedFile($oAccount, $sKey, $sSource); /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * @param string $sOpenMode = 'rb' * @@ -32,7 +32,7 @@ interface FilesInterface public function GetFile($oAccount, $sKey, $sOpenMode = 'rb'); /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return string|bool @@ -40,7 +40,7 @@ interface FilesInterface public function GetFileName($oAccount, $sKey); /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return bool @@ -48,7 +48,7 @@ interface FilesInterface public function Clear($oAccount, $sKey); /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return int | bool @@ -56,7 +56,7 @@ interface FilesInterface public function FileSize($oAccount, $sKey); /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param string $sKey * * @return bool diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php new file mode 100644 index 000000000..ce256cbe4 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters.php @@ -0,0 +1,84 @@ +oDriver = $oDriver instanceof \RainLoop\Providers\Filters\FiltersInterface ? $oDriver : null; + } + + /** + * @param \RainLoop\Account $oAccount + * @param bool $bAllowRaw = false + * + * @return array + */ + public function Load($oAccount, $bAllowRaw = false) + { + try + { + return $this->IsActive() ? $this->oDriver->Load($oAccount, $bAllowRaw) : array(); + } + catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ConnectionError, $oException); + } + catch (\Exception $oException) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetFilters, $oException); + } + + return false; + } + + /** + * @param \RainLoop\Account $oAccount + * @param array $aFilters + * @param string $sRaw = '' + * @param bool $bRawIsActive = false + * + * @return bool + */ + public function Save($oAccount, $aFilters, $sRaw = '', $bRawIsActive = false) + { + try + { + return $this->IsActive() ? $this->oDriver->Save( + $oAccount, $aFilters, $sRaw, $bRawIsActive) : false; + } + catch (\MailSo\Net\Exceptions\SocketCanNotConnectToHostException $oException) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::ConnectionError, $oException); + } + catch (\MailSo\Sieve\Exceptions\NegativeResponseException $oException) + { + throw new \RainLoop\Exceptions\ClientException( + \RainLoop\Notifications::ClientViewError, $oException, + \implode("\r\n", $oException->GetResponses())); + } + catch (\Exception $oException) + { + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantSaveFilters, $oException); + } + + return false; + } + + /** + * @return bool + */ + public function IsActive() + { + return $this->oDriver instanceof \RainLoop\Providers\Filters\FiltersInterface; + } +} \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/Filter.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/Filter.php new file mode 100644 index 000000000..360a2c074 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/Filter.php @@ -0,0 +1,284 @@ +Clear(); + } + + public function Clear() + { + $this->sID = ''; + $this->sName = ''; + + $this->bEnabled = true; + + $this->aConditions = array(); + + $this->sConditionsType = \RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY; + + $this->sActionType = \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO; + $this->sActionValue = ''; + $this->sActionValueSecond = ''; + $this->sActionValueThird = ''; + + $this->bMarkAsRead = false; + $this->bKeep = true; + $this->bStop = true; + } + + /** + * @return string + */ + public function ID() + { + return $this->sID; + } + + /** + * @return bool + */ + public function Enabled() + { + return $this->bEnabled; + } + + /** + * @return string + */ + public function Name() + { + return $this->sName; + } + + /** + * @return array + */ + public function Conditions() + { + return $this->aConditions; + } + + /** + * @return string + */ + public function ConditionsType() + { + return $this->sConditionsType; + } + + /** + * @return string + */ + public function ActionType() + { + return $this->sActionType; + } + + /** + * @return string + */ + public function ActionValue() + { + return $this->sActionValue; + } + + /** + * @return string + */ + public function ActionValueSecond() + { + return $this->sActionValueSecond; + } + + /** + * @return string + */ + public function ActionValueThird() + { + return $this->sActionValueThird; + } + + /** + * @return bool + */ + public function MarkAsRead() + { + return $this->bMarkAsRead; + } + + /** + * @return bool + */ + public function Stop() + { + return $this->bStop; + } + + /** + * @return bool + */ + public function Keep() + { + return $this->bKeep; + } + + /** + * @return string + */ + public function serializeToJson() + { + return \json_encode($this->ToSimpleJSON()); + } + + /** + * @param string $sFilterJson + */ + public function unserializeFromJson($sFilterJson) + { + $aFilterJson = \json_decode(\trim($sFilterJson), true); + if (\is_array($aFilterJson)) + { + return $this->FromJSON($aFilterJson); + } + + return false; + } + + /** + * @param array $aFilter + * + * @return array + */ + public function FromJSON($aFilter) + { + if (\is_array($aFilter)) + { + $this->sID = isset($aFilter['ID']) ? $aFilter['ID'] : ''; + $this->sName = isset($aFilter['Name']) ? $aFilter['Name'] : ''; + + $this->bEnabled = isset($aFilter['Enabled']) ? '1' === (string) $aFilter['Enabled'] : true; + + $this->sConditionsType = isset($aFilter['ConditionsType']) ? $aFilter['ConditionsType'] : + \RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY; + + $this->sActionType = isset($aFilter['ActionType']) ? $aFilter['ActionType'] : + \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO; + + $this->sActionValue = isset($aFilter['ActionValue']) ? $aFilter['ActionValue'] : ''; + $this->sActionValueSecond = isset($aFilter['ActionValueSecond']) ? $aFilter['ActionValueSecond'] : ''; + $this->sActionValueThird = isset($aFilter['ActionValueThird']) ? $aFilter['ActionValueThird'] : ''; + + $this->bKeep = isset($aFilter['Keep']) ? '1' === (string) $aFilter['Keep'] : true; + $this->bStop = isset($aFilter['Stop']) ? '1' === (string) $aFilter['Stop'] : true; + $this->bMarkAsRead = isset($aFilter['MarkAsRead']) ? '1' === (string) $aFilter['MarkAsRead'] : false; + + $this->aConditions = \RainLoop\Providers\Filters\Classes\FilterCondition::CollectionFromJSON( + isset($aFilter['Conditions']) ? $aFilter['Conditions'] : array()); + + return true; + } + + return false; + } + + /** + * @param bool $bAjax = false + * + * @return array + */ + public function ToSimpleJSON($bAjax = false) + { + $aConditions = array(); + foreach ($this->Conditions() as $oItem) + { + if ($oItem) + { + $aConditions[] = $oItem->ToSimpleJSON($bAjax); + } + } + + return array( + 'ID' => $this->ID(), + 'Enabled' => $this->Enabled(), + 'Name' => $this->Name(), + 'Conditions' => $aConditions, + 'ConditionsType' => $this->ConditionsType(), + 'ActionType' => $this->ActionType(), + 'ActionValue' => $this->ActionValue(), + 'ActionValueSecond' => $this->ActionValueSecond(), + 'ActionValueThird' => $this->ActionValueThird(), + 'Keep' => $this->Keep(), + 'Stop' => $this->Stop(), + 'MarkAsRead' => $this->MarkAsRead() + ); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/FilterCondition.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/FilterCondition.php new file mode 100644 index 000000000..b56e92fdf --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Classes/FilterCondition.php @@ -0,0 +1,118 @@ +Clear(); + } + + public function Clear() + { + $this->sField = \RainLoop\Providers\Filters\Enumerations\ConditionField::FROM; + $this->sType = \RainLoop\Providers\Filters\Enumerations\ConditionType::EQUAL_TO; + $this->sValue = ''; + } + + /** + * @return string + */ + public function Field() + { + return $this->sField; + } + + /** + * @return string + */ + public function Type() + { + return $this->sType; + } + + /** + * @return string + */ + public function Value() + { + return $this->sValue; + } + + /** + * @param array $aData + * + * @return array + */ + public function FromJSON($aData) + { + if (\is_array($aData)) + { + $this->sField = isset($aData['Field']) ? $aData['Field'] : + \RainLoop\Providers\Filters\Enumerations\ConditionField::FROM; + + $this->sType = isset($aData['Type']) ? $aData['Type'] : + \RainLoop\Providers\Filters\Enumerations\ConditionType::EQUAL_TO; + + $this->sValue = isset($aData['Value']) ? $aData['Value'] : ''; + + return true; + } + + return false; + } + + /** + * @param bool $bAjax = false + * + * @return array + */ + public function ToSimpleJSON($bAjax = false) + { + return array( + 'Field' => $this->Field(), + 'Type' => $this->Type(), + 'Value' => $this->Value() + ); + } + + /** + * @return array + */ + public static function CollectionFromJSON($aCollection) + { + $aResult = array(); + if (\is_array($aCollection) && 0 < \count($aCollection)) + { + foreach ($aCollection as $aItem) + { + if (\is_array($aItem) && 0 < \count($aItem)) + { + $oItem = new \RainLoop\Providers\Filters\Classes\FilterCondition(); + if ($oItem->FromJSON($aItem)) + { + $aResult[] = $oItem; + } + } + } + } + + return $aResult; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Enumerations/ActionType.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Enumerations/ActionType.php new file mode 100644 index 000000000..c5b87d9b3 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Filters/Enumerations/ActionType.php @@ -0,0 +1,13 @@ +oLogger = null; + + $this->oPlugins = $oPlugins; + $this->oConfig = $oConfig; + + $this->bUtf8FolderName = !!$this->oConfig->Get('labs', 'sieve_utf8_folder_name', true); + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param bool $bAllowRaw = false + * + * @return array + */ + public function Load($oAccount, $bAllowRaw = false) + { + $sRaw = ''; + + $bBasicIsActive = false; + $bRawIsActive = false; + + $aModules = array(); + $aFilters = array(); + + $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->oLogger); + + if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) + { + $aModules = $oSieveClient->Modules(); + $aList = $oSieveClient->ListScripts(); + + if (\is_array($aList) && 0 < \count($aList)) + { + if (isset($aList[self::SIEVE_FILE_NAME])) + { + $bBasicIsActive = !!$aList[self::SIEVE_FILE_NAME]; + $sS = $oSieveClient->GetScript(self::SIEVE_FILE_NAME); + if ($sS) + { + $aFilters = $this->fileStringToCollection($sS); + } + } + + if ($bAllowRaw && isset($aList[self::SIEVE_FILE_NAME_RAW])) + { + $bRawIsActive = !!$aList[self::SIEVE_FILE_NAME_RAW]; + $sRaw = \trim($oSieveClient->GetScript(self::SIEVE_FILE_NAME_RAW)); + } + } + + $oSieveClient->LogoutAndDisconnect(); + } + + return array( + 'RawIsAllow' => $bAllowRaw, + 'RawIsActive' => $bRawIsActive, + 'Raw' => $bAllowRaw ? $sRaw : '', + 'Filters' => !$bBasicIsActive && !$bRawIsActive ? array() : $aFilters, + 'Capa' => $bAllowRaw ? $aModules : array(), + 'Modules' => array( + 'redirect' => \in_array('fileinto', $aModules), + 'moveto' => \in_array('fileinto', $aModules), + 'reject' => \in_array('reject', $aModules), + 'vacation' => \in_array('vacation', $aModules), + 'markasread' => \in_array('imap4flags', $aModules) + ) + ); + } + + /** + * @param \RainLoop\Model\Account $oAccount + * @param array $aFilters + * @param string $sRaw = '' + * @param bool $bRawIsActive = false + * + * @return bool + */ + public function Save($oAccount, $aFilters, $sRaw = '', $bRawIsActive = false) + { + $oSieveClient = \MailSo\Sieve\ManageSieveClient::NewInstance()->SetLogger($this->oLogger); + + if ($oAccount->SieveConnectAndLoginHelper($this->oPlugins, $oSieveClient, $this->oConfig)) + { + $aList = $oSieveClient->ListScripts(); + + if ($bRawIsActive) + { + if (!empty($sRaw)) + { + $oSieveClient->PutScript(self::SIEVE_FILE_NAME_RAW, $sRaw); + $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME_RAW); + } + else if (isset($aList[self::SIEVE_FILE_NAME_RAW])) + { + $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME_RAW); + } + } + else + { + $sUserFilter = $this->collectionToFileString($aFilters); + + if (!empty($sUserFilter)) + { + $oSieveClient->PutScript(self::SIEVE_FILE_NAME, $sUserFilter); + $oSieveClient->SetActiveScript(self::SIEVE_FILE_NAME); + } + else if (isset($aList[self::SIEVE_FILE_NAME])) + { + $oSieveClient->DeleteScript(self::SIEVE_FILE_NAME); + } + } + + $oSieveClient->LogoutAndDisconnect(); + + return true; + } + + return false; + } + + /** + * @param \RainLoop\Providers\Filters\Classes\FilterCondition $oCondition + * + * @return string + */ + private function conditionToSieveScript($oCondition) + { + $sResult = ''; + $sTypeWord = ''; + + $sValue = \trim($oCondition->Value()); + if (0 < strlen($sValue)) + { + switch ($oCondition->Type()) + { + case \RainLoop\Providers\Filters\Enumerations\ConditionType::NOT_EQUAL_TO: + $sResult .= 'not '; + case \RainLoop\Providers\Filters\Enumerations\ConditionType::EQUAL_TO: + $sTypeWord = ':is'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionType::NOT_CONTAINS: + $sResult .= 'not '; + case \RainLoop\Providers\Filters\Enumerations\ConditionType::CONTAINS: + $sTypeWord = ':contains'; + break; + } + + switch ($oCondition->Field()) + { + case \RainLoop\Providers\Filters\Enumerations\ConditionField::FROM: + $sResult .= 'header '.$sTypeWord.' ["From"]'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionField::RECIPIENT: + $sResult .= 'header '.$sTypeWord.' ["To", "CC"]'; + break; + case \RainLoop\Providers\Filters\Enumerations\ConditionField::SUBJECT: + $sResult .= 'header '.$sTypeWord.' ["Subject"]'; + break; + } + + if (\in_array($oCondition->Field(), array( + \RainLoop\Providers\Filters\Enumerations\ConditionField::FROM, + \RainLoop\Providers\Filters\Enumerations\ConditionField::RECIPIENT + )) && false !== \strpos($sValue, ',')) + { + $self = $this; + $aValue = \array_map(function ($sValue) use ($self) { + return '"'.$self->quote(\trim($sValue)).'"'; + }, \explode(',', $sValue)); + + $sResult .= ' ['.\trim(\implode(', ', $aValue)).']'; + } + else + { + $sResult .= ' "'.$this->quote($sValue).'"'; + } + + $sResult = \preg_replace('/[\s]+/u', ' ', $sResult); + } + else + { + $sResult = '/* @Error: empty condition value */ false'; + } + + return $sResult; + } + + /** + * @param \RainLoop\Providers\Filters\Classes\Filter $oFilter + * @param array $aCapa + * + * @return string + */ + private function filterToSieveScript($oFilter, &$aCapa) + { + $sNL = \RainLoop\Providers\Filters\SieveStorage::NEW_LINE; + $sTab = ' '; + + $bAll = false; + $aResult = array(); + + // Conditions + $aConditions = $oFilter->Conditions(); + if (\is_array($aConditions)) + { + if (1 < \count($aConditions)) + { + if (\RainLoop\Providers\Filters\Enumerations\ConditionsType::ANY === + $oFilter->ConditionsType()) + { + $aResult[] = 'if anyof('; + + $bTrim = false; + foreach ($aConditions as $oCond) + { + $bTrim = true; + $sCons = $this->conditionToSieveScript($oCond); + if (!empty($sCons)) + { + $aResult[] = $sTab.$sCons.','; + } + } + if ($bTrim) + { + $aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ','); + } + + $aResult[] = ')'; + } + else + { + $aResult[] = 'if allof('; + foreach ($aConditions as $oCond) + { + $aResult[] = $sTab.$this->conditionToSieveScript($oCond).','; + } + + $aResult[\count($aResult) - 1] = \rtrim($aResult[\count($aResult) - 1], ','); + $aResult[] = ')'; + } + } + else if (1 === \count($aConditions)) + { + $aResult[] = 'if '.$this->conditionToSieveScript($aConditions[0]).''; + } + else + { + $bAll = true; + } + } + + // actions + if (!$bAll) + { + $aResult[] = '{'; + } + else + { + $sTab = ''; + } + + if ($oFilter->MarkAsRead() && \in_array($oFilter->ActionType(), array( + \RainLoop\Providers\Filters\Enumerations\ActionType::NONE, + \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO, + \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD + ))) + { + $aCapa['imap4flags'] = true; + $aResult[] = $sTab.'addflag "\\\\Seen";'; + } + + switch ($oFilter->ActionType()) + { + case \RainLoop\Providers\Filters\Enumerations\ActionType::NONE: + $aResult[] = $sTab.'stop;'; + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::DISCARD: + $aResult[] = $sTab.'discard;'; + $aResult[] = $sTab.'stop;'; + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::VACATION: + $sValue = \trim($oFilter->ActionValue()); + $sValueSecond = \trim($oFilter->ActionValueSecond()); + $sValueThird = \trim($oFilter->ActionValueThird()); + if (0 < \strlen($sValue)) + { + $aCapa['vacation'] = true; + + $iDays = 1; + $sSubject = ''; + if (0 < \strlen($sValueSecond)) + { + $sSubject = ':subject "'.$this->quote( + \preg_replace('/[\s]+/u', ' ', $sValueSecond)).'" '; + } + + if (0 < \strlen($sValueThird) && \is_numeric($sValueThird) && 1 < (int) $sValueThird) + { + $iDays = (int) $sValueThird; + } + + $aResult[] = $sTab.'vacation :days '.$iDays.' '.$sSubject.'"'.$this->quote($sValue).'";'; + + if ($oFilter->Stop()) + { + $aResult[] = $sTab.'stop;'; + } + } + else + { + $aResult[] = $sTab.'# @Error (vacation): empty action value'; + } + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::REJECT: + $sValue = \trim($oFilter->ActionValue()); + if (0 < \strlen($sValue)) + { + $aCapa['reject'] = true; + + $aResult[] = $sTab.'reject "'.$this->quote($sValue).'";'; + $aResult[] = $sTab.'stop;'; + } + else + { + $aResult[] = $sTab.'# @Error (reject): empty action value'; + } + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::FORWARD: + $sValue = $oFilter->ActionValue(); + if (0 < \strlen($sValue)) + { + if ($oFilter->Keep()) + { + $aCapa['fileinto'] = true; + $aResult[] = $sTab.'fileinto "INBOX";'; + } + + $aResult[] = $sTab.'redirect "'.$this->quote($sValue).'";'; + $aResult[] = $sTab.'stop;'; + } + else + { + $aResult[] = $sTab.'# @Error (redirect): empty action value'; + } + break; + case \RainLoop\Providers\Filters\Enumerations\ActionType::MOVE_TO: + $sValue = $oFilter->ActionValue(); + if (0 < \strlen($sValue)) + { + $sFolderName = $sValue; // utf7-imap + if ($this->bUtf8FolderName) // to utf-8 + { + $sFolderName = \MailSo\Base\Utils::ConvertEncoding($sFolderName, + \MailSo\Base\Enumerations\Charset::UTF_7_IMAP, + \MailSo\Base\Enumerations\Charset::UTF_8); + } + + $aCapa['fileinto'] = true; + $aResult[] = $sTab.'fileinto "'.$this->quote($sFolderName).'";'; + $aResult[] = $sTab.'stop;'; + } + else + { + $aResult[] = $sTab.'# @Error (fileinto): empty action value'; + } + break; + } + + if (!$bAll) + { + $aResult[] = '}'; + } + + return \implode($sNL, $aResult); + } + + /** + * @param array $aFilters + * + * @return string + */ + private function collectionToFileString($aFilters) + { + $sNL = \RainLoop\Providers\Filters\SieveStorage::NEW_LINE; + + $aCapa = array(); + $aParts = array(); + + $aParts[] = '# This is RainLoop Webmail sieve script.'; + $aParts[] = '# Please don\'t change anything here.'; + $aParts[] = '# RAINLOOP:SIEVE'; + $aParts[] = ''; + + foreach ($aFilters as /* @var $oItem \RainLoop\Providers\Filters\Classes\Filter */ $oItem) + { + $aData = array(); + $aData[] = '/*'; + $aData[] = 'BEGIN:FILTER:'.$oItem->ID(); + $aData[] = 'BEGIN:HEADER'; + $aData[] = \chunk_split(\base64_encode($oItem->serializeToJson()), 74, $sNL).'END:HEADER'; + $aData[] = '*/'; + $aData[] = $oItem->Enabled() ? '' : '/* @Filter is disabled '; + $aData[] = $this->filterToSieveScript($oItem, $aCapa); + $aData[] = $oItem->Enabled() ? '' : '*/'; + $aData[] = '/* END:FILTER */'; + $aData[] = ''; + + $aParts[] = \implode($sNL, $aData); + } + + $aCapa = \array_keys($aCapa); + $sCapa = 0 < \count($aCapa) ? $sNL.'require '. + \str_replace('","', '", "', \json_encode($aCapa)).';'.$sNL : ''; + + return $sCapa.$sNL.\implode($sNL, $aParts).$sNL; + } + + /** + * @param string $sFileString + * + * @return array + */ + private function fileStringToCollection($sFileString) + { + $aResult = array(); + if (!empty($sFileString) && false !== \strpos($sFileString, 'RAINLOOP:SIEVE')) + { + $aMatch = array(); + if (\preg_match_all('/BEGIN:FILTER(.+?)BEGIN:HEADER(.+?)END:HEADER/s', $sFileString, $aMatch) && + isset($aMatch[2]) && \is_array($aMatch[2])) + { + foreach ($aMatch[2] as $sEncodedLine) + { + if (!empty($sEncodedLine)) + { + $sDecodedLine = \base64_decode(\preg_replace('/[\s]+/', '', $sEncodedLine)); + if (!empty($sDecodedLine)) + { + $oItem = new \RainLoop\Providers\Filters\Classes\Filter(); + if ($oItem && $oItem->unserializeFromJson($sDecodedLine)) + { + $aResult[] = $oItem; + } + } + } + } + } + } + + return $aResult; + } + + /** + * @param string $sValue + * + * @return string + */ + private function quote($sValue) + { + return \str_replace(array('\\', '"'), array('\\\\', '\\"'), \trim($sValue)); + } + + /** + * @param \MailSo\Log\Logger $oLogger + */ + public function SetLogger($oLogger) + { + $this->oLogger = $oLogger instanceof \MailSo\Log\Logger ? $oLogger : null; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings.php index 7c76db218..bddff7cfd 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings.php @@ -5,26 +5,26 @@ namespace RainLoop\Providers; class Settings extends \RainLoop\Providers\AbstractProvider { /** - * @var \RainLoop\Providers\Settings\SettingsInterface + * @var \RainLoop\Providers\Settings\ISettings */ private $oDriver; /** - * @param \RainLoop\Providers\Settings\SettingsInterface $oDriver + * @param \RainLoop\Providers\Settings\ISettings $oDriver * * @return void */ - public function __construct(\RainLoop\Providers\Settings\SettingsInterface $oDriver) + public function __construct(\RainLoop\Providers\Settings\ISettings $oDriver) { $this->oDriver = $oDriver; } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return \RainLoop\Settings */ - public function Load(\RainLoop\Account $oAccount) + public function Load(\RainLoop\Model\Account $oAccount) { $oSettings = new \RainLoop\Settings(); $oSettings->InitData($this->oDriver->Load($oAccount)); @@ -32,31 +32,21 @@ class Settings extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param \RainLoop\Settings $oSettings * * @return bool */ - public function Save(\RainLoop\Account $oAccount, \RainLoop\Settings $oSettings) + public function Save(\RainLoop\Model\Account $oAccount, \RainLoop\Settings $oSettings) { return $this->oDriver->Save($oAccount, $oSettings->DataAsArray()); } - /** - * @param string $sEmail - * - * @return bool - */ - public function ClearByEmail($sEmail) - { - return $this->oDriver->ClearByEmail($sEmail); - } - /** * @return bool */ public function IsActive() { - return $this->oDriver instanceof \RainLoop\Providers\Settings\SettingsInterface; + return $this->oDriver instanceof \RainLoop\Providers\Settings\ISettings; } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings/DefaultSettings.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings/DefaultSettings.php index d4c84a10e..10db4cf15 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings/DefaultSettings.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings/DefaultSettings.php @@ -2,9 +2,10 @@ namespace RainLoop\Providers\Settings; -class DefaultSettings implements \RainLoop\Providers\Settings\SettingsInterface +class DefaultSettings implements \RainLoop\Providers\Settings\ISettings { const FILE_NAME = 'settings'; + const FILE_NAME_LOCAL = 'settings_local'; /** * @var \RainLoop\Providers\Storage @@ -20,15 +21,18 @@ class DefaultSettings implements \RainLoop\Providers\Settings\SettingsInterface } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * * @return array */ - public function Load(\RainLoop\Account $oAccount) + public function Load($oAccount) { $sValue = $this->oStorageProvider->Get($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, - \RainLoop\Providers\Settings\DefaultSettings::FILE_NAME); + $this->oStorageProvider->IsLocal() ? + \RainLoop\Providers\Settings\DefaultSettings::FILE_NAME_LOCAL : + \RainLoop\Providers\Settings\DefaultSettings::FILE_NAME + ); $aSettings = array(); if (\is_string($sValue)) @@ -44,28 +48,32 @@ class DefaultSettings implements \RainLoop\Providers\Settings\SettingsInterface } /** - * @param \RainLoop\Account $oAccount + * @param \RainLoop\Model\Account $oAccount * @param array $aSettings * * @return bool */ - public function Save(\RainLoop\Account $oAccount, array $aSettings) + public function Save($oAccount, array $aSettings) { return $this->oStorageProvider->Put($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, - \RainLoop\Providers\Settings\DefaultSettings::FILE_NAME, + $this->oStorageProvider->IsLocal() ? + \RainLoop\Providers\Settings\DefaultSettings::FILE_NAME_LOCAL : + \RainLoop\Providers\Settings\DefaultSettings::FILE_NAME, \json_encode($aSettings)); } /** - * @param string $sEmail + * @param \RainLoop\Model\Account $oAccount * * @return bool */ - public function ClearByEmail($sEmail) + public function Delete($oAccount) { - return $this->oStorageProvider->Clear($sEmail, + return $this->oStorageProvider->Clear($oAccount, \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, - \RainLoop\Providers\Settings\DefaultSettings::FILE_NAME); + $this->oStorageProvider->IsLocal() ? + \RainLoop\Providers\Settings\DefaultSettings::FILE_NAME_LOCAL : + \RainLoop\Providers\Settings\DefaultSettings::FILE_NAME); } } \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings/ISettings.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings/ISettings.php new file mode 100644 index 000000000..efbdd086d --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Settings/ISettings.php @@ -0,0 +1,28 @@ +oDriver = $oDriver; } /** - * @param \RainLoop\Account|string|null $oAccount + * @param \RainLoop\Model\Account|string|null $mAccount * @param int $iStorageType * * @return bool */ - public function verifyAccount($oAccount, $iStorageType) + private function verifyAccount($mAccount, $iStorageType) { if (\RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY !== $iStorageType && - !($oAccount instanceof \RainLoop\Account || \is_string($oAccount))) + !($mAccount instanceof \RainLoop\Model\Account || \is_string($mAccount))) { return false; } @@ -35,7 +35,7 @@ class Storage extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account|string|null $oAccount + * @param \RainLoop\Model\Account|string|null $oAccount * @param int $iStorageType * @param string $sKey * @param string $sValue @@ -53,7 +53,7 @@ class Storage extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account|string|null $oAccount + * @param \RainLoop\Model\Account|string|null $oAccount * @param int $iStorageType * @param string $sKey * @param mixed $mDefault = false @@ -71,7 +71,7 @@ class Storage extends \RainLoop\Providers\AbstractProvider } /** - * @param \RainLoop\Account|string|null $oAccount + * @param \RainLoop\Model\Account|string|null $oAccount * @param int $iStorageType * @param string $sKey * @@ -83,15 +83,34 @@ class Storage extends \RainLoop\Providers\AbstractProvider { return false; } - + return $this->oDriver->Clear($oAccount, $iStorageType, $sKey); } + /** + * @param \RainLoop\Model\Account|string $oAccount + * + * @return bool + */ + public function DeleteStorage($oAccount) + { + return $this->oDriver->DeleteStorage($oAccount); + } + /** * @return bool */ public function IsActive() { - return $this->oDriver instanceof \RainLoop\Providers\Storage\StorageInterface; + return $this->oDriver instanceof \RainLoop\Providers\Storage\IStorage; + } + + /** + * @return bool + */ + public function IsLocal() + { + return $this->oDriver instanceof \RainLoop\Providers\Storage\IStorage && + $this->oDriver->IsLocal(); } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/DefaultStorage.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php similarity index 52% rename from rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/DefaultStorage.php rename to rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php index 155943d31..50eca4dde 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/DefaultStorage.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/FileStorage.php @@ -2,25 +2,38 @@ namespace RainLoop\Providers\Storage; -class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface +class FileStorage implements \RainLoop\Providers\Storage\IStorage { /** * @var string */ private $sDataPath; + /** + * @var bool + */ + private $bLocal; + + /** + * @var \MailSo\Log\Logger + */ + protected $oLogger; + /** * @param string $sStoragePath + * @param bool $bLocal = false * * @return void */ - public function __construct($sStoragePath) + public function __construct($sStoragePath, $bLocal = false) { $this->sDataPath = \rtrim(\trim($sStoragePath), '\\/'); + $this->bLocal = !!$bLocal; + $this->oLogger = null; } /** - * @param \RainLoop\Account|string|null $oAccount + * @param \RainLoop\Model\Account|string|null $oAccount * @param int $iStorageType * @param string $sKey * @param string $sValue @@ -34,7 +47,7 @@ class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface } /** - * @param \RainLoop\Account|string|null $oAccount + * @param \RainLoop\Model\Account|string|null $oAccount * @param int $iStorageType * @param string $sKey * @param mixed $mDefault = false @@ -49,12 +62,12 @@ class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface { $mValue = \file_get_contents($sFileName); } - + return false === $mValue ? $mDefault : $mValue; } /** - * @param \RainLoop\Account|string|null $oAccount + * @param \RainLoop\Model\Account|string|null $oAccount * @param int $iStorageType * @param string $sKey * @@ -68,33 +81,78 @@ class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface { $mResult = @\unlink($sFileName); } - + return $mResult; } /** - * @param \RainLoop\Account|string|null $mAccount + * @param \RainLoop\Model\Account|string $oAccount + * + * @return bool + */ + public function DeleteStorage($oAccount) + { + $sPath = $this->generateFileName($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::USER, 'xxx', false, true); + + if (!empty($sPath) && \is_dir($sPath)) + { + \MailSo\Base\Utils::RecRmDir($sPath); + } + + $sPath = $this->generateFileName($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, 'xxx', false, true); + + if (!empty($sPath) && \is_dir($sPath)) + { + \MailSo\Base\Utils::RecRmDir($sPath); + } + + return true; + } + + /** + * @return bool + */ + public function IsLocal() + { + return $this->bLocal; + } + + /** + * @param \RainLoop\Model\Account|string|null $mAccount * @param int $iStorageType * @param string $sKey * @param bool $bMkDir = false + * @param bool $bForDeleteAction = false * * @return string */ - private function generateFileName($mAccount, $iStorageType, $sKey, $bMkDir = false) + public function generateFileName($mAccount, $iStorageType, $sKey, $bMkDir = false, $bForDeleteAction = false) { if (null === $mAccount) { $iStorageType = \RainLoop\Providers\Storage\Enumerations\StorageType::NOBODY; } - $sEmail = $mAccount instanceof \RainLoop\Account ? \preg_replace('/[^a-z0-9\-\.@]+/', '_', - ('' === $mAccount->ParentEmail() ? '' : $mAccount->ParentEmail().'/').$mAccount->Email()) : ''; + $sEmail = $sSubEmail = ''; + if ($mAccount instanceof \RainLoop\Model\Account) + { + $sEmail = $mAccount->ParentEmailHelper(); + if ($this->bLocal && $mAccount->IsAdditionalAccount() && !$bForDeleteAction) + { + $sSubEmail = $mAccount->Email(); + } + } if (\is_string($mAccount) && empty($sEmail)) { - $sEmail = \preg_replace('/[^a-z0-9\-\.@]+/', '_', $mAccount); + $sEmail = $mAccount; } + $sEmail = \preg_replace('/[^a-z0-9\-\.@]+/', '_', $sEmail); + $sSubEmail = \preg_replace('/[^a-z0-9\-\.@]+/', '_', $sSubEmail); + $sTypePath = $sKeyPath = ''; switch ($iStorageType) { @@ -118,10 +176,13 @@ class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface } else if (!empty($sEmail)) { - $sFilePath = $this->sDataPath.'/'.$sTypePath.'/'.rtrim(substr($sEmail, 0, 2), '@').'/'.$sEmail.'/'.$sKeyPath; + $sFilePath = $this->sDataPath.'/'.$sTypePath.'/'. + \str_pad(\rtrim(\substr($sEmail, 0, 2), '@'), 2, '_').'/'.$sEmail.'/'. + (0 < \strlen($sSubEmail) ? $sSubEmail.'/' : ''). + ($bForDeleteAction ? '' : $sKeyPath); } - if ($bMkDir && !empty($sFilePath) && !@\is_dir(\dirname($sFilePath))) + if ($bMkDir && !$bForDeleteAction && !empty($sFilePath) && !@\is_dir(\dirname($sFilePath))) { if (!@\mkdir(\dirname($sFilePath), 0755, true)) { @@ -131,4 +192,12 @@ class DefaultStorage implements \RainLoop\Providers\Storage\StorageInterface return $sFilePath; } + + /** + * @param \MailSo\Log\Logger $oLogger + */ + public function SetLogger($oLogger) + { + $this->oLogger = $oLogger instanceof \MailSo\Log\Logger ? $oLogger : null; + } } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/StorageInterface.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/IStorage.php similarity index 71% rename from rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/StorageInterface.php rename to rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/IStorage.php index b6febd8fe..dbef23a4f 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/StorageInterface.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/IStorage.php @@ -2,10 +2,10 @@ namespace RainLoop\Providers\Storage; -interface StorageInterface +interface IStorage { /** - * @param \RainLoop\Account|null $oAccount + * @param \RainLoop\Model\Account|null $oAccount * @param int $iStorageType * @param string $sKey * @param string $sValue @@ -15,7 +15,7 @@ interface StorageInterface public function Put($oAccount, $iStorageType, $sKey, $sValue); /** - * @param \RainLoop\Account|null $oAccount + * @param \RainLoop\Model\Account|null $oAccount * @param int $iStorageType * @param string $sKey * @param mixed $mDefault = false @@ -25,11 +25,16 @@ interface StorageInterface public function Get($oAccount, $iStorageType, $sKey, $mDefault = false); /** - * @param \RainLoop\Account|null $oAccount + * @param \RainLoop\Model\Account|null $oAccount * @param int $iStorageType * @param string $sKey * * @return bool */ public function Clear($oAccount, $iStorageType, $sKey); + + /** + * @return bool + */ + public function IsLocal(); } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/TemproryApcStorage.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/TemproryApcStorage.php new file mode 100644 index 000000000..509122d70 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Storage/TemproryApcStorage.php @@ -0,0 +1,78 @@ +generateFileName($oAccount, $iStorageType, $sKey, true), $sValue); + } + + /** + * @param \RainLoop\Model\Account|string|null $oAccount + * @param int $iStorageType + * @param string $sKey + * @param mixed $mDefault = false + * + * @return mixed + */ + public function Get($oAccount, $iStorageType, $sKey, $mDefault = false) + { + $bValue = false; + $mValue = @\apc_fetch($this->generateFileName($oAccount, $iStorageType, $sKey), $bValue); + if (!$bValue) + { + $mValue = $mDefault; + } + + return $mValue; + } + + /** + * @param \RainLoop\Model\Account|string|null $oAccount + * @param int $iStorageType + * @param string $sKey + * + * @return bool + */ + public function Clear($oAccount, $iStorageType, $sKey) + { + @\apc_delete($this->generateFileName($oAccount, $iStorageType, $sKey)); + + return true; + } + + /** + * @param \RainLoop\Model\Account|string $oAccount + * + * @return bool + */ + public function DeleteStorage($oAccount) + { + return !!$oAccount; + } + + /** + * @param \RainLoop\Model\Account|string|null $mAccount + * @param int $iStorageType + * @param string $sKey + * @param bool $bMkDir = false + * @param bool $bForDeleteAction = false + * + * @return string + */ + public function generateFileName($mAccount, $iStorageType, $sKey, $bMkDir = false, $bForDeleteAction = false) + { + $sFileName = parent::generateFileName($mAccount, $iStorageType, $sKey, false, false); + return $sFileName.'/'.\RainLoop\Utils::GetConnectionToken().'/'; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions.php index 37982f081..d4b5b27bc 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions.php @@ -1,40 +1,42 @@ -oDriver = $oDriver; - } - - /** - * @param \RainLoop\Account $oAccount - * @param string $sQuery - * - * @return array - */ - public function Process(\RainLoop\Account $oAccount, $sQuery) - { - return $this->oDriver && $this->IsActive() && 0 < \strlen($sQuery) ? $this->oDriver->Process($oAccount, $sQuery) : array(); - } - - /** - * @return bool - */ - public function IsActive() - { - return $this->oDriver instanceof \RainLoop\Providers\Suggestions\SuggestionsInterface; - } +oDriver = $oDriver; + } + + /** + * @param \RainLoop\Account $oAccount + * @param string $sQuery + * @param int $iLimit = 20 + * + * @return array + */ + public function Process(\RainLoop\Account $oAccount, $sQuery, $iLimit = 20) + { + return $this->oDriver && $this->IsActive() && 0 < \strlen($sQuery) ? + $this->oDriver->Process($oAccount, $sQuery, $iLimit = 20) : array(); + } + + /** + * @return bool + */ + public function IsActive() + { + return $this->oDriver instanceof \RainLoop\Providers\Suggestions\ISuggestions; + } } \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/ISuggestions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/ISuggestions.php new file mode 100644 index 000000000..cbb8130d2 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/ISuggestions.php @@ -0,0 +1,15 @@ +oLogger->WriteDump($aSearchResult); + + $aHashes = array(); + if (\is_array($aSearchResult) && 0 < \count($aSearchResult)) + { + foreach ($aSearchResult as $aContact) + { + if (0 >= $iLimit) + { + break; + } + + $sUid = empty($aContact['UID']) ? '' : $aContact['UID']; + if (!empty($sUid)) + { + $sFullName = isset($aContact['FN']) ? \trim($aContact['FN']) : ''; + $mEmails = isset($aContact['EMAIL']) ? $aContact['EMAIL'] : ''; + + if (!\is_array($mEmails)) + { + $mEmails = array($mEmails); + } + + foreach ($mEmails as $sEmail) + { + $sHash = '"'.$sFullName.'" <'.$sEmail.'>'; + if (!isset($aHashes[$sHash])) + { + $aHashes[$sHash] = true; + $aResult[] = array($sEmail, $sFullName); + $iLimit--; + } + } + } + } + + $aResult = \array_slice($aResult, 0, $iInputLimit); + } + + unset($aSearchResult, $aHashes); + } + catch (\Exception $oException) + { + if ($this->oLogger) + { + $this->oLogger->WriteException($oException); + } + } + + return $aResult; + } + + /** + * @param \MailSo\Log\Logger $oLogger + */ + public function SetLogger($oLogger) + { + $this->oLogger = $oLogger instanceof \MailSo\Log\Logger ? $oLogger : null; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/SuggestionsInterface.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/SuggestionsInterface.php deleted file mode 100644 index ffd3096d3..000000000 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/SuggestionsInterface.php +++ /dev/null @@ -1,14 +0,0 @@ -Email(), ''), + array('xxx@xxx', 'xxx') + ); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/TwoFactorAuth/AbstractTwoFactorAuth.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/TwoFactorAuth/AbstractTwoFactorAuth.php index 5519d0856..f720b9a10 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/TwoFactorAuth/AbstractTwoFactorAuth.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/TwoFactorAuth/AbstractTwoFactorAuth.php @@ -1,24 +1,24 @@ -verifyCode($sSecret, $sCode); + return $oGoogleAuthenticator->verifyCode($sSecret, $sCode, 8); } /** 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 1d07881e9..896fbdfa7 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Service.php @@ -35,33 +35,60 @@ class Service \ini_set('display_errors', 1); } - \RainLoop\Api::SetupDefaultMailSoConfig(); - $sServer = \trim($this->oActions->Config()->Get('security', 'custom_server_signature', '')); if (0 < \strlen($sServer)) { @\header('Server: '.$sServer, true); } + $sXFrameOptionsHeader = \trim($this->oActions->Config()->Get('security', 'x_frame_options_header', '')); + if (0 < \strlen($sXFrameOptionsHeader)) + { + @\header('X-Frame-Options: '.$sXFrameOptionsHeader, true); + } + if ($this->oActions->Config()->Get('labs', 'force_https', false) && !$this->oHttp->IsSecure()) { @\header('Location: https://'.$this->oHttp->GetHost(false, false).$this->oHttp->GetUrl(), true); exit(); } + + $this->localHandle(); } /** - * @return \RainLoop\Service + * @return bool */ - public static function NewInstance() + public function RunResult() { - return new self(); + return true; + } + + /** + * @staticvar bool $bOne + * @return bool + */ + public static function Handle() + { + static $bOne = null; + if (null === $bOne) + { + $oService = null; + if (\class_exists('MailSo\Version')) + { + $oService = new self(); + } + + $bOne = $oService->RunResult(); + } + + return $bOne; } /** * @return \RainLoop\Service */ - public function Handle() + private function localHandle() { if (!\class_exists('MailSo\Version')) { @@ -70,17 +97,10 @@ class Service $this->oActions->BootStart(); - $this->oActions->ParseQueryAuthString(); - - $bCached = false; $sResult = ''; + $bCached = false; - $sQuery = \trim(\trim($this->oHttp->GetServer('QUERY_STRING', '')), ' /'); - $iPos = \strpos($sQuery, '&'); - if (0 < $iPos) - { - $sQuery = \substr($sQuery, 0, $iPos); - } + $sQuery = $this->oActions->ParseQueryAuthString(); $this->oActions->Plugins()->RunHook('filter.http-query', array(&$sQuery)); $aPaths = \explode('/', $sQuery); @@ -98,9 +118,14 @@ class Service $bAdmin = true; } + if ($this->oHttp->IsPost()) + { + $this->oHttp->ServerNoCache(); + } + if ($bAdmin && !$this->oActions->Config()->Get('security', 'allow_admin_panel', true)) { - echo $this->oActions->ErrorTemplates('Access Denied.', + echo $this->oServiceActions->ErrorTemplates('Access Denied.', 'Access to the RainLoop Webmail Admin Panel is not allowed!', true); return $this; @@ -125,9 +150,22 @@ class Service if ($bIndex) { + @\header('Content-Security-Policy:'); + @\header_remove('Content-Security-Policy'); + @header('Content-Type: text/html; charset=utf-8'); $this->oHttp->ServerNoCache(); + if (!@\is_dir(APP_DATA_FOLDER_PATH) || !@\is_writable(APP_DATA_FOLDER_PATH)) + { + echo $this->oServiceActions->ErrorTemplates( + 'Permission denied!', + 'RainLoop Webmail cannot access to the data folder "'.APP_DATA_FOLDER_PATH.'"' + ); + + return $this; + } + $aTemplateParameters = $this->indexTemplateParameters($bAdmin); $sCacheFileName = ''; @@ -139,6 +177,7 @@ class Service if (0 === \strlen($sResult)) { +// $aTemplateParameters['{{BaseTemplates}}'] = $this->oServiceActions->compileTemplates($bAdmin, false); $sResult = \strtr(\file_get_contents(APP_VERSION_ROOT_PATH.'app/templates/Index.html'), $aTemplateParameters); $sResult = \RainLoop\Utils::ClearHtmlOutput($sResult); @@ -158,6 +197,12 @@ class Service $sResult .= '][cached:'.($bCached ? 'true' : 'false'); $sResult .= '][hash:'.$aTemplateParameters['{{BaseHash}}']; $sResult .= '][session:'.\md5(\RainLoop\Utils::GetShortToken()); + + if (\RainLoop\Utils::IsOwnCloud()) + { + $sResult .= '][owncloud:true'; + } + $sResult .= '] -->'; } @@ -170,40 +215,35 @@ class Service } /** - * @param bool $bAdmin + * @param bool $bAdmin = false * * @return array */ - private function indexTemplateParameters($bAdmin) + private function indexTemplateParameters($bAdmin = false) { $sLanguage = 'en'; $sTheme = 'Default'; - if (!$bAdmin) - { - list($sLanguage, $sTheme) = $this->oActions->GetLanguageAndTheme(); - } - - $sLanguage = $this->oActions->ValidateLanguage($sLanguage); - $sTheme = $this->oActions->ValidateTheme($sTheme); + list($sLanguage, $sTheme) = $this->oActions->GetLanguageAndTheme($bAdmin); $bAppJsDebug = !!$this->oActions->Config()->Get('labs', 'use_app_debug_js', false); $bAppCssDebug = !!$this->oActions->Config()->Get('labs', 'use_app_debug_css', false); - $sStaticPrefix = APP_WEB_STATIC_PATH; + $sStaticPrefix = \RainLoop\Utils::WebStaticPath(); $aData = array( 'Language' => $sLanguage, 'Theme' => $sTheme, - 'LoadingDescription' => $this->oActions->Config()->Get('webmail', 'loading_description', 'RainLoop'), 'FaviconIcoLink' => $sStaticPrefix.'favicon.ico', 'FaviconPngLink' => $sStaticPrefix.'favicon.png', 'AppleTouchLink' => $sStaticPrefix.'apple-touch-icon.png', 'AppCssLink' => $sStaticPrefix.'css/app'.($bAppCssDebug ? '' : '.min').'.css', 'BootJsLink' => $sStaticPrefix.'js/min/boot.js', + 'ComponentsJsLink' => $sStaticPrefix.'js/'.($bAppJsDebug ? '' : 'min/').'components.js', 'LibJsLink' => $sStaticPrefix.'js/min/libs.js', 'EditorJsLink' => $sStaticPrefix.'ckeditor/ckeditor.js', 'OpenPgpJsLink' => $sStaticPrefix.'js/min/openpgp.min.js', + 'AppJsCommonLink' => $sStaticPrefix.'js/'.($bAppJsDebug ? '' : 'min/').'common.js', 'AppJsLink' => $sStaticPrefix.'js/'.($bAppJsDebug ? '' : 'min/').($bAdmin ? 'admin' : 'app').'.js' ); @@ -214,19 +254,21 @@ class Service '{{BaseAppAppleTouchFile}}' => $aData['AppleTouchLink'], '{{BaseAppMainCssLink}}' => $aData['AppCssLink'], '{{BaseAppBootScriptLink}}' => $aData['BootJsLink'], + '{{BaseAppComponentsScriptLink}}' => $aData['ComponentsJsLink'], '{{BaseAppLibsScriptLink}}' => $aData['LibJsLink'], '{{BaseAppEditorScriptLink}}' => $aData['EditorJsLink'], '{{BaseAppOpenPgpScriptLink}}' => $aData['OpenPgpJsLink'], + '{{BaseAppMainCommonScriptLink}}' => $aData['AppJsCommonLink'], '{{BaseAppMainScriptLink}}' => $aData['AppJsLink'], - '{{BaseAppLoadingDescription}}' => \htmlspecialchars($aData['LoadingDescription'], ENT_QUOTES|ENT_IGNORE, 'UTF-8'), '{{BaseDir}}' => \in_array($aData['Language'], array('ar', 'he', 'ur')) ? 'rtl' : 'ltr' ); $aTemplateParameters['{{BaseHash}}'] = \md5( \implode('~', array( + $bAdmin ? '1' : '0', \md5($this->oActions->Config()->Get('cache', 'index', '')), $this->oActions->Plugins()->Hash(), - APP_WEB_PATH, APP_VERSION + \RainLoop\Utils::WebVersionPath(), APP_VERSION )). \implode('~', $aTemplateParameters) ); diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php index d5b017bc3..76eb2c773 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php @@ -184,7 +184,7 @@ class ServiceActions if (\is_array($aResponseItem) && 'Folders' === $sAction && $oException instanceof \RainLoop\Exceptions\ClientException) { - $aResponseItem['Logout'] = true; + $aResponseItem['ClearAuth'] = true; } } @@ -196,6 +196,7 @@ class ServiceActions $this->Plugins()->RunHook('filter.ajax-response', array($sAction, &$aResponseItem)); @\header('Content-Type: application/json; charset=utf-8'); + $sResult = \MailSo\Base\Utils::Php2js($aResponseItem, $this->Logger()); $sObResult = @\ob_get_clean(); @@ -220,6 +221,48 @@ class ServiceActions return $sResult; } + /** + * @return string + */ + public function ServiceOwnCloudAuth() + { + if (!\RainLoop\Utils::IsOwnCloud() || + !isset($_ENV['___rainloop_owncloud_email']) || + !isset($_ENV['___rainloop_owncloud_password']) || + empty($_ENV['___rainloop_owncloud_email']) + ) + { + $this->oActions->SetAuthLogoutToken(); + $this->oActions->Location('./'); + return ''; + } + + $bLogout = true; + + $sEmail = $_ENV['___rainloop_owncloud_email']; + $sPassword = $_ENV['___rainloop_owncloud_password']; + + try + { + $oAccount = $this->oActions->LoginProcess($sEmail, $sPassword); + $this->oActions->AuthToken($oAccount); + + $bLogout = !($oAccount instanceof \RainLoop\Model\Account); + } + catch (\Exception $oException) + { + $this->oActions->Logger()->WriteException($oException); + } + + if ($bLogout) + { + $this->oActions->SetAuthLogoutToken(); + } + + $this->oActions->Location('./'); + return ''; + } + /** * @return string */ @@ -263,19 +306,57 @@ class ServiceActions /** * @param string $sAction + * @param int $iSizeLimit = 0 * * @return string */ - private function privateUpload($sAction) + private function privateUpload($sAction, $iSizeLimit = 0) { + $oConfig = $this->Config(); + @\ob_start(); $aResponseItem = null; try { + $aFile = null; + $sInputName = 'uploader'; + $iError = \RainLoop\Enumerations\UploadError::UNKNOWN; + $iSizeLimit = (0 < $iSizeLimit ? $iSizeLimit : ((int) $oConfig->Get('webmail', 'attachment_size_limit', 0))) * 1024 * 1024; + + $iError = UPLOAD_ERR_OK; + $_FILES = isset($_FILES) ? $_FILES : null; + if (isset($_FILES, $_FILES[$sInputName], $_FILES[$sInputName]['name'], $_FILES[$sInputName]['tmp_name'], $_FILES[$sInputName]['size'])) + { + $iError = (isset($_FILES[$sInputName]['error'])) ? (int) $_FILES[$sInputName]['error'] : UPLOAD_ERR_OK; + + if (UPLOAD_ERR_OK === $iError && 0 < $iSizeLimit && $iSizeLimit < (int) $_FILES[$sInputName]['size']) + { + $iError = \RainLoop\Enumerations\UploadError::CONFIG_SIZE; + } + + if (UPLOAD_ERR_OK === $iError) + { + $aFile = $_FILES[$sInputName]; + } + } + else if (!isset($_FILES) || !is_array($_FILES) || 0 === count($_FILES)) + { + $iError = UPLOAD_ERR_INI_SIZE; + } + else + { + $iError = \RainLoop\Enumerations\UploadError::EMPTY_FILES_DATA; + } + if (\method_exists($this->oActions, $sAction) && \is_callable(array($this->oActions, $sAction))) { - $this->oActions->SetActionParams($this->oHttp->GetQueryAsArray(), $sAction); + $aActionParams = $this->oHttp->GetQueryAsArray(); + + $aActionParams['File'] = $aFile; + $aActionParams['Error'] = $iError; + + $this->oActions->SetActionParams($aActionParams, $sAction); $aResponseItem = \call_user_func(array($this->oActions, $sAction)); } @@ -326,7 +407,7 @@ class ServiceActions */ public function ServiceUploadContacts() { - return $this->privateUpload('UploadContacts'); + return $this->privateUpload('UploadContacts', 5); } /** @@ -334,7 +415,7 @@ class ServiceActions */ public function ServiceUploadBackground() { - return $this->privateUpload('UploadBackground'); + return $this->privateUpload('UploadBackground', 1); } /** @@ -372,6 +453,8 @@ class ServiceActions { $this->oHttp->StatusHeader(404); } + + return ''; } /** @@ -394,7 +477,8 @@ class ServiceActions { $sRawError = ''; $this->oActions->SetActionParams(array( - 'RawKey' => empty($this->aPaths[3]) ? '' : $this->aPaths[3] + 'RawKey' => empty($this->aPaths[3]) ? '' : $this->aPaths[3], + 'Params' => $this->aPaths ), $sMethodName); if (!\call_user_func(array($this->oActions, $sMethodName))) @@ -406,6 +490,14 @@ class ServiceActions $sRawError = ''; } } + else + { + $sRawError = 'Unknown action "'.$sAction.'"'; + } + } + else + { + $sRawError = 'Empty action'; } } catch (\RainLoop\Exceptions\ClientException $oException) @@ -426,6 +518,7 @@ class ServiceActions if (0 < \strlen($sRawError)) { $this->oActions->Logger()->Write($sRawError, \MailSo\Log\Enumerations\Type::ERROR); + $this->oActions->Logger()->WriteDump($this->aPaths, \MailSo\Log\Enumerations\Type::ERROR, 'PATHS'); } if ($oException) @@ -441,12 +534,14 @@ class ServiceActions */ public function ServiceLang() { +// sleep(2); $sResult = ''; @\header('Content-Type: application/javascript; charset=utf-8'); - if (!empty($this->aPaths[2])) + if (!empty($this->aPaths[3])) { - $sLanguage = $this->oActions->ValidateLanguage($this->aPaths[2]); + $bAdmim = 'Admin' === (isset($this->aPaths[2]) ? (string) $this->aPaths[2] : 'App'); + $sLanguage = $this->oActions->ValidateLanguage($this->aPaths[3], '', $bAdmim); $bCacheEnabled = $this->Config()->Get('labs', 'cache_system_data', true); if (!empty($sLanguage) && $bCacheEnabled) @@ -457,13 +552,15 @@ class ServiceActions $sCacheFileName = ''; if ($bCacheEnabled) { - $sCacheFileName = \RainLoop\KeyPathHelper::LangCache($sLanguage, $this->oActions->Plugins()->Hash()); + $sCacheFileName = \RainLoop\KeyPathHelper::LangCache( + $sLanguage, $bAdmim, $this->oActions->Plugins()->Hash()); + $sResult = $this->Cacher()->Get($sCacheFileName); } if (0 === \strlen($sResult)) { - $sResult = $this->compileLanguage($sLanguage, false); + $sResult = $this->compileLanguage($sLanguage, $bAdmim, false); if ($bCacheEnabled && 0 < \strlen($sCacheFileName)) { $this->Cacher()->Set($sCacheFileName, $sResult); @@ -567,7 +664,7 @@ class ServiceActions $sResult = ''; $bAdmin = !empty($this->aPaths[2]) && 'Admin' === $this->aPaths[2]; - $bJson = !empty($this->aPaths[7]) && 'Json' === $this->aPaths[7]; + $bJson = !empty($this->aPaths[9]) && 'Json' === $this->aPaths[9]; if ($bJson) { @@ -623,7 +720,10 @@ class ServiceActions if (\file_exists($sThemeFile) && \file_exists($sThemeTemplateFile) && \file_exists($sThemeValuesFile)) { - $aResult[] = '@base: "'.($bCustomTheme ? '' : APP_WEB_PATH).'themes/'.$sRealTheme.'/";'; + $aResult[] = '@base: "'. + ($bCustomTheme ? \RainLoop\Utils::WebPath() : \RainLoop\Utils::WebVersionPath()). + 'themes/'.$sRealTheme.'/";'; + $aResult[] = \file_get_contents($sThemeValuesFile); $aResult[] = \file_get_contents($sThemeFile); $aResult[] = \file_get_contents($sThemeTemplateFile); @@ -727,7 +827,7 @@ class ServiceActions @\header('Content-Type: text/html; charset=utf-8'); return \strtr(\file_get_contents(APP_VERSION_ROOT_PATH.'app/templates/BadBrowser.html'), array( - '{{BaseWebStaticPath}}' => APP_WEB_STATIC_PATH, + '{{BaseWebStaticPath}}' => \RainLoop\Utils::WebStaticPath(), '{{ErrorTitle}}' => $sTitle, '{{ErrorHeader}}' => $sTitle, '{{ErrorDesc}}' => $sDesc @@ -801,12 +901,43 @@ class ServiceActions $sEmail = \trim($mData['Email']); $sPassword = $mData['Password']; + $aAdditionalOptions = isset($mData['AdditionalOptions']) && \is_array($mData['AdditionalOptions']) && + 0 < \count($mData['AdditionalOptions']) ? $mData['AdditionalOptions'] : null; + try { $oAccount = $this->oActions->LoginProcess($sEmail, $sPassword); - $this->oActions->AuthProcess($oAccount); - $bLogout = !($oAccount instanceof \RainLoop\Account); + if ($oAccount instanceof \RainLoop\Model\Account && $aAdditionalOptions) + { + $bNeedToSettings = false; + + $oSettings = $this->SettingsProvider()->Load($oAccount); + if ($oSettings) + { + $sLanguage = isset($aAdditionalOptions['Language']) ? + $aAdditionalOptions['Language'] : ''; + + if ($sLanguage) + { + $sLanguage = $this->oActions->ValidateLanguage($sLanguage); + if ($sLanguage !== $oSettings->GetConf('Language', '')) + { + $bNeedToSettings = true; + $oSettings->SetConf('Language', $sLanguage); + } + } + } + + if ($bNeedToSettings) + { + $this->SettingsProvider()->Save($oAccount, $oSettings); + } + } + + $this->oActions->AuthToken($oAccount); + + $bLogout = !($oAccount instanceof \RainLoop\Model\Account); } catch (\Exception $oException) { @@ -842,8 +973,8 @@ class ServiceActions try { $oAccount = $this->oActions->LoginProcess($sEmail, $sPassword); - $this->oActions->AuthProcess($oAccount); - $bLogout = !($oAccount instanceof \RainLoop\Account); + $this->oActions->AuthToken($oAccount); + $bLogout = !($oAccount instanceof \RainLoop\Model\Account); } catch (\Exception $oException) { @@ -877,8 +1008,8 @@ class ServiceActions try { $oAccount = $this->oActions->LoginProcess($sEmail, $sPassword); - $this->oActions->AuthProcess($oAccount); - $bLogout = !($oAccount instanceof \RainLoop\Account); + $this->oActions->AuthToken($oAccount); + $bLogout = !($oAccount instanceof \RainLoop\Model\Account); } catch (\Exception $oException) { @@ -899,7 +1030,7 @@ class ServiceActions $aResult = array( 'Action' => 'ExternalLogin', - 'Result' => $oAccount instanceof \RainLoop\Account ? true : false, + 'Result' => $oAccount instanceof \RainLoop\Model\Account ? true : false, 'ErrorCode' => 0 ); @@ -940,8 +1071,6 @@ class ServiceActions $sEmail = \trim($this->oHttp->GetRequest('Email', '')); $sPassword = $this->oHttp->GetRequest('Password', ''); - \RainLoop\Api::Handle(); - $sResult = \RainLoop\Api::GetUserSsoHash($sEmail, $sPassword); $bLogout = 0 === \strlen($sResult); @@ -974,7 +1103,9 @@ class ServiceActions */ public function ServiceChange() { - if ($this->Config()->Get('webmail', 'allow_additional_accounts', true)) + $oAccount = $this->oActions->GetAccount(); + + if ($oAccount && $this->oActions->GetCapa(false, \RainLoop\Enumerations\Capa::ADDITIONAL_ACCOUNTS, $oAccount)) { $oAccountToLogin = null; $sEmail = empty($this->aPaths[2]) ? '' : \urldecode(\trim($this->aPaths[2])); @@ -982,20 +1113,16 @@ class ServiceActions { $sEmail = \MailSo\Base\Utils::IdnToAscii($sEmail); - $oAccount = $this->oActions->GetAccount(); - if ($oAccount) + $aAccounts = $this->oActions->GetAccounts($oAccount); + if (isset($aAccounts[$sEmail])) { - $aAccounts = $this->oActions->GetAccounts($oAccount); - if (isset($aAccounts[$sEmail])) - { - $oAccountToLogin = $this->oActions->GetAccountFromCustomToken($aAccounts[$sEmail], false, false); - } + $oAccountToLogin = $this->oActions->GetAccountFromCustomToken($aAccounts[$sEmail], false, false); } } if ($oAccountToLogin) { - $this->oActions->AuthProcess($oAccountToLogin); + $this->oActions->AuthToken($oAccountToLogin); } } @@ -1003,6 +1130,25 @@ class ServiceActions return ''; } + /** + * @param string $sTitle + * @param string $sDesc + * + * @return mixed + */ + public function ErrorTemplates($sTitle, $sDesc, $bShowBackLink = true) + { + return strtr(file_get_contents(APP_VERSION_ROOT_PATH.'app/templates/Error.html'), array( + '{{BaseWebStaticPath}}' => \RainLoop\Utils::WebStaticPath(), + '{{ErrorTitle}}' => $sTitle, + '{{ErrorHeader}}' => $sTitle, + '{{ErrorDesc}}' => $sDesc, + '{{BackLinkVisibilityStyle}}' => $bShowBackLink ? 'display:inline-block' : 'display:none', + '{{BackLink}}' => $this->oActions->StaticI18N('STATIC/BACK_LINK'), + '{{BackHref}}' => './' + )); + } + /** * @param string $sTitle * @param string $sDesc @@ -1012,7 +1158,7 @@ class ServiceActions private function localError($sTitle, $sDesc) { @header('Content-Type: text/html; charset=utf-8'); - return $this->oActions->ErrorTemplates($sTitle, \nl2br($sDesc)); + return $this->ErrorTemplates($sTitle, \nl2br($sDesc)); } /** @@ -1043,12 +1189,13 @@ class ServiceActions { $this->oActions->CheckMailConnection($oAccount); - $this->oActions->AuthProcess($oAccount); + $this->oActions->AuthToken($oAccount); $sAuthAccountHash = $this->oActions->GetSpecAuthToken(); } catch (\Exception $oException) { + $oException = null; $this->oActions->ClearSignMeData($oAccount); } } @@ -1066,30 +1213,59 @@ class ServiceActions /** * @param bool $bAdmin = false + * @param bool $bJsOutput = true * * @return string */ - private function compileTemplates($bAdmin = false) + public function compileTemplates($bAdmin = false, $bJsOutput = true) { - $sHtml = \RainLoop\Utils::CompileTemplates(APP_VERSION_ROOT_PATH.'app/templates/Views/'.($bAdmin ? 'Admin' : 'App'), $this->oActions). + $sHtml = + \RainLoop\Utils::CompileTemplates(APP_VERSION_ROOT_PATH.'app/templates/Views/Components', $this->oActions, 'Component'). + \RainLoop\Utils::CompileTemplates(APP_VERSION_ROOT_PATH.'app/templates/Views/'.($bAdmin ? 'Admin' : 'User'), $this->oActions). \RainLoop\Utils::CompileTemplates(APP_VERSION_ROOT_PATH.'app/templates/Views/Common', $this->oActions). $this->oActions->Plugins()->CompileTemplate($bAdmin); - return 'window.rainloopTEMPLATES='.\MailSo\Base\Utils::Php2js(array($sHtml), $this->Logger()).';'; + return $bJsOutput ? 'window.rainloopTEMPLATES='.\MailSo\Base\Utils::Php2js(array($sHtml), $this->Logger()).';' : $sHtml; } /** * @param string $sLanguage + * + * @return string + */ + private function convertLanguageNameToMomentLanguageName($sLanguage) + { + switch ($sLanguage) + { + case 'pt-pt': + $sLanguage = 'pt'; + break; + case 'ja-jp': + $sLanguage = 'ja'; + break; + case 'ko-kr': + $sLanguage = 'ko'; + break; + } + + return $sLanguage; + } + + /** + * @param string $sLanguage + * @param bool $bAdmin = false * @param bool $bWrapByScriptTag = true * * @return string */ - private function compileLanguage($sLanguage, $bWrapByScriptTag = true) + private function compileLanguage($sLanguage, $bAdmin = false, $bWrapByScriptTag = true) { $aResultLang = array(); $sMoment = 'window.moment && window.moment.lang && window.moment.lang(\'en\');'; - $sMomentFileName = APP_VERSION_ROOT_PATH.'app/i18n/moment/'.$sLanguage.'.js'; + $sMomentFileName = APP_VERSION_ROOT_PATH.'app/i18n/moment/'. + $this->convertLanguageNameToMomentLanguageName($sLanguage).'.js'; + if (\file_exists($sMomentFileName)) { $sMoment = \file_get_contents($sMomentFileName); @@ -1097,7 +1273,8 @@ class ServiceActions } \RainLoop\Utils::ReadAndAddLang(APP_VERSION_ROOT_PATH.'app/i18n/langs.ini', $aResultLang); - \RainLoop\Utils::ReadAndAddLang(APP_VERSION_ROOT_PATH.'langs/'.$sLanguage.'.ini', $aResultLang); + \RainLoop\Utils::ReadAndAddLang(APP_VERSION_ROOT_PATH.'langs/'. + ($bAdmin ? 'admin/' : '').$sLanguage.'.ini', $aResultLang); $this->Plugins()->ReadLang($sLanguage, $aResultLang); @@ -1115,7 +1292,7 @@ class ServiceActions $sResult = empty($sLangJs) ? 'null' : '{'.\substr($sLangJs, 0, -1).'}'; return - ($bWrapByScriptTag ? '' : '') ; @@ -1130,7 +1307,7 @@ class ServiceActions private function compileAppData($aAppData, $bWrapByScriptTag = true) { return - ($bWrapByScriptTag ? '' : '') diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Settings.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Settings.php index f766f13bd..d234f0833 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Settings.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Settings.php @@ -1,64 +1,78 @@ -aData = array(); - } - - /** - * @param array $aData - * - * @return \RainLoop\Settings - */ - public function InitData($aData) - { - if (\is_array($aData)) - { - $this->aData = $aData; - } - - return $this; - } - - /** - * @return array - */ - public function DataAsArray() - { - return $this->aData; - } - - /** - * @param string $sName - * @param mixed $mDefValue = null - * - * @return mixed - */ - public function GetConf($sName, $mDefValue = null) - { - return isset($this->aData[$sName]) ? $this->aData[$sName] : $mDefValue; - } - - /** - * @param string $sName - * @param mixed $mValue - * - * @return void - */ - public function SetConf($sName, $mValue) - { - $this->aData[$sName] = $mValue; - } +aData = array(); + $this->bLocal = !!$bLocal; + } + + /** + * @param array $aData + * + * @return \RainLoop\Settings + */ + public function InitData($aData) + { + if (\is_array($aData)) + { + $this->aData = $aData; + } + + return $this; + } + + /** + * @return array + */ + public function DataAsArray() + { + return $this->aData; + } + + /** + * @return bool + */ + public function IsLocal() + { + return $this->bLocal; + } + + /** + * @param string $sName + * @param mixed $mDefValue = null + * + * @return mixed + */ + public function GetConf($sName, $mDefValue = null) + { + return isset($this->aData[$sName]) ? $this->aData[$sName] : $mDefValue; + } + + /** + * @param string $sName + * @param mixed $mValue + * + * @return void + */ + public function SetConf($sName, $mValue) + { + $this->aData[$sName] = $mValue; + } } \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Social.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Social.php index 5bc28b247..aa0175c40 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Social.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Social.php @@ -196,6 +196,7 @@ class Social $oSettings->SetConf('GoogleAccessToken', ''); $oSettings->SetConf('GoogleSocialName', ''); + return $this->oActions->SettingsProvider()->Save($oAccount, $oSettings); } @@ -260,6 +261,7 @@ class Social $oSettings->SetConf('TwitterAccessToken', ''); $oSettings->SetConf('TwitterSocialName', ''); + return $this->oActions->SettingsProvider()->Save($oAccount, $oSettings); } @@ -273,6 +275,7 @@ class Social { $sResult = ''; $sLoginUrl = ''; + $oAccount = null; $bLogin = false; $iErrorCode = \RainLoop\Notifications::UnknownError; @@ -345,13 +348,7 @@ class Social if ($aUserData && \is_array($aUserData) && !empty($aUserData['Email']) && isset($aUserData['Password'])) { - $oAccount = $this->oActions->LoginProcess($aUserData['Email'], $aUserData['Password']); - if ($oAccount instanceof \RainLoop\Account) - { - $this->oActions->AuthProcess($oAccount); - - $iErrorCode = 0; - } + $iErrorCode = $this->loginProcess($oAccount, $aUserData['Email'], $aUserData['Password']); } else { @@ -400,7 +397,7 @@ class Social @\header('Content-Type: text/html; charset=utf-8'); $sCallBackType = $bLogin ? '_login' : ''; $sConnectionFunc = 'rl_'.\md5(\RainLoop\Utils::GetConnectionToken()).'_google'.$sCallBackType.'_service'; - $sResult = ''; } @@ -419,6 +416,7 @@ class Social $mData = false; $sUserData = ''; $aUserData = false; + $oAccount = null; $bLogin = false; $iErrorCode = \RainLoop\Notifications::UnknownError; @@ -492,13 +490,7 @@ class Social if ($aUserData && \is_array($aUserData) && !empty($aUserData['Email']) && isset($aUserData['Password'])) { - $oAccount = $this->oActions->LoginProcess($aUserData['Email'], $aUserData['Password']); - if ($oAccount instanceof \RainLoop\Account) - { - $this->oActions->AuthProcess($oAccount); - - $iErrorCode = 0; - } + $iErrorCode = $this->loginProcess($oAccount, $aUserData['Email'], $aUserData['Password']); } else { @@ -520,11 +512,12 @@ class Social else { $this->oHttp->ServerNoCache(); + @\header('Content-Type: text/html; charset=utf-8'); $sCallBackType = $bLogin ? '_login' : ''; $sConnectionFunc = 'rl_'.\md5(\RainLoop\Utils::GetConnectionToken()).'_facebook'.$sCallBackType.'_service'; - $sResult = ''; } @@ -540,6 +533,7 @@ class Social $sLoginUrl = ''; $sSocialName = ''; + $oAccount = null; $bLogin = false; $iErrorCode = \RainLoop\Notifications::UnknownError; @@ -667,13 +661,7 @@ class Social !empty($aUserData['Email']) && isset($aUserData['Password'])) { - $oAccount = $this->oActions->LoginProcess($aUserData['Email'], $aUserData['Password']); - if ($oAccount instanceof \RainLoop\Account) - { - $this->oActions->AuthProcess($oAccount); - - $iErrorCode = 0; - } + $iErrorCode = $this->loginProcess($oAccount, $aUserData['Email'], $aUserData['Password']); } else { @@ -720,7 +708,7 @@ class Social @\header('Content-Type: text/html; charset=utf-8'); $sCallBackType = $bLogin ? '_login' : ''; $sConnectionFunc = 'rl_'.\md5(\RainLoop\Utils::GetConnectionToken()).'_twitter'.$sCallBackType.'_service'; - $sResult = ''; } @@ -799,7 +787,7 @@ class Social } /** - * @param \RainLoop\Account|null $oAccount = null + * @param \RainLoop\Model\Account|null $oAccount = null * * @return \RainLoop\Common\RainLoopFacebookRedirectLoginHelper|null */ @@ -811,7 +799,9 @@ class Social if (\version_compare(PHP_VERSION, '5.4.0', '>=') && $oConfig->Get('social', 'fb_enable', false) && '' !== $sAppID && - '' !== \trim($oConfig->Get('social', 'fb_app_secret', ''))) + '' !== \trim($oConfig->Get('social', 'fb_app_secret', '')) && + \class_exists('Facebook\FacebookSession') + ) { \Facebook\FacebookSession::setDefaultApplication($sAppID, \trim($oConfig->Get('social', 'fb_app_secret', ''))); @@ -871,4 +861,42 @@ class Social { return \implode('_', array('twitter', \md5($oTwitter->config['consumer_secret']), $sTwitterUserId, APP_SALT)); } + + /** + * @param \RainLoop\Model\Account|null $oAccount + * @param string $sEmail + * @param string $sPassword + * + * @return int + */ + private function loginProcess(&$oAccount, $sEmail, $sPassword) + { + $iErrorCode = \RainLoop\Notifications::UnknownError; + + try + { + $oAccount = $this->oActions->LoginProcess($sEmail, $sPassword); + if ($oAccount instanceof \RainLoop\Model\Account) + { + $this->oActions->AuthToken($oAccount); + $iErrorCode = 0; + } + else + { + $oAccount = null; + $iErrorCode = \RainLoop\Notifications::AuthError; + } + } + catch (\RainLoop\Exceptions\ClientException $oException) + { + $iErrorCode = $oException->getCode(); + } + catch (\Exception $oException) + { + unset($oException); + $iErrorCode = \RainLoop\Notifications::UnknownError; + } + + return $iErrorCode; + } } \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php index ad6231505..b85f33c99 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php @@ -72,7 +72,7 @@ class Utils $sToken = \RainLoop\Utils::GetCookie($sKey, null); if (null === $sToken) { - $sToken = \md5(\rand(10000, 99999).\microtime(true).APP_SALT); + $sToken = \MailSo\Base\Utils::Md5Rand(APP_SALT); \RainLoop\Utils::SetCookie($sKey, $sToken, \time() + 60 * 60 * 24 * 30, '/', null, null, true); } @@ -97,7 +97,7 @@ class Utils $sToken = \RainLoop\Utils::GetCookie($sKey, null); if (null === $sToken) { - $sToken = \md5(\rand(10000, 99999).\microtime(true).APP_SALT); + $sToken = \MailSo\Base\Utils::Md5Rand(APP_SALT); \RainLoop\Utils::SetCookie($sKey, $sToken, 0, '/', null, null, true); } @@ -149,16 +149,16 @@ class Utils } /** - * @param string $FileName + * @param string $sFileName * @param array $aResultLang * * @return void */ - public static function ReadAndAddLang($FileName, &$aResultLang) + public static function ReadAndAddLang($sFileName, &$aResultLang) { - if (\file_exists($FileName)) + if (\file_exists($sFileName)) { - $aLang = @\parse_ini_file($FileName, true); + $aLang = \RainLoop\Utils::CustomParseIniFile($sFileName, true); if (\is_array($aLang)) { foreach ($aLang as $sKey => $mValue) @@ -245,10 +245,12 @@ class Utils /** * @param string $sDirName + * @param \RainLoop\Actions $oAction + * @param string $sNameSuffix = '' * * @return string */ - public static function CompileTemplates($sDirName, $oAction) + public static function CompileTemplates($sDirName, $oAction, $sNameSuffix = '') { $sResult = ''; if (\file_exists($sDirName)) @@ -257,7 +259,7 @@ class Utils foreach ($aList as $sName) { - $sTemplateName = \substr($sName, 0, -5); + $sTemplateName = \substr($sName, 0, -5).$sNameSuffix; $sResult .= ''; } @@ -305,6 +307,74 @@ class Utils @\setcookie($sName, '', \time() - 3600 * 24 * 30, '/'); } + /** + * @return bool + */ + public static function IsOwnCloud() + { + return isset($_ENV['RAINLOOP_OWNCLOUD']) && $_ENV['RAINLOOP_OWNCLOUD'] && + \class_exists('\\OC'); + } + + /** + * @return string + */ + public static function WebPath() + { + $sAppPath = ''; + if (\RainLoop\Utils::IsOwnCloud()) + { + if (\class_exists('\\OC_App')) + { + $sAppPath = \rtrim(\trim(\OC_App::getAppWebPath('rainloop')), '\\/').'/app/'; + } + + if (empty($sAppPath)) + { + $sUrl = \MailSo\Base\Http::SingletonInstance()->GetUrl(); + if ($sUrl && \preg_match('/\/index\.php\/apps\/rainloop/', $sUrl)) + { + $sAppPath = \preg_replace('/\/index\.php\/apps\/rainloop.+$/', + '/apps/rainloop/app/', $sUrl); + } + } + } + + return $sAppPath; + } + /** + * @return string + */ + public static function WebVersionPath() + { + return self::WebPath().'rainloop/v/'.APP_VERSION.'/'; + } + + /** + * @return string + */ + public static function WebStaticPath() + { + return self::WebVersionPath().'static/'; + } + + /** + * @param string $sFileName + * @param bool $bProcessSections = false + * + * @return array + */ + public static function CustomParseIniFile($sFileName, $bProcessSections = false) + { +// if (\MailSo\Base\Utils::FunctionExistsAndEnabled('parse_ini_file')) +// { +// return @\parse_ini_file($sFileName, !!$bProcessSections); +// } + + $sData = @\file_get_contents($sFileName); + return \is_string($sData) ? @\parse_ini_string($sData, !!$bProcessSections) : null; + } + public static function CustomBaseConvert($sNumberInput, $sFromBaseInput = '0123456789', $sToBaseInput = '0123456789') { if ($sFromBaseInput === $sToBaseInput) diff --git a/rainloop/v/0.0.0/app/libraries/Sabre/DAV/Client.php b/rainloop/v/0.0.0/app/libraries/Sabre/DAV/Client.php index 0d771c8eb..e2831bf70 100644 --- a/rainloop/v/0.0.0/app/libraries/Sabre/DAV/Client.php +++ b/rainloop/v/0.0.0/app/libraries/Sabre/DAV/Client.php @@ -483,7 +483,7 @@ class Client { if ($mr > 0) { $newurl = curl_getinfo($curl, CURLINFO_EFFECTIVE_URL); - + $rcurl = curl_copy_handle($curl); curl_setopt($rcurl, CURLOPT_HEADER, true); curl_setopt($rcurl, CURLOPT_NOBODY, true); @@ -512,7 +512,7 @@ class Client { } } } while ($code && --$mr); - + curl_close($rcurl); if ($mr > 0) { diff --git a/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/ExceptionInterface.php b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/ExceptionInterface.php new file mode 100644 index 000000000..c212e664d --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/ExceptionInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception interface for all exceptions thrown by the component. + * + * @author Romain Neutron + * + * @api + */ +interface ExceptionInterface +{ +} diff --git a/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/FileNotFoundException.php b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/FileNotFoundException.php new file mode 100644 index 000000000..15533db40 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/FileNotFoundException.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception class thrown when a file couldn't be found + * + * @author Fabien Potencier + * @author Christian Gärtner + */ +class FileNotFoundException extends IOException +{ + public function __construct($message = null, $code = 0, \Exception $previous = null, $path = null) + { + if (null === $message) { + if (null === $path) { + $message = 'File could not be found.'; + } else { + $message = sprintf('File "%s" could not be found.', $path); + } + } + + parent::__construct($message, $code, $previous, $path); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/IOException.php b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/IOException.php new file mode 100644 index 000000000..4b551af71 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/IOException.php @@ -0,0 +1,41 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * Exception class thrown when a filesystem operation failure happens + * + * @author Romain Neutron + * @author Christian Gärtner + * @author Fabien Potencier + * + * @api + */ +class IOException extends \RuntimeException implements IOExceptionInterface +{ + private $path; + + public function __construct($message, $code = 0, \Exception $previous = null, $path = null) + { + $this->path = $path; + + parent::__construct($message, $code, $previous); + } + + /** + * {@inheritdoc} + */ + public function getPath() + { + return $this->path; + } +} diff --git a/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/IOExceptionInterface.php b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/IOExceptionInterface.php new file mode 100644 index 000000000..de9f3e714 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Exception/IOExceptionInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem\Exception; + +/** + * IOException interface for file and input/output stream releated exceptions thrown by the component. + * + * @author Christian Gärtner + */ +interface IOExceptionInterface extends ExceptionInterface +{ + /** + * Returns the associated path for the exception + * + * @return string The path. + */ + public function getPath(); +} diff --git a/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Filesystem.php b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Filesystem.php new file mode 100644 index 000000000..9e45d2ba3 --- /dev/null +++ b/rainloop/v/0.0.0/app/libraries/Symfony/Component/Filesystem/Filesystem.php @@ -0,0 +1,474 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Filesystem; + +use Symfony\Component\Filesystem\Exception\IOException; +use Symfony\Component\Filesystem\Exception\FileNotFoundException; + +/** + * Provides basic utility to manipulate the file system. + * + * @author Fabien Potencier + */ +class Filesystem +{ + /** + * Copies a file. + * + * This method only copies the file if the origin file is newer than the target file. + * + * By default, if the target already exists, it is not overridden. + * + * @param string $originFile The original filename + * @param string $targetFile The target filename + * @param boolean $override Whether to override an existing file or not + * + * @throws FileNotFoundException When orginFile doesn't exist + * @throws IOException When copy fails + */ + public function copy($originFile, $targetFile, $override = false) + { + if (stream_is_local($originFile) && !is_file($originFile)) { + throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile); + } + + $this->mkdir(dirname($targetFile)); + + if (!$override && is_file($targetFile)) { + $doCopy = filemtime($originFile) > filemtime($targetFile); + } else { + $doCopy = true; + } + + if ($doCopy) { + // https://bugs.php.net/bug.php?id=64634 + $source = fopen($originFile, 'r'); + $target = fopen($targetFile, 'w+'); + stream_copy_to_stream($source, $target); + fclose($source); + fclose($target); + unset($source, $target); + + if (!is_file($targetFile)) { + throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile); + } + } + } + + /** + * Creates a directory recursively. + * + * @param string|array|\Traversable $dirs The directory path + * @param integer $mode The directory mode + * + * @throws IOException On any directory creation failure + */ + public function mkdir($dirs, $mode = 0777) + { + foreach ($this->toIterator($dirs) as $dir) { + if (is_dir($dir)) { + continue; + } + + if (true !== @mkdir($dir, $mode, true)) { + throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir); + } + } + } + + /** + * Checks the existence of files or directories. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to check + * + * @return Boolean true if the file exists, false otherwise + */ + public function exists($files) + { + foreach ($this->toIterator($files) as $file) { + if (!file_exists($file)) { + return false; + } + } + + return true; + } + + /** + * Sets access and modification time of file. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to create + * @param integer $time The touch time as a unix timestamp + * @param integer $atime The access time as a unix timestamp + * + * @throws IOException When touch fails + */ + public function touch($files, $time = null, $atime = null) + { + foreach ($this->toIterator($files) as $file) { + $touch = $time ? @touch($file, $time, $atime) : @touch($file); + if (true !== $touch) { + throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file); + } + } + } + + /** + * Removes files or directories. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to remove + * + * @throws IOException When removal fails + */ + public function remove($files) + { + $files = iterator_to_array($this->toIterator($files)); + $files = array_reverse($files); + foreach ($files as $file) { + if (!file_exists($file) && !is_link($file)) { + continue; + } + + if (is_dir($file) && !is_link($file)) { + $this->remove(new \FilesystemIterator($file)); + + if (true !== @rmdir($file)) { + throw new IOException(sprintf('Failed to remove directory "%s".', $file), 0, null, $file); + } + } else { + // https://bugs.php.net/bug.php?id=52176 + if (defined('PHP_WINDOWS_VERSION_MAJOR') && is_dir($file)) { + if (true !== @rmdir($file)) { + throw new IOException(sprintf('Failed to remove file "%s".', $file), 0, null, $file); + } + } else { + if (true !== @unlink($file)) { + throw new IOException(sprintf('Failed to remove file "%s".', $file), 0, null, $file); + } + } + } + } + } + + /** + * Change mode for an array of files or directories. + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change mode + * @param integer $mode The new mode (octal) + * @param integer $umask The mode mask (octal) + * @param Boolean $recursive Whether change the mod recursively or not + * + * @throws IOException When the change fail + */ + public function chmod($files, $mode, $umask = 0000, $recursive = false) + { + foreach ($this->toIterator($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chmod(new \FilesystemIterator($file), $mode, $umask, true); + } + if (true !== @chmod($file, $mode & ~$umask)) { + throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file); + } + } + } + + /** + * Change the owner of an array of files or directories + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change owner + * @param string $user The new owner user name + * @param Boolean $recursive Whether change the owner recursively or not + * + * @throws IOException When the change fail + */ + public function chown($files, $user, $recursive = false) + { + foreach ($this->toIterator($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chown(new \FilesystemIterator($file), $user, true); + } + if (is_link($file) && function_exists('lchown')) { + if (true !== @lchown($file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); + } + } else { + if (true !== @chown($file, $user)) { + throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file); + } + } + } + } + + /** + * Change the group of an array of files or directories + * + * @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance to change group + * @param string $group The group name + * @param Boolean $recursive Whether change the group recursively or not + * + * @throws IOException When the change fail + */ + public function chgrp($files, $group, $recursive = false) + { + foreach ($this->toIterator($files) as $file) { + if ($recursive && is_dir($file) && !is_link($file)) { + $this->chgrp(new \FilesystemIterator($file), $group, true); + } + if (is_link($file) && function_exists('lchgrp')) { + if (true !== @lchgrp($file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); + } + } else { + if (true !== @chgrp($file, $group)) { + throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); + } + } + } + } + + /** + * Renames a file or a directory. + * + * @param string $origin The origin filename or directory + * @param string $target The new filename or directory + * @param Boolean $overwrite Whether to overwrite the target if it already exists + * + * @throws IOException When target file or directory already exists + * @throws IOException When origin cannot be renamed + */ + public function rename($origin, $target, $overwrite = false) + { + // we check that target does not exist + if (!$overwrite && is_readable($target)) { + throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target); + } + + if (true !== @rename($origin, $target)) { + throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target); + } + } + + /** + * Creates a symbolic link or copy a directory. + * + * @param string $originDir The origin directory path + * @param string $targetDir The symbolic link name + * @param Boolean $copyOnWindows Whether to copy files if on Windows + * + * @throws IOException When symlink fails + */ + public function symlink($originDir, $targetDir, $copyOnWindows = false) + { + if (!function_exists('symlink') && $copyOnWindows) { + $this->mirror($originDir, $targetDir); + + return; + } + + $this->mkdir(dirname($targetDir)); + + $ok = false; + if (is_link($targetDir)) { + if (readlink($targetDir) != $originDir) { + $this->remove($targetDir); + } else { + $ok = true; + } + } + + if (!$ok) { + if (true !== @symlink($originDir, $targetDir)) { + $report = error_get_last(); + if (is_array($report)) { + if (defined('PHP_WINDOWS_VERSION_MAJOR') && false !== strpos($report['message'], 'error code(1314)')) { + throw new IOException('Unable to create symlink due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?'); + } + } + + throw new IOException(sprintf('Failed to create symbolic link from "%s" to "%s".', $originDir, $targetDir), 0, null, $targetDir); + } + } + } + + /** + * Given an existing path, convert it to a path relative to a given starting path + * + * @param string $endPath Absolute path of target + * @param string $startPath Absolute path where traversal begins + * + * @return string Path of target relative to starting path + */ + public function makePathRelative($endPath, $startPath) + { + // Normalize separators on windows + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $endPath = strtr($endPath, '\\', '/'); + $startPath = strtr($startPath, '\\', '/'); + } + + // Split the paths into arrays + $startPathArr = explode('/', trim($startPath, '/')); + $endPathArr = explode('/', trim($endPath, '/')); + + // Find for which directory the common path stops + $index = 0; + while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) { + $index++; + } + + // Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels) + $depth = count($startPathArr) - $index; + + // Repeated "../" for each level need to reach the common path + $traverser = str_repeat('../', $depth); + + $endPathRemainder = implode('/', array_slice($endPathArr, $index)); + + // Construct $endPath from traversing to the common path, then to the remaining $endPath + $relativePath = $traverser.(strlen($endPathRemainder) > 0 ? $endPathRemainder.'/' : ''); + + return (strlen($relativePath) === 0) ? './' : $relativePath; + } + + /** + * Mirrors a directory to another. + * + * @param string $originDir The origin directory + * @param string $targetDir The target directory + * @param \Traversable $iterator A Traversable instance + * @param array $options An array of boolean options + * Valid options are: + * - $options['override'] Whether to override an existing file on copy or not (see copy()) + * - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink()) + * - $options['delete'] Whether to delete files that are not in the source directory (defaults to false) + * + * @throws IOException When file type is unknown + */ + public function mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array()) + { + $targetDir = rtrim($targetDir, '/\\'); + $originDir = rtrim($originDir, '/\\'); + + // Iterate in destination folder to remove obsolete entries + if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) { + $deleteIterator = $iterator; + if (null === $deleteIterator) { + $flags = \FilesystemIterator::SKIP_DOTS; + $deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST); + } + foreach ($deleteIterator as $file) { + $origin = str_replace($targetDir, $originDir, $file->getPathname()); + if (!$this->exists($origin)) { + $this->remove($file); + } + } + } + + $copyOnWindows = false; + if (isset($options['copy_on_windows']) && !function_exists('symlink')) { + $copyOnWindows = $options['copy_on_windows']; + } + + if (null === $iterator) { + $flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS; + $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST); + } + + foreach ($iterator as $file) { + $target = str_replace($originDir, $targetDir, $file->getPathname()); + + if ($copyOnWindows) { + if (is_link($file) || is_file($file)) { + $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); + } elseif (is_dir($file)) { + $this->mkdir($target); + } else { + throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); + } + } else { + if (is_link($file)) { + $this->symlink($file, $target); + } elseif (is_dir($file)) { + $this->mkdir($target); + } elseif (is_file($file)) { + $this->copy($file, $target, isset($options['override']) ? $options['override'] : false); + } else { + throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file); + } + } + } + } + + /** + * Returns whether the file path is an absolute path. + * + * @param string $file A file path + * + * @return Boolean + */ + public function isAbsolutePath($file) + { + if (strspn($file, '/\\', 0, 1) + || (strlen($file) > 3 && ctype_alpha($file[0]) + && substr($file, 1, 1) === ':' + && (strspn($file, '/\\', 2, 1)) + ) + || null !== parse_url($file, PHP_URL_SCHEME) + ) { + return true; + } + + return false; + } + + /** + * Atomically dumps content into a file. + * + * @param string $filename The file to be written to. + * @param string $content The data to write into the file. + * @param integer $mode The file mode (octal). + * @throws IOException If the file cannot be written to. + */ + public function dumpFile($filename, $content, $mode = 0666) + { + $dir = dirname($filename); + + if (!is_dir($dir)) { + $this->mkdir($dir); + } elseif (!is_writable($dir)) { + throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir); + } + + $tmpFile = tempnam($dir, basename($filename)); + + if (false === @file_put_contents($tmpFile, $content)) { + throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); + } + + $this->rename($tmpFile, $filename, true); + $this->chmod($filename, $mode); + } + + /** + * @param mixed $files + * + * @return \Traversable + */ + private function toIterator($files) + { + if (!$files instanceof \Traversable) { + $files = new \ArrayObject(is_array($files) ? $files : array($files)); + } + + return $files; + } +} diff --git a/rainloop/v/0.0.0/app/templates/Index.html b/rainloop/v/0.0.0/app/templates/Index.html index d53c8b567..7eed16e6d 100644 --- a/rainloop/v/0.0.0/app/templates/Index.html +++ b/rainloop/v/0.0.0/app/templates/Index.html @@ -11,7 +11,7 @@ - - + - + +
- {{BaseAppLoadingDescription}} +
Loading
-
An Error occurred,
please refresh the page and try again. +
@@ -69,31 +85,54 @@
- - - - - -

- CKEditor Samples » Create and Destroy Editor Instances for Ajax Applications -

-
-

- This sample shows how to create and destroy CKEditor instances on the fly. After the removal of CKEditor the content created inside the editing - area will be displayed in a <div> element. -

-

- For details of how to create this setup check the source code of this sample page - for JavaScript code responsible for the creation and destruction of a CKEditor instance. -

-
-

Click the buttons to create and remove a CKEditor instance.

-

- - -

- -
-
- - - - + + + + + + Ajax — CKEditor Sample + + + + + +

+ CKEditor Samples » Create and Destroy Editor Instances for Ajax Applications +

+
+

+ This sample shows how to create and destroy CKEditor instances on the fly. After the removal of CKEditor the content created inside the editing + area will be displayed in a <div> element. +

+

+ For details of how to create this setup check the source code of this sample page + for JavaScript code responsible for the creation and destruction of a CKEditor instance. +

+
+

Click the buttons to create and remove a CKEditor instance.

+

+ + +

+ +
+
+ + + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/api.html b/rainloop/v/0.0.0/static/ckeditor/samples/api.html index a957eed02..50f568edf 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/api.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/api.html @@ -1,207 +1,207 @@ - - - - - - API Usage — CKEditor Sample - - - - - - -

- CKEditor Samples » Using CKEditor JavaScript API -

-
-

- This sample shows how to use the - CKEditor JavaScript API - to interact with the editor at runtime. -

-

- For details on how to create this setup check the source code of this sample page. -

-
- - -
- -
-
- - - - -

-

- - -
- - - + + + + + + API Usage — CKEditor Sample + + + + + + +

+ CKEditor Samples » Using CKEditor JavaScript API +

+
+

+ This sample shows how to use the + CKEditor JavaScript API + to interact with the editor at runtime. +

+

+ For details on how to create this setup check the source code of this sample page. +

+
+ + +
+ +
+
+ + + + +

+

+ + +
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/appendto.html b/rainloop/v/0.0.0/static/ckeditor/samples/appendto.html index a84b0ee18..8ed16b6fc 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/appendto.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/appendto.html @@ -1,6 +1,6 @@ @@ -48,7 +48,7 @@ For licensing, see LICENSE.md or http://ckeditor.com/license CKEditor - The text editor for the Internet - http://ckeditor.com

- Copyright © 2003-2014, CKSource - Frederico + Copyright © 2003-2015, CKSource - Frederico Knabben. All rights reserved.

diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/assets/inlineall/logo.png b/rainloop/v/0.0.0/static/ckeditor/samples/assets/inlineall/logo.png index 334e7ac99..b4d5979e3 100644 Binary files a/rainloop/v/0.0.0/static/ckeditor/samples/assets/inlineall/logo.png and b/rainloop/v/0.0.0/static/ckeditor/samples/assets/inlineall/logo.png differ diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/assets/outputxhtml/outputxhtml.css b/rainloop/v/0.0.0/static/ckeditor/samples/assets/outputxhtml/outputxhtml.css index fa0ff379b..1b3bf6467 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/assets/outputxhtml/outputxhtml.css +++ b/rainloop/v/0.0.0/static/ckeditor/samples/assets/outputxhtml/outputxhtml.css @@ -1,204 +1,204 @@ -/* - * Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or http://ckeditor.com/license - * - * Styles used by the XHTML 1.1 sample page (xhtml.html). - */ - -/** - * Basic definitions for the editing area. - */ -body -{ - font-family: Arial, Verdana, sans-serif; - font-size: 80%; - color: #000000; - background-color: #ffffff; - padding: 5px; - margin: 0px; -} - -/** - * Core styles. - */ - -.Bold -{ - font-weight: bold; -} - -.Italic -{ - font-style: italic; -} - -.Underline -{ - text-decoration: underline; -} - -.StrikeThrough -{ - text-decoration: line-through; -} - -.Subscript -{ - vertical-align: sub; - font-size: smaller; -} - -.Superscript -{ - vertical-align: super; - font-size: smaller; -} - -/** - * Font faces. - */ - -.FontComic -{ - font-family: 'Comic Sans MS'; -} - -.FontCourier -{ - font-family: 'Courier New'; -} - -.FontTimes -{ - font-family: 'Times New Roman'; -} - -/** - * Font sizes. - */ - -.FontSmaller -{ - font-size: smaller; -} - -.FontLarger -{ - font-size: larger; -} - -.FontSmall -{ - font-size: 8pt; -} - -.FontBig -{ - font-size: 14pt; -} - -.FontDouble -{ - font-size: 200%; -} - -/** - * Font colors. - */ -.FontColor1 -{ - color: #ff9900; -} - -.FontColor2 -{ - color: #0066cc; -} - -.FontColor3 -{ - color: #ff0000; -} - -.FontColor1BG -{ - background-color: #ff9900; -} - -.FontColor2BG -{ - background-color: #0066cc; -} - -.FontColor3BG -{ - background-color: #ff0000; -} - -/** - * Indentation. - */ - -.Indent1 -{ - margin-left: 40px; -} - -.Indent2 -{ - margin-left: 80px; -} - -.Indent3 -{ - margin-left: 120px; -} - -/** - * Alignment. - */ - -.JustifyLeft -{ - text-align: left; -} - -.JustifyRight -{ - text-align: right; -} - -.JustifyCenter -{ - text-align: center; -} - -.JustifyFull -{ - text-align: justify; -} - -/** - * Other. - */ - -code -{ - font-family: courier, monospace; - background-color: #eeeeee; - padding-left: 1px; - padding-right: 1px; - border: #c0c0c0 1px solid; -} - -kbd -{ - padding: 0px 1px 0px 1px; - border-width: 1px 2px 2px 1px; - border-style: solid; -} - -blockquote -{ - color: #808080; -} +/* + * Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + * + * Styles used by the XHTML 1.1 sample page (xhtml.html). + */ + +/** + * Basic definitions for the editing area. + */ +body +{ + font-family: Arial, Verdana, sans-serif; + font-size: 80%; + color: #000000; + background-color: #ffffff; + padding: 5px; + margin: 0px; +} + +/** + * Core styles. + */ + +.Bold +{ + font-weight: bold; +} + +.Italic +{ + font-style: italic; +} + +.Underline +{ + text-decoration: underline; +} + +.StrikeThrough +{ + text-decoration: line-through; +} + +.Subscript +{ + vertical-align: sub; + font-size: smaller; +} + +.Superscript +{ + vertical-align: super; + font-size: smaller; +} + +/** + * Font faces. + */ + +.FontComic +{ + font-family: 'Comic Sans MS'; +} + +.FontCourier +{ + font-family: 'Courier New'; +} + +.FontTimes +{ + font-family: 'Times New Roman'; +} + +/** + * Font sizes. + */ + +.FontSmaller +{ + font-size: smaller; +} + +.FontLarger +{ + font-size: larger; +} + +.FontSmall +{ + font-size: 8pt; +} + +.FontBig +{ + font-size: 14pt; +} + +.FontDouble +{ + font-size: 200%; +} + +/** + * Font colors. + */ +.FontColor1 +{ + color: #ff9900; +} + +.FontColor2 +{ + color: #0066cc; +} + +.FontColor3 +{ + color: #ff0000; +} + +.FontColor1BG +{ + background-color: #ff9900; +} + +.FontColor2BG +{ + background-color: #0066cc; +} + +.FontColor3BG +{ + background-color: #ff0000; +} + +/** + * Indentation. + */ + +.Indent1 +{ + margin-left: 40px; +} + +.Indent2 +{ + margin-left: 80px; +} + +.Indent3 +{ + margin-left: 120px; +} + +/** + * Alignment. + */ + +.JustifyLeft +{ + text-align: left; +} + +.JustifyRight +{ + text-align: right; +} + +.JustifyCenter +{ + text-align: center; +} + +.JustifyFull +{ + text-align: justify; +} + +/** + * Other. + */ + +code +{ + font-family: courier, monospace; + background-color: #eeeeee; + padding-left: 1px; + padding-right: 1px; + border: #c0c0c0 1px solid; +} + +kbd +{ + padding: 0px 1px 0px 1px; + border-width: 1px 2px 2px 1px; + border-style: solid; +} + +blockquote +{ + color: #808080; +} diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/assets/posteddata.php b/rainloop/v/0.0.0/static/ckeditor/samples/assets/posteddata.php index 6b26aae3b..1e1406f23 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/assets/posteddata.php +++ b/rainloop/v/0.0.0/static/ckeditor/samples/assets/posteddata.php @@ -1,59 +1,59 @@ - - - - - - Sample — CKEditor - - - -

- CKEditor — Posted Data -

- - - - - - - - - $value ) - { - if ( ( !is_string($value) && !is_numeric($value) ) || !is_string($key) ) - continue; - - if ( get_magic_quotes_gpc() ) - $value = htmlspecialchars( stripslashes((string)$value) ); - else - $value = htmlspecialchars( (string)$value ); -?> - - - - - -
Field NameValue
- - - + + + + + + Sample — CKEditor + + + +

+ CKEditor — Posted Data +

+ + + + + + + + + $value ) + { + if ( ( !is_string($value) && !is_numeric($value) ) || !is_string($key) ) + continue; + + if ( get_magic_quotes_gpc() ) + $value = htmlspecialchars( stripslashes((string)$value) ); + else + $value = htmlspecialchars( (string)$value ); +?> + + + + + +
Field NameValue
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/assets/sample.jpg b/rainloop/v/0.0.0/static/ckeditor/samples/assets/sample.jpg index a4a77fae8..9498271c6 100644 Binary files a/rainloop/v/0.0.0/static/ckeditor/samples/assets/sample.jpg and b/rainloop/v/0.0.0/static/ckeditor/samples/assets/sample.jpg differ diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/assets/uilanguages/languages.js b/rainloop/v/0.0.0/static/ckeditor/samples/assets/uilanguages/languages.js index 3f7ff624f..5494469eb 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/assets/uilanguages/languages.js +++ b/rainloop/v/0.0.0/static/ckeditor/samples/assets/uilanguages/languages.js @@ -1,5 +1,5 @@ -/* - Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. +/* + Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. For licensing, see LICENSE.md or http://ckeditor.com/license */ var CKEDITOR_LANGS=function(){var c={af:"Afrikaans",ar:"Arabic",bg:"Bulgarian",bn:"Bengali/Bangla",bs:"Bosnian",ca:"Catalan",cs:"Czech",cy:"Welsh",da:"Danish",de:"German",el:"Greek",en:"English","en-au":"English (Australia)","en-ca":"English (Canadian)","en-gb":"English (United Kingdom)",eo:"Esperanto",es:"Spanish",et:"Estonian",eu:"Basque",fa:"Persian",fi:"Finnish",fo:"Faroese",fr:"French","fr-ca":"French (Canada)",gl:"Galician",gu:"Gujarati",he:"Hebrew",hi:"Hindi",hr:"Croatian",hu:"Hungarian",id:"Indonesian", diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/datafiltering.html b/rainloop/v/0.0.0/static/ckeditor/samples/datafiltering.html index 7f2f2a73f..1165594ae 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/datafiltering.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/datafiltering.html @@ -1,401 +1,401 @@ - - - - - - Data Filtering — CKEditor Sample - - - - - -

- CKEditor Samples » Data Filtering and Features Activation -

-
-

- This sample page demonstrates the idea of Advanced Content Filter - (ACF), a sophisticated - tool that takes control over what kind of data is accepted by the editor and what - kind of output is produced. -

-

When and what is being filtered?

-

- ACF controls - every single source of data that comes to the editor. - It process both HTML that is inserted manually (i.e. pasted by the user) - and programmatically like: -

-
-editor.setData( '<p>Hello world!</p>' );
-
-

- ACF discards invalid, - useless HTML tags and attributes so the editor remains "clean" during - runtime. ACF behaviour - can be configured and adjusted for a particular case to prevent the - output HTML (i.e. in CMS systems) from being polluted. - - This kind of filtering is a first, client-side line of defense - against "tag soups", - the tool that precisely restricts which tags, attributes and styles - are allowed (desired). When properly configured, ACF - is an easy and fast way to produce a high-quality, intentionally filtered HTML. -

- -

How to configure or disable ACF?

-

- Advanced Content Filter is enabled by default, working in "automatic mode", yet - it provides a set of easy rules that allow adjusting filtering rules - and disabling the entire feature when necessary. The config property - responsible for this feature is config.allowedContent. -

-

- By "automatic mode" is meant that loaded plugins decide which kind - of content is enabled and which is not. For example, if the link - plugin is loaded it implies that <a> tag is - automatically allowed. Each plugin is given a set - of predefined ACF rules - that control the editor until - config.allowedContent - is defined manually. -

-

- Let's assume our intention is to restrict the editor to accept (produce) paragraphs - only: no attributes, no styles, no other tags. - With ACF - this is very simple. Basically set - config.allowedContent to 'p': -

-
-var editor = CKEDITOR.replace( textarea_id, {
-	allowedContent: 'p'
-} );
-
-

- Now try to play with allowed content: -

-
-// Trying to insert disallowed tag and attribute.
-editor.setData( '<p style="color: red">Hello <em>world</em>!</p>' );
-alert( editor.getData() );
-
-// Filtered data is returned.
-"<p>Hello world!</p>"
-
-

- What happened? Since config.allowedContent: 'p' is set the editor assumes - that only plain <p> are accepted. Nothing more. This is why - style attribute and <em> tag are gone. The same - filtering would happen if we pasted disallowed HTML into this editor. -

-

- This is just a small sample of what ACF - can do. To know more, please refer to the sample section below and - the official Advanced Content Filter guide. -

-

- You may, of course, want CKEditor to avoid filtering of any kind. - To get rid of ACF, - basically set - config.allowedContent to true like this: -

-
-CKEDITOR.replace( textarea_id, {
-	allowedContent: true
-} );
-
- -

Beyond data flow: Features activation

-

- ACF is far more than - I/O control: the entire - UI of the editor is adjusted to what - filters restrict. For example: if <a> tag is - disallowed - by ACF, - then accordingly link command, toolbar button and link dialog - are also disabled. Editor is smart: it knows which features must be - removed from the interface to match filtering rules. -

-

- CKEditor can be far more specific. If <a> tag is - allowed by filtering rules to be used but it is restricted - to have only one attribute (href) - config.allowedContent = 'a[!href]', then - "Target" tab of the link dialog is automatically disabled as target - attribute isn't included in ACF rules - for <a>. This behaviour applies to dialog fields, context - menus and toolbar buttons. -

- -

Sample configurations

-

- There are several editor instances below that present different - ACF setups. All of them, - except the last inline instance, share the same HTML content to visualize - how different filtering rules affect the same input data. -

-
- -
- -
-

- This editor is using default configuration ("automatic mode"). It means that - - config.allowedContent is defined by loaded plugins. - Each plugin extends filtering rules to make it's own associated content - available for the user. -

-
- - - -
- -
- -
- -
-

- This editor is using a custom configuration for - ACF: -

-
-CKEDITOR.replace( 'editor2', {
-	allowedContent:
-		'h1 h2 h3 p blockquote strong em;' +
-		'a[!href];' +
-		'img(left,right)[!src,alt,width,height];' +
-		'table tr th td caption;' +
-		'span{!font-family};' +'
-		'span{!color};' +
-		'span(!marker);' +
-		'del ins'
-} );
-
-

- The following rules may require additional explanation: -

-
    -
  • - h1 h2 h3 p blockquote strong em - These tags - are accepted by the editor. Any tag attributes will be discarded. -
  • -
  • - a[!href] - href attribute is obligatory - for <a> tag. Tags without this attribute - are disarded. No other attribute will be accepted. -
  • -
  • - img(left,right)[!src,alt,width,height] - src - attribute is obligatory for <img> tag. - alt, width, height - and class attributes are accepted but - class must be either class="left" - or class="right" -
  • -
  • - table tr th td caption - These tags - are accepted by the editor. Any tag attributes will be discarded. -
  • -
  • - span{!font-family}, span{!color}, - span(!marker) - <span> tags - will be accepted if either font-family or - color style is set or class="marker" - is present. -
  • -
  • - del ins - These tags - are accepted by the editor. Any tag attributes will be discarded. -
  • -
-

- Please note that UI of the - editor is different. It's a response to what happened to the filters. - Since text-align isn't allowed, the align toolbar is gone. - The same thing happened to subscript/superscript, strike, underline - (<u>, <sub>, <sup> - are disallowed by - config.allowedContent) and many other buttons. -

-
- - -
- -
- -
- -
-

- This editor is using a custom configuration for - ACF. - Note that filters can be configured as an object literal - as an alternative to a string-based definition. -

-
-CKEDITOR.replace( 'editor3', {
-	allowedContent: {
-		'b i ul ol big small': true,
-		'h1 h2 h3 p blockquote li': {
-			styles: 'text-align'
-		},
-		a: { attributes: '!href,target' },
-		img: {
-			attributes: '!src,alt',
-			styles: 'width,height',
-			classes: 'left,right'
-		}
-	}
-} );
-
-
- - -
- -
- -
- -
-

- This editor is using a custom set of plugins and buttons. -

-
-CKEDITOR.replace( 'editor4', {
-	removePlugins: 'bidi,font,forms,flash,horizontalrule,iframe,justify,table,tabletools,smiley',
-	removeButtons: 'Anchor,Underline,Strike,Subscript,Superscript,Image',
-	format_tags: 'p;h1;h2;h3;pre;address'
-} );
-
-

- As you can see, removing plugins and buttons implies filtering. - Several tags are not allowed in the editor because there's no - plugin/button that is responsible for creating and editing this - kind of content (for example: the image is missing because - of removeButtons: 'Image'). The conclusion is that - ACF works "backwards" - as well: modifying UI - elements is changing allowed content rules. -

-
- - -
- -
- -
- -
-

- This editor is built on editable <h1> element. - ACF takes care of - what can be included in <h1>. Note that there - are no block styles in Styles combo. Also why lists, indentation, - blockquote, div, form and other buttons are missing. -

-

- ACF makes sure that - no disallowed tags will come to <h1> so the final - markup is valid. If the user tried to paste some invalid HTML - into this editor (let's say a list), it would be automatically - converted into plain text. -

-
-

- Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC. -

-
- - - - + + + + + + Data Filtering — CKEditor Sample + + + + + +

+ CKEditor Samples » Data Filtering and Features Activation +

+
+

+ This sample page demonstrates the idea of Advanced Content Filter + (ACF), a sophisticated + tool that takes control over what kind of data is accepted by the editor and what + kind of output is produced. +

+

When and what is being filtered?

+

+ ACF controls + every single source of data that comes to the editor. + It process both HTML that is inserted manually (i.e. pasted by the user) + and programmatically like: +

+
+editor.setData( '<p>Hello world!</p>' );
+
+

+ ACF discards invalid, + useless HTML tags and attributes so the editor remains "clean" during + runtime. ACF behaviour + can be configured and adjusted for a particular case to prevent the + output HTML (i.e. in CMS systems) from being polluted. + + This kind of filtering is a first, client-side line of defense + against "tag soups", + the tool that precisely restricts which tags, attributes and styles + are allowed (desired). When properly configured, ACF + is an easy and fast way to produce a high-quality, intentionally filtered HTML. +

+ +

How to configure or disable ACF?

+

+ Advanced Content Filter is enabled by default, working in "automatic mode", yet + it provides a set of easy rules that allow adjusting filtering rules + and disabling the entire feature when necessary. The config property + responsible for this feature is config.allowedContent. +

+

+ By "automatic mode" is meant that loaded plugins decide which kind + of content is enabled and which is not. For example, if the link + plugin is loaded it implies that <a> tag is + automatically allowed. Each plugin is given a set + of predefined ACF rules + that control the editor until + config.allowedContent + is defined manually. +

+

+ Let's assume our intention is to restrict the editor to accept (produce) paragraphs + only: no attributes, no styles, no other tags. + With ACF + this is very simple. Basically set + config.allowedContent to 'p': +

+
+var editor = CKEDITOR.replace( textarea_id, {
+	allowedContent: 'p'
+} );
+
+

+ Now try to play with allowed content: +

+
+// Trying to insert disallowed tag and attribute.
+editor.setData( '<p style="color: red">Hello <em>world</em>!</p>' );
+alert( editor.getData() );
+
+// Filtered data is returned.
+"<p>Hello world!</p>"
+
+

+ What happened? Since config.allowedContent: 'p' is set the editor assumes + that only plain <p> are accepted. Nothing more. This is why + style attribute and <em> tag are gone. The same + filtering would happen if we pasted disallowed HTML into this editor. +

+

+ This is just a small sample of what ACF + can do. To know more, please refer to the sample section below and + the official Advanced Content Filter guide. +

+

+ You may, of course, want CKEditor to avoid filtering of any kind. + To get rid of ACF, + basically set + config.allowedContent to true like this: +

+
+CKEDITOR.replace( textarea_id, {
+	allowedContent: true
+} );
+
+ +

Beyond data flow: Features activation

+

+ ACF is far more than + I/O control: the entire + UI of the editor is adjusted to what + filters restrict. For example: if <a> tag is + disallowed + by ACF, + then accordingly link command, toolbar button and link dialog + are also disabled. Editor is smart: it knows which features must be + removed from the interface to match filtering rules. +

+

+ CKEditor can be far more specific. If <a> tag is + allowed by filtering rules to be used but it is restricted + to have only one attribute (href) + config.allowedContent = 'a[!href]', then + "Target" tab of the link dialog is automatically disabled as target + attribute isn't included in ACF rules + for <a>. This behaviour applies to dialog fields, context + menus and toolbar buttons. +

+ +

Sample configurations

+

+ There are several editor instances below that present different + ACF setups. All of them, + except the last inline instance, share the same HTML content to visualize + how different filtering rules affect the same input data. +

+
+ +
+ +
+

+ This editor is using default configuration ("automatic mode"). It means that + + config.allowedContent is defined by loaded plugins. + Each plugin extends filtering rules to make it's own associated content + available for the user. +

+
+ + + +
+ +
+ +
+ +
+

+ This editor is using a custom configuration for + ACF: +

+
+CKEDITOR.replace( 'editor2', {
+	allowedContent:
+		'h1 h2 h3 p blockquote strong em;' +
+		'a[!href];' +
+		'img(left,right)[!src,alt,width,height];' +
+		'table tr th td caption;' +
+		'span{!font-family};' +'
+		'span{!color};' +
+		'span(!marker);' +
+		'del ins'
+} );
+
+

+ The following rules may require additional explanation: +

+
    +
  • + h1 h2 h3 p blockquote strong em - These tags + are accepted by the editor. Any tag attributes will be discarded. +
  • +
  • + a[!href] - href attribute is obligatory + for <a> tag. Tags without this attribute + are disarded. No other attribute will be accepted. +
  • +
  • + img(left,right)[!src,alt,width,height] - src + attribute is obligatory for <img> tag. + alt, width, height + and class attributes are accepted but + class must be either class="left" + or class="right" +
  • +
  • + table tr th td caption - These tags + are accepted by the editor. Any tag attributes will be discarded. +
  • +
  • + span{!font-family}, span{!color}, + span(!marker) - <span> tags + will be accepted if either font-family or + color style is set or class="marker" + is present. +
  • +
  • + del ins - These tags + are accepted by the editor. Any tag attributes will be discarded. +
  • +
+

+ Please note that UI of the + editor is different. It's a response to what happened to the filters. + Since text-align isn't allowed, the align toolbar is gone. + The same thing happened to subscript/superscript, strike, underline + (<u>, <sub>, <sup> + are disallowed by + config.allowedContent) and many other buttons. +

+
+ + +
+ +
+ +
+ +
+

+ This editor is using a custom configuration for + ACF. + Note that filters can be configured as an object literal + as an alternative to a string-based definition. +

+
+CKEDITOR.replace( 'editor3', {
+	allowedContent: {
+		'b i ul ol big small': true,
+		'h1 h2 h3 p blockquote li': {
+			styles: 'text-align'
+		},
+		a: { attributes: '!href,target' },
+		img: {
+			attributes: '!src,alt',
+			styles: 'width,height',
+			classes: 'left,right'
+		}
+	}
+} );
+
+
+ + +
+ +
+ +
+ +
+

+ This editor is using a custom set of plugins and buttons. +

+
+CKEDITOR.replace( 'editor4', {
+	removePlugins: 'bidi,font,forms,flash,horizontalrule,iframe,justify,table,tabletools,smiley',
+	removeButtons: 'Anchor,Underline,Strike,Subscript,Superscript,Image',
+	format_tags: 'p;h1;h2;h3;pre;address'
+} );
+
+

+ As you can see, removing plugins and buttons implies filtering. + Several tags are not allowed in the editor because there's no + plugin/button that is responsible for creating and editing this + kind of content (for example: the image is missing because + of removeButtons: 'Image'). The conclusion is that + ACF works "backwards" + as well: modifying UI + elements is changing allowed content rules. +

+
+ + +
+ +
+ +
+ +
+

+ This editor is built on editable <h1> element. + ACF takes care of + what can be included in <h1>. Note that there + are no block styles in Styles combo. Also why lists, indentation, + blockquote, div, form and other buttons are missing. +

+

+ ACF makes sure that + no disallowed tags will come to <h1> so the final + markup is valid. If the user tried to paste some invalid HTML + into this editor (let's say a list), it would be automatically + converted into plain text. +

+
+

+ Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC. +

+
+ + + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/divreplace.html b/rainloop/v/0.0.0/static/ckeditor/samples/divreplace.html index 873c8c2e5..b87086a60 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/divreplace.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/divreplace.html @@ -1,141 +1,141 @@ - - - - - - Replace DIV — CKEditor Sample - - - - - - -

- CKEditor Samples » Replace DIV with CKEditor on the Fly -

-
-

- This sample shows how to automatically replace <div> elements - with a CKEditor instance on the fly, following user's doubleclick. The content - that was previously placed inside the <div> element will now - be moved into CKEditor editing area. -

-

- For details on how to create this setup check the source code of this sample page. -

-
-

- Double-click any of the following <div> elements to transform them into - editor instances. -

-
-

- Part 1 -

-

- Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi - semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna - rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla - nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce - eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. -

-
-
-

- Part 2 -

-

- Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi - semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna - rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla - nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce - eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. -

-

- Donec velit. Mauris massa. Vestibulum non nulla. Nam suscipit arcu nec elit. Phasellus - sollicitudin iaculis ante. Ut non mauris et sapien tincidunt adipiscing. Vestibulum - vitae leo. Suspendisse nec mi tristique nulla laoreet vulputate. -

-
-
-

- Part 3 -

-

- Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi - semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna - rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla - nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce - eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. -

-
- - - + + + + + + Replace DIV — CKEditor Sample + + + + + + +

+ CKEditor Samples » Replace DIV with CKEditor on the Fly +

+
+

+ This sample shows how to automatically replace <div> elements + with a CKEditor instance on the fly, following user's doubleclick. The content + that was previously placed inside the <div> element will now + be moved into CKEditor editing area. +

+

+ For details on how to create this setup check the source code of this sample page. +

+
+

+ Double-click any of the following <div> elements to transform them into + editor instances. +

+
+

+ Part 1 +

+

+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi + semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna + rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla + nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce + eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. +

+
+
+

+ Part 2 +

+

+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi + semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna + rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla + nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce + eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. +

+

+ Donec velit. Mauris massa. Vestibulum non nulla. Nam suscipit arcu nec elit. Phasellus + sollicitudin iaculis ante. Ut non mauris et sapien tincidunt adipiscing. Vestibulum + vitae leo. Suspendisse nec mi tristique nulla laoreet vulputate. +

+
+
+

+ Part 3 +

+

+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Cras et ipsum quis mi + semper accumsan. Integer pretium dui id massa. Suspendisse in nisl sit amet urna + rutrum imperdiet. Nulla eu tellus. Donec ante nisi, ullamcorper quis, fringilla + nec, sagittis eleifend, pede. Nulla commodo interdum massa. Donec id metus. Fusce + eu ipsum. Suspendisse auctor. Phasellus fermentum porttitor risus. +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/index.html b/rainloop/v/0.0.0/static/ckeditor/samples/index.html index a2311312e..ce98e9fc2 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/index.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/index.html @@ -1,6 +1,6 @@ @@ -48,6 +48,9 @@ For licensing, see LICENSE.md or http://ckeditor.com/license
Shared-Space plugin
Having the toolbar and the bottom bar spaces shared by different editor instances.
+ +
TableResize plugin
+
Using the TableResize plugin to enable table column resizing.
@@ -106,12 +109,6 @@ For licensing, see LICENSE.md or http://ckeditor.com/license
Using the "Enter" key in CKEditor
Configuring the behavior of Enter and Shift+Enter keys.
-
Output for Flash
-
Configuring CKEditor to produce HTML code that can be used with Adobe Flash.
- -
Output HTML
-
Configuring CKEditor to produce legacy HTML 4 code.
-
Toolbar Configurations
Configuring CKEditor to display full or custom toolbar layout.
@@ -124,7 +121,7 @@ For licensing, see LICENSE.md or http://ckeditor.com/license CKEditor - The text editor for the Internet - http://ckeditor.com

- Copyright © 2003-2014, CKSource - Frederico Knabben. All rights reserved. + Copyright © 2003-2015, CKSource - Frederico Knabben. All rights reserved.

diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/inlineall.html b/rainloop/v/0.0.0/static/ckeditor/samples/inlineall.html index f82af1dbd..fc33619f1 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/inlineall.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/inlineall.html @@ -1,311 +1,311 @@ - - - - - - Massive inline editing — CKEditor Sample - - - - - - -
-

CKEditor Samples » Massive inline editing

-
-

This sample page demonstrates the inline editing feature - CKEditor instances will be created automatically from page elements with contentEditable attribute set to value true:

-
<div contenteditable="true" > ... </div>
-

Click inside of any element below to start editing.

-
-
-
- -
-
-
-

- Fusce vitae porttitor -

-

- - Lorem ipsum dolor sit amet dolor. Duis blandit vestibulum faucibus a, tortor. - -

-

- Proin nunc justo felis mollis tincidunt, risus risus pede, posuere cubilia Curae, Nullam euismod, enim. Etiam nibh ultricies dolor ac dignissim erat volutpat. Vivamus fermentum nisl nulla sem in metus. Maecenas wisi. Donec nec erat volutpat. -

-
-

- Fusce vitae porttitor a, euismod convallis nisl, blandit risus tortor, pretium. - Vehicula vitae, imperdiet vel, ornare enim vel sodales rutrum -

-
-
-

- Libero nunc, rhoncus ante ipsum non ipsum. Nunc eleifend pede turpis id sollicitudin fringilla. Phasellus ultrices, velit ac arcu. -

-
-

Pellentesque nunc. Donec suscipit erat. Pellentesque habitant morbi tristique ullamcorper.

-

Mauris mattis feugiat lectus nec mauris. Nullam vitae ante.

-
-
-
-
-

- Integer condimentum sit amet -

-

- Aenean nonummy a, mattis varius. Cras aliquet. - Praesent magna non mattis ac, rhoncus nunc, rhoncus eget, cursus pulvinar mollis.

-

Proin id nibh. Sed eu libero posuere sed, lectus. Phasellus dui gravida gravida feugiat mattis ac, felis.

-

Integer condimentum sit amet, tempor elit odio, a dolor non ante at sapien. Sed ac lectus. Nulla ligula quis eleifend mi, id leo velit pede cursus arcu id nulla ac lectus. Phasellus vestibulum. Nunc viverra enim quis diam.

-
-
-

- Praesent wisi accumsan sit amet nibh -

-

Donec ullamcorper, risus tortor, pretium porttitor. Morbi quam quis lectus non leo.

-

Integer faucibus scelerisque. Proin faucibus at, aliquet vulputate, odio at eros. Fusce gravida, erat vitae augue. Fusce urna fringilla gravida.

-

In hac habitasse platea dictumst. Praesent wisi accumsan sit amet nibh. Maecenas orci luctus a, lacinia quam sem, posuere commodo, odio condimentum tempor, pede semper risus. Suspendisse pede. In hac habitasse platea dictumst. Nam sed laoreet sit amet erat. Integer.

-
-
-
-
-

- CKEditor logo -

-

Quisque justo neque, mattis sed, fermentum ultrices posuere cubilia Curae, Vestibulum elit metus, quis placerat ut, lectus. Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis. Fusce porttitor, nulla quis turpis. Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi. Donec odio nec velit ac nunc sit amet, accumsan cursus aliquet. Vestibulum ante sit amet sagittis mi.

-

- Nullam laoreet vel consectetuer tellus suscipit -

-
    -
  • Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis.
  • -
  • Fusce porttitor, nulla quis turpis. Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi.
  • -
  • Mauris eget tellus. Donec non felis. Nam eget dolor. Vestibulum enim. Donec.
  • -
-

Quisque justo neque, mattis sed, fermentum ultrices posuere cubilia Curae, Vestibulum elit metus, quis placerat ut, lectus.

-

Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi. Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis. Fusce porttitor, nulla quis turpis.

-

Donec odio nec velit ac nunc sit amet, accumsan cursus aliquet. Vestibulum ante sit amet sagittis mi. Sed in nonummy faucibus turpis. Mauris eget tellus. Donec non felis. Nam eget dolor. Vestibulum enim. Donec.

-
-
-
-
- Tags of this article: -

- inline, editing, floating, CKEditor -

-
-
- - - + + + + + + Massive inline editing — CKEditor Sample + + + + + + +
+

CKEditor Samples » Massive inline editing

+
+

This sample page demonstrates the inline editing feature - CKEditor instances will be created automatically from page elements with contentEditable attribute set to value true:

+
<div contenteditable="true" > ... </div>
+

Click inside of any element below to start editing.

+
+
+
+ +
+
+
+

+ Fusce vitae porttitor +

+

+ + Lorem ipsum dolor sit amet dolor. Duis blandit vestibulum faucibus a, tortor. + +

+

+ Proin nunc justo felis mollis tincidunt, risus risus pede, posuere cubilia Curae, Nullam euismod, enim. Etiam nibh ultricies dolor ac dignissim erat volutpat. Vivamus fermentum nisl nulla sem in metus. Maecenas wisi. Donec nec erat volutpat. +

+
+

+ Fusce vitae porttitor a, euismod convallis nisl, blandit risus tortor, pretium. + Vehicula vitae, imperdiet vel, ornare enim vel sodales rutrum +

+
+
+

+ Libero nunc, rhoncus ante ipsum non ipsum. Nunc eleifend pede turpis id sollicitudin fringilla. Phasellus ultrices, velit ac arcu. +

+
+

Pellentesque nunc. Donec suscipit erat. Pellentesque habitant morbi tristique ullamcorper.

+

Mauris mattis feugiat lectus nec mauris. Nullam vitae ante.

+
+
+
+
+

+ Integer condimentum sit amet +

+

+ Aenean nonummy a, mattis varius. Cras aliquet. + Praesent magna non mattis ac, rhoncus nunc, rhoncus eget, cursus pulvinar mollis.

+

Proin id nibh. Sed eu libero posuere sed, lectus. Phasellus dui gravida gravida feugiat mattis ac, felis.

+

Integer condimentum sit amet, tempor elit odio, a dolor non ante at sapien. Sed ac lectus. Nulla ligula quis eleifend mi, id leo velit pede cursus arcu id nulla ac lectus. Phasellus vestibulum. Nunc viverra enim quis diam.

+
+
+

+ Praesent wisi accumsan sit amet nibh +

+

Donec ullamcorper, risus tortor, pretium porttitor. Morbi quam quis lectus non leo.

+

Integer faucibus scelerisque. Proin faucibus at, aliquet vulputate, odio at eros. Fusce gravida, erat vitae augue. Fusce urna fringilla gravida.

+

In hac habitasse platea dictumst. Praesent wisi accumsan sit amet nibh. Maecenas orci luctus a, lacinia quam sem, posuere commodo, odio condimentum tempor, pede semper risus. Suspendisse pede. In hac habitasse platea dictumst. Nam sed laoreet sit amet erat. Integer.

+
+
+
+
+

+ CKEditor logo +

+

Quisque justo neque, mattis sed, fermentum ultrices posuere cubilia Curae, Vestibulum elit metus, quis placerat ut, lectus. Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis. Fusce porttitor, nulla quis turpis. Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi. Donec odio nec velit ac nunc sit amet, accumsan cursus aliquet. Vestibulum ante sit amet sagittis mi.

+

+ Nullam laoreet vel consectetuer tellus suscipit +

+
    +
  • Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis.
  • +
  • Fusce porttitor, nulla quis turpis. Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi.
  • +
  • Mauris eget tellus. Donec non felis. Nam eget dolor. Vestibulum enim. Donec.
  • +
+

Quisque justo neque, mattis sed, fermentum ultrices posuere cubilia Curae, Vestibulum elit metus, quis placerat ut, lectus.

+

Nullam laoreet vel, consectetuer tellus suscipit ultricies, hendrerit wisi. Ut sagittis, nunc libero, egestas consequat lobortis velit rutrum ut, faucibus turpis. Fusce porttitor, nulla quis turpis.

+

Donec odio nec velit ac nunc sit amet, accumsan cursus aliquet. Vestibulum ante sit amet sagittis mi. Sed in nonummy faucibus turpis. Mauris eget tellus. Donec non felis. Nam eget dolor. Vestibulum enim. Donec.

+
+
+
+
+ Tags of this article: +

+ inline, editing, floating, CKEditor +

+
+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/inlinebycode.html b/rainloop/v/0.0.0/static/ckeditor/samples/inlinebycode.html index 4e4753672..c1df0e905 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/inlinebycode.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/inlinebycode.html @@ -1,121 +1,121 @@ - - - - - - Inline Editing by Code — CKEditor Sample - - - - - -

- CKEditor Samples » Inline Editing by Code -

-
-

- This sample shows how to create an inline editor instance of CKEditor. It is created - with a JavaScript call using the following code: -

-
-// This property tells CKEditor to not activate every element with contenteditable=true element.
-CKEDITOR.disableAutoInline = true;
-
-var editor = CKEDITOR.inline( document.getElementById( 'editable' ) );
-
-

- Note that editable in the code above is the id - attribute of the <div> element to be converted into an inline instance. -

-
-
-

Saturn V carrying Apollo 11 Apollo 11

- -

Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.

- -

Armstrong spent about three and a half two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5 kg) of lunar material for return to Earth. A third member of the mission, Michael Collins, piloted the command spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.

- -

Broadcasting and quotes

- -

Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:

- -
-

One small step for [a] man, one giant leap for mankind.

-
- -

Apollo 11 effectively ended the Space Race and fulfilled a national goal proposed in 1961 by the late U.S. President John F. Kennedy in a speech before the United States Congress:

- -
-

[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.

-
- -

Technical details

- - - - - - - - - - - - - - - - - - - - - - - -
Mission crew
PositionAstronaut
CommanderNeil A. Armstrong
Command Module PilotMichael Collins
Lunar Module PilotEdwin "Buzz" E. Aldrin, Jr.
- -

Launched by a Saturn V rocket from Kennedy Space Center in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of NASA's Apollo program. The Apollo spacecraft had three parts:

- -
    -
  1. Command Module with a cabin for the three astronauts which was the only part which landed back on Earth
  2. -
  3. Service Module which supported the Command Module with propulsion, electrical power, oxygen and water
  4. -
  5. Lunar Module for landing on the Moon.
  6. -
- -

After being sent to the Moon by the Saturn V's upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the Sea of Tranquility. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the Pacific Ocean on July 24.

- -
-

Source: Wikipedia.org

-
- - - - - + + + + + + Inline Editing by Code — CKEditor Sample + + + + + +

+ CKEditor Samples » Inline Editing by Code +

+
+

+ This sample shows how to create an inline editor instance of CKEditor. It is created + with a JavaScript call using the following code: +

+
+// This property tells CKEditor to not activate every element with contenteditable=true element.
+CKEDITOR.disableAutoInline = true;
+
+var editor = CKEDITOR.inline( document.getElementById( 'editable' ) );
+
+

+ Note that editable in the code above is the id + attribute of the <div> element to be converted into an inline instance. +

+
+
+

Saturn V carrying Apollo 11 Apollo 11

+ +

Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.

+ +

Armstrong spent about three and a half two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5 kg) of lunar material for return to Earth. A third member of the mission, Michael Collins, piloted the command spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth.

+ +

Broadcasting and quotes

+ +

Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:

+ +
+

One small step for [a] man, one giant leap for mankind.

+
+ +

Apollo 11 effectively ended the Space Race and fulfilled a national goal proposed in 1961 by the late U.S. President John F. Kennedy in a speech before the United States Congress:

+ +
+

[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.

+
+ +

Technical details

+ + + + + + + + + + + + + + + + + + + + + + + +
Mission crew
PositionAstronaut
CommanderNeil A. Armstrong
Command Module PilotMichael Collins
Lunar Module PilotEdwin "Buzz" E. Aldrin, Jr.
+ +

Launched by a Saturn V rocket from Kennedy Space Center in Merritt Island, Florida on July 16, Apollo 11 was the fifth manned mission of NASA's Apollo program. The Apollo spacecraft had three parts:

+ +
    +
  1. Command Module with a cabin for the three astronauts which was the only part which landed back on Earth
  2. +
  3. Service Module which supported the Command Module with propulsion, electrical power, oxygen and water
  4. +
  5. Lunar Module for landing on the Moon.
  6. +
+ +

After being sent to the Moon by the Saturn V's upper stage, the astronauts separated the spacecraft from it and travelled for three days until they entered into lunar orbit. Armstrong and Aldrin then moved into the Lunar Module and landed in the Sea of Tranquility. They stayed a total of about 21 and a half hours on the lunar surface. After lifting off in the upper part of the Lunar Module and rejoining Collins in the Command Module, they returned to Earth and landed in the Pacific Ocean on July 24.

+ +
+

Source: Wikipedia.org

+
+ + + + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/inlinetextarea.html b/rainloop/v/0.0.0/static/ckeditor/samples/inlinetextarea.html index 97ced1edc..9e3d077ac 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/inlinetextarea.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/inlinetextarea.html @@ -1,110 +1,110 @@ - - - - - - Replace Textarea with Inline Editor — CKEditor Sample - - - - - -

- CKEditor Samples » Replace Textarea with Inline Editor -

-
-

- You can also create an inline editor from a textarea - element. In this case the textarea will be replaced - by a div element with inline editing enabled. -

-
-// "article-body" is the name of a textarea element.
-var editor = CKEDITOR.inline( 'article-body' );
-
-
-
-

This is a sample form with some fields

-

- Title:
-

-

- Article Body (Textarea converted to CKEditor):
- -

-

- -

-
- - - - - + + + + + + Replace Textarea with Inline Editor — CKEditor Sample + + + + + +

+ CKEditor Samples » Replace Textarea with Inline Editor +

+
+

+ You can also create an inline editor from a textarea + element. In this case the textarea will be replaced + by a div element with inline editing enabled. +

+
+// "article-body" is the name of a textarea element.
+var editor = CKEDITOR.inline( 'article-body' );
+
+
+
+

This is a sample form with some fields

+

+ Title:
+

+

+ Article Body (Textarea converted to CKEditor):
+ +

+

+ +

+
+ + + + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/jquery.html b/rainloop/v/0.0.0/static/ckeditor/samples/jquery.html index 380b8284b..74e128a53 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/jquery.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/jquery.html @@ -1,100 +1,100 @@ - - - - - - jQuery Adapter — CKEditor Sample - - - - - - - - -

- CKEditor Samples » Create Editors with jQuery -

-
-
-

- This sample shows how to use the jQuery adapter. - Note that you have to include both CKEditor and jQuery scripts before including the adapter. -

- -
-<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
-<script src="/ckeditor/ckeditor.js"></script>
-<script src="/ckeditor/adapters/jquery.js"></script>
-
- -

Then you can replace HTML elements with a CKEditor instance using the ckeditor() method.

- -
-$( document ).ready( function() {
-	$( 'textarea#editor1' ).ckeditor();
-} );
-
-
- -

Inline Example

- -
-

Saturn V carrying Apollo 11Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.

-

Armstrong spent about three and a half two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5 kg) of lunar material for return to Earth. A third member of the mission, Michael Collins, piloted the command spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth. -

Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:

-

One small step for [a] man, one giant leap for mankind.

Apollo 11 effectively ended the Space Race and fulfilled a national goal proposed in 1961 by the late U.S. President John F. Kennedy in a speech before the United States Congress:

[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.

-
- -
- -

Classic (iframe-based) Example

- - - -

- - - - - -

-
- - - + + + + + + jQuery Adapter — CKEditor Sample + + + + + + + + +

+ CKEditor Samples » Create Editors with jQuery +

+
+
+

+ This sample shows how to use the jQuery adapter. + Note that you have to include both CKEditor and jQuery scripts before including the adapter. +

+ +
+<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
+<script src="/ckeditor/ckeditor.js"></script>
+<script src="/ckeditor/adapters/jquery.js"></script>
+
+ +

Then you can replace HTML elements with a CKEditor instance using the ckeditor() method.

+ +
+$( document ).ready( function() {
+	$( 'textarea#editor1' ).ckeditor();
+} );
+
+
+ +

Inline Example

+ +
+

Saturn V carrying Apollo 11Apollo 11 was the spaceflight that landed the first humans, Americans Neil Armstrong and Buzz Aldrin, on the Moon on July 20, 1969, at 20:18 UTC. Armstrong became the first to step onto the lunar surface 6 hours later on July 21 at 02:56 UTC.

+

Armstrong spent about three and a half two and a half hours outside the spacecraft, Aldrin slightly less; and together they collected 47.5 pounds (21.5 kg) of lunar material for return to Earth. A third member of the mission, Michael Collins, piloted the command spacecraft alone in lunar orbit until Armstrong and Aldrin returned to it for the trip back to Earth. +

Broadcast on live TV to a world-wide audience, Armstrong stepped onto the lunar surface and described the event as:

+

One small step for [a] man, one giant leap for mankind.

Apollo 11 effectively ended the Space Race and fulfilled a national goal proposed in 1961 by the late U.S. President John F. Kennedy in a speech before the United States Congress:

[...] before this decade is out, of landing a man on the Moon and returning him safely to the Earth.

+
+ +
+ +

Classic (iframe-based) Example

+ + + +

+ + + + + +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/dialog/assets/my_dialog.js b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/dialog/assets/my_dialog.js index 3edd07286..420cc0b0d 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/dialog/assets/my_dialog.js +++ b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/dialog/assets/my_dialog.js @@ -1,48 +1,48 @@ -/** - * Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or http://ckeditor.com/license - */ - -CKEDITOR.dialog.add( 'myDialog', function( editor ) { - return { - title: 'My Dialog', - minWidth: 400, - minHeight: 200, - contents: [ - { - id: 'tab1', - label: 'First Tab', - title: 'First Tab', - elements: [ - { - id: 'input1', - type: 'text', - label: 'Text Field' - }, - { - id: 'select1', - type: 'select', - label: 'Select Field', - items: [ - [ 'option1', 'value1' ], - [ 'option2', 'value2' ] - ] - } - ] - }, - { - id: 'tab2', - label: 'Second Tab', - title: 'Second Tab', - elements: [ - { - id: 'button1', - type: 'button', - label: 'Button Field' - } - ] - } - ] - }; -} ); - +/** + * Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +CKEDITOR.dialog.add( 'myDialog', function() { + return { + title: 'My Dialog', + minWidth: 400, + minHeight: 200, + contents: [ + { + id: 'tab1', + label: 'First Tab', + title: 'First Tab', + elements: [ + { + id: 'input1', + type: 'text', + label: 'Text Field' + }, + { + id: 'select1', + type: 'select', + label: 'Select Field', + items: [ + [ 'option1', 'value1' ], + [ 'option2', 'value2' ] + ] + } + ] + }, + { + id: 'tab2', + label: 'Second Tab', + title: 'Second Tab', + elements: [ + { + id: 'button1', + type: 'button', + label: 'Button Field' + } + ] + } + ] + }; +} ); + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/dialog/dialog.html b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/dialog/dialog.html index df09d25b3..a85c56645 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/dialog/dialog.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/dialog/dialog.html @@ -1,187 +1,187 @@ - - - - - - Using API to Customize Dialog Windows — CKEditor Sample - - - - - - - - - -

- CKEditor Samples » Using CKEditor Dialog API -

-
-

- This sample shows how to use the - CKEditor Dialog API - to customize CKEditor dialog windows without changing the original editor code. - The following customizations are being done in the example below: -

-

- For details on how to create this setup check the source code of this sample page. -

-
-

A custom dialog is added to the editors using the pluginsLoaded event, from an external dialog definition file:

-
    -
  1. Creating a custom dialog window – "My Dialog" dialog window opened with the "My Dialog" toolbar button.
  2. -
  3. Creating a custom button – Add button to open the dialog with "My Dialog" toolbar button.
  4. -
- - -

The below editor modify the dialog definition of the above added dialog using the dialogDefinition event:

-
    -
  1. Adding dialog tab – Add new tab "My Tab" to dialog window.
  2. -
  3. Removing a dialog window tab – Remove "Second Tab" page from the dialog window.
  4. -
  5. Adding dialog window fields – Add "My Custom Field" to the dialog window.
  6. -
  7. Removing dialog window field – Remove "Select Field" selection field from the dialog window.
  8. -
  9. Setting default values for dialog window fields – Set default value of "Text Field" text field.
  10. -
  11. Setup initial focus for dialog window – Put initial focus on "My Custom Field" text field.
  12. -
- - - - - + + + + + + Using API to Customize Dialog Windows — CKEditor Sample + + + + + + + + + +

+ CKEditor Samples » Using CKEditor Dialog API +

+
+

+ This sample shows how to use the + CKEditor Dialog API + to customize CKEditor dialog windows without changing the original editor code. + The following customizations are being done in the example below: +

+

+ For details on how to create this setup check the source code of this sample page. +

+
+

A custom dialog is added to the editors using the pluginsLoaded event, from an external dialog definition file:

+
    +
  1. Creating a custom dialog window – "My Dialog" dialog window opened with the "My Dialog" toolbar button.
  2. +
  3. Creating a custom button – Add button to open the dialog with "My Dialog" toolbar button.
  4. +
+ + +

The below editor modify the dialog definition of the above added dialog using the dialogDefinition event:

+
    +
  1. Adding dialog tab – Add new tab "My Tab" to dialog window.
  2. +
  3. Removing a dialog window tab – Remove "Second Tab" page from the dialog window.
  4. +
  5. Adding dialog window fields – Add "My Custom Field" to the dialog window.
  6. +
  7. Removing dialog window field – Remove "Select Field" selection field from the dialog window.
  8. +
  9. Setting default values for dialog window fields – Set default value of "Text Field" text field.
  10. +
  11. Setup initial focus for dialog window – Put initial focus on "My Custom Field" text field.
  12. +
+ + + + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/divarea/divarea.html b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/divarea/divarea.html index d2880bd2c..68b191c39 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/divarea/divarea.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/divarea/divarea.html @@ -1,61 +1,61 @@ - - - - - - Replace Textarea with a "DIV-based" editor — CKEditor Sample - - - - - - - -

- CKEditor Samples » Replace Textarea with a "DIV-based" editor -

-
-
-

- This editor is using a <div> element-based editing area, provided by the Divarea plugin. -

-
-CKEDITOR.replace( 'textarea_id', {
-	extraPlugins: 'divarea'
-});
-
- - -

- -

-
- - - + + + + + + Replace Textarea with a "DIV-based" editor — CKEditor Sample + + + + + + + +

+ CKEditor Samples » Replace Textarea with a "DIV-based" editor +

+
+
+

+ This editor is using a <div> element-based editing area, provided by the Divarea plugin. +

+
+CKEDITOR.replace( 'textarea_id', {
+	extraPlugins: 'divarea'
+});
+
+ + +

+ +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/enterkey/enterkey.html b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/enterkey/enterkey.html index 2d5150122..61fbc7245 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/enterkey/enterkey.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/enterkey/enterkey.html @@ -1,103 +1,103 @@ - - - - - - ENTER Key Configuration — CKEditor Sample - - - - - - - - -

- CKEditor Samples » ENTER Key Configuration -

-
-

- This sample shows how to configure the Enter and Shift+Enter keys - to perform actions specified in the - enterMode - and shiftEnterMode - parameters, respectively. - You can choose from the following options: -

-
    -
  • ENTER_P – new <p> paragraphs are created;
  • -
  • ENTER_BR – lines are broken with <br> elements;
  • -
  • ENTER_DIV – new <div> blocks are created.
  • -
-

- The sample code below shows how to configure CKEditor to create a <div> block when Enter key is pressed. -

-
-CKEDITOR.replace( 'textarea_id', {
-	enterMode: CKEDITOR.ENTER_DIV
-});
-

- Note that textarea_id in the code above is the id attribute of - the <textarea> element to be replaced. -

-
-
- When Enter is pressed:
- -
-
- When Shift+Enter is pressed:
- -
-
-
-

-
- -

-

- -

-
- - - + + + + + + ENTER Key Configuration — CKEditor Sample + + + + + + + + +

+ CKEditor Samples » ENTER Key Configuration +

+
+

+ This sample shows how to configure the Enter and Shift+Enter keys + to perform actions specified in the + enterMode + and shiftEnterMode + parameters, respectively. + You can choose from the following options: +

+
    +
  • ENTER_P – new <p> paragraphs are created;
  • +
  • ENTER_BR – lines are broken with <br> elements;
  • +
  • ENTER_DIV – new <div> blocks are created.
  • +
+

+ The sample code below shows how to configure CKEditor to create a <div> block when Enter key is pressed. +

+
+CKEDITOR.replace( 'textarea_id', {
+	enterMode: CKEDITOR.ENTER_DIV
+});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+
+
+ When Enter is pressed:
+ +
+
+ When Shift+Enter is pressed:
+ +
+
+
+

+
+ +

+

+ +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.fla b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.fla deleted file mode 100644 index 27e68ccd1..000000000 Binary files a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.fla and /dev/null differ diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.swf b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.swf deleted file mode 100644 index dbe17b6bc..000000000 Binary files a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/outputforflash.swf and /dev/null differ diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/swfobject.js b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/swfobject.js deleted file mode 100644 index 95fdf0a7a..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/assets/outputforflash/swfobject.js +++ /dev/null @@ -1,18 +0,0 @@ -var swfobject=function(){function u(){if(!s){try{var a=d.getElementsByTagName("body")[0].appendChild(d.createElement("span"));a.parentNode.removeChild(a)}catch(b){return}s=!0;for(var a=x.length,c=0;cf){f++;setTimeout(arguments.callee,10);return}a.removeChild(b);c=null;D()})()}else D()}function D(){var a=p.length;if(0e.wk))t(c,!0),f&&(g.success=!0,g.ref=E(c),f(g));else if(p[b].expressInstall&&F()){g={};g.data=p[b].expressInstall;g.width=d.getAttribute("width")||"0";g.height=d.getAttribute("height")||"0";d.getAttribute("class")&&(g.styleclass=d.getAttribute("class"));d.getAttribute("align")&&(g.align=d.getAttribute("align"));for(var h={},d=d.getElementsByTagName("param"),j=d.length,k=0;ke.wk)}function G(a,b,c,f){A=!0;H=f||null;N={success:!1,id:c};var g=n(c);if(g){"OBJECT"==g.nodeName?(w=I(g),B=null):(w=g,B=c);a.id= -O;if(typeof a.width==i||!/%$/.test(a.width)&&310>parseInt(a.width,10))a.width="310";if(typeof a.height==i||!/%$/.test(a.height)&&137>parseInt(a.height,10))a.height="137";d.title=d.title.slice(0,47)+" - Flash Player Installation";f=e.ie&&e.win?"ActiveX":"PlugIn";f="MMredirectURL="+m.location.toString().replace(/&/g,"%26")+"&MMplayerType="+f+"&MMdoctitle="+d.title;b.flashvars=typeof b.flashvars!=i?b.flashvars+("&"+f):f;e.ie&&(e.win&&4!=g.readyState)&&(f=d.createElement("div"),c+="SWFObjectNew",f.setAttribute("id", -c),g.parentNode.insertBefore(f,g),g.style.display="none",function(){g.readyState==4?g.parentNode.removeChild(g):setTimeout(arguments.callee,10)}());J(a,b,c)}}function W(a){if(e.ie&&e.win&&4!=a.readyState){var b=d.createElement("div");a.parentNode.insertBefore(b,a);b.parentNode.replaceChild(I(a),b);a.style.display="none";(function(){4==a.readyState?a.parentNode.removeChild(a):setTimeout(arguments.callee,10)})()}else a.parentNode.replaceChild(I(a),a)}function I(a){var b=d.createElement("div");if(e.win&& -e.ie)b.innerHTML=a.innerHTML;else if(a=a.getElementsByTagName(r)[0])if(a=a.childNodes)for(var c=a.length,f=0;fe.wk)return f;if(g)if(typeof a.id==i&&(a.id=c),e.ie&&e.win){var o="",h;for(h in a)a[h]!=Object.prototype[h]&&("data"==h.toLowerCase()?b.movie=a[h]:"styleclass"==h.toLowerCase()?o+=' class="'+a[h]+'"':"classid"!=h.toLowerCase()&&(o+=" "+ -h+'="'+a[h]+'"'));h="";for(var j in b)b[j]!=Object.prototype[j]&&(h+='');g.outerHTML='"+h+"";C[C.length]=a.id;f=n(a.id)}else{j=d.createElement(r);j.setAttribute("type",y);for(var k in a)a[k]!=Object.prototype[k]&&("styleclass"==k.toLowerCase()?j.setAttribute("class",a[k]):"classid"!=k.toLowerCase()&&j.setAttribute(k,a[k]));for(o in b)b[o]!=Object.prototype[o]&&"movie"!=o.toLowerCase()&& -(a=j,h=o,k=b[o],c=d.createElement("param"),c.setAttribute("name",h),c.setAttribute("value",k),a.appendChild(c));g.parentNode.replaceChild(j,g);f=j}return f}function P(a){var b=n(a);b&&"OBJECT"==b.nodeName&&(e.ie&&e.win?(b.style.display="none",function(){if(4==b.readyState){var c=n(a);if(c){for(var f in c)"function"==typeof c[f]&&(c[f]=null);c.parentNode.removeChild(c)}}else setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function n(a){var b=null;try{b=d.getElementById(a)}catch(c){}return b} -function U(a,b,c){a.attachEvent(b,c);v[v.length]=[a,b,c]}function z(a){var b=e.pv,a=a.split(".");a[0]=parseInt(a[0],10);a[1]=parseInt(a[1],10)||0;a[2]=parseInt(a[2],10)||0;return b[0]>a[0]||b[0]==a[0]&&b[1]>a[1]||b[0]==a[0]&&b[1]==a[1]&&b[2]>=a[2]?!0:!1}function Q(a,b,c,f){if(!e.ie||!e.mac){var g=d.getElementsByTagName("head")[0];if(g){c=c&&"string"==typeof c?c:"screen";f&&(K=l=null);if(!l||K!=c)f=d.createElement("style"),f.setAttribute("type","text/css"),f.setAttribute("media",c),l=g.appendChild(f), -e.ie&&(e.win&&typeof d.styleSheets!=i&&0\.;]/.exec(a)&&typeof encodeURIComponent!=i?encodeURIComponent(a):a}var i="undefined",r="object",y="application/x-shockwave-flash", -O="SWFObjectExprInst",m=window,d=document,q=navigator,T=!1,x=[function(){T?V():D()}],p=[],C=[],v=[],w,B,H,N,s=!1,A=!1,l,K,R=!0,e=function(){var a=typeof d.getElementById!=i&&typeof d.getElementsByTagName!=i&&typeof d.createElement!=i,b=q.userAgent.toLowerCase(),c=q.platform.toLowerCase(),f=c?/win/.test(c):/win/.test(b),c=c?/mac/.test(c):/mac/.test(b),b=/webkit/.test(b)?parseFloat(b.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,g=!+"\v1",e=[0,0,0],h=null;if(typeof q.plugins!=i&&typeof q.plugins["Shockwave Flash"]== -r){if((h=q.plugins["Shockwave Flash"].description)&&!(typeof q.mimeTypes!=i&&q.mimeTypes[y]&&!q.mimeTypes[y].enabledPlugin))T=!0,g=!1,h=h.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),e[0]=parseInt(h.replace(/^(.*)\..*$/,"$1"),10),e[1]=parseInt(h.replace(/^.*\.(.*)\s.*$/,"$1"),10),e[2]=/[a-zA-Z]/.test(h)?parseInt(h.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}else if(typeof m.ActiveXObject!=i)try{var j=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");if(j&&(h=j.GetVariable("$version")))g=!0,h=h.split(" ")[1].split(","), -e=[parseInt(h[0],10),parseInt(h[1],10),parseInt(h[2],10)]}catch(k){}return{w3:a,pv:e,wk:b,ie:g,win:f,mac:c}}();(function(){e.w3&&((typeof d.readyState!=i&&"complete"==d.readyState||typeof d.readyState==i&&(d.getElementsByTagName("body")[0]||d.body))&&u(),s||(typeof d.addEventListener!=i&&d.addEventListener("DOMContentLoaded",u,!1),e.ie&&e.win&&(d.attachEvent("onreadystatechange",function(){"complete"==d.readyState&&(d.detachEvent("onreadystatechange",arguments.callee),u())}),m==top&&function(){if(!s){try{d.documentElement.doScroll("left")}catch(a){setTimeout(arguments.callee, -0);return}u()}}()),e.wk&&function(){s||(/loaded|complete/.test(d.readyState)?u():setTimeout(arguments.callee,0))}(),M(u)))})();(function(){e.ie&&e.win&&window.attachEvent("onunload",function(){for(var a=v.length,b=0;be.wk)&&a&&b&&c&&d&&g?(t(b,!1),L(function(){c+="";d+="";var e={};if(k&&typeof k===r)for(var l in k)e[l]=k[l];e.data=a;e.width=c;e.height=d;l={};if(j&&typeof j===r)for(var p in j)l[p]=j[p];if(h&&typeof h===r)for(var q in h)l.flashvars=typeof l.flashvars!=i?l.flashvars+("&"+q+"="+h[q]):q+"="+h[q];if(z(g))p=J(e,l,b),e.id== -b&&t(b,!0),n.success=!0,n.ref=p;else{if(o&&F()){e.data=o;G(e,l,b,m);return}t(b,!0)}m&&m(n)})):m&&m(n)},switchOffAutoHideShow:function(){R=!1},ua:e,getFlashPlayerVersion:function(){return{major:e.pv[0],minor:e.pv[1],release:e.pv[2]}},hasFlashPlayerVersion:z,createSWF:function(a,b,c){if(e.w3)return J(a,b,c)},showExpressInstall:function(a,b,c,d){e.w3&&F()&&G(a,b,c,d)},removeSWF:function(a){e.w3&&P(a)},createCSS:function(a,b,c,d){e.w3&&Q(a,b,c,d)},addDomLoadEvent:L,addLoadEvent:M,getQueryParamValue:function(a){var b= -d.location.search||d.location.hash;if(b){/\?/.test(b)&&(b=b.split("?")[1]);if(null==a)return S(b);for(var b=b.split("&"),c=0;c - - - - - Output for Flash — CKEditor Sample - - - - - - - - - - - -

- CKEditor Samples » Producing Flash Compliant HTML Output -

-
-

- This sample shows how to configure CKEditor to output - HTML code that can be used with - - Adobe Flash. - The code will contain a subset of standard HTML elements like <b>, - <i>, and <p> as well as HTML attributes. -

-

- To add a CKEditor instance outputting Flash compliant HTML code, load the editor using a standard - JavaScript call, and define CKEditor features to use HTML elements and attributes. -

-

- For details on how to create this setup check the source code of this sample page. -

-
-

- To see how it works, create some content in the editing area of CKEditor on the left - and send it to the Flash object on the right side of the page by using the - Send to Flash button. -

- - - - - -
- - -

- -

-
-
-
- - - diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/outputhtml.html b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/outputhtml.html deleted file mode 100644 index f25697df4..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/htmlwriter/outputhtml.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - HTML Compliant Output — CKEditor Sample - - - - - - - - - -

- CKEditor Samples » Producing HTML Compliant Output -

-
-

- This sample shows how to configure CKEditor to output valid - HTML 4.01 code. - Traditional HTML elements like <b>, - <i>, and <font> are used in place of - <strong>, <em>, and CSS styles. -

-

- To add a CKEditor instance outputting legacy HTML 4.01 code, load the editor using a standard - JavaScript call, and define CKEditor features to use the HTML compliant elements and attributes. -

-

- A snippet of the configuration code can be seen below; check the source of this page for - full definition: -

-
-CKEDITOR.replace( 'textarea_id', {
-	coreStyles_bold: { element: 'b' },
-	coreStyles_italic: { element: 'i' },
-
-	fontSize_style: {
-		element: 'font',
-		attributes: { 'size': '#(size)' }
-	}
-
-	...
-});
-
-
-

- - - -

-

- -

-
- - - diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/assets/image1.jpg b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/assets/image1.jpg index 2fe79eb2c..ca491e39d 100644 Binary files a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/assets/image1.jpg and b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/assets/image1.jpg differ diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/assets/image2.jpg b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/assets/image2.jpg index 453ece5d3..3dd6d61fc 100644 Binary files a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/assets/image2.jpg and b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/assets/image2.jpg differ diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/image2.html b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/image2.html index 34a5339a2..75452bcac 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/image2.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/image2/image2.html @@ -1,65 +1,65 @@ - - - - - - New Image plugin — CKEditor Sample - - - - - - - - - -

- CKEditor Samples » New Image plugin -

- -
-

- This editor is using the new Image (image2) plugin, which implements a dynamic click-and-drag resizing - and easy captioning of the images. -

-

- To use the new plugin, extend config.extraPlugins: -

-
-CKEDITOR.replace( 'textarea_id', {
-	extraPlugins: 'image2'
-} );
-
-
- - - - - - - - + + + + + + New Image plugin — CKEditor Sample + + + + + + + + + +

+ CKEditor Samples » New Image plugin +

+ +
+

+ This editor is using the new Image (image2) plugin, which implements a dynamic click-and-drag resizing + and easy captioning of the images. +

+

+ To use the new plugin, extend config.extraPlugins: +

+
+CKEDITOR.replace( 'textarea_id', {
+	extraPlugins: 'image2'
+} );
+
+
+ + + + + + + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/quicktable/quicktable.html b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/quicktable/quicktable.html new file mode 100644 index 000000000..516d274ea --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/quicktable/quicktable.html @@ -0,0 +1,93 @@ + + + + + + Quicktable — CKEditor Sample + + + + + + +

+ CKEditor Samples » Quicktable example +

+

Default Configuration:

+ + +

Configuration Example:

+
+	<script type="text/javascript">
+		CKEDITOR.replace( 'editor2', {
+			extraPlugins: 'quicktable',
+			qtRows: 20, // Count of rows in the quicktable (default: 8)
+			qtColumns: 20, // Count of columns in the quicktable (default: 10)
+			qtBorder: '0', // Border of the inserted table (default: '1')
+			qtWidth: '90%', // Width of the inserted table (default: '500px')
+			qtStyle: { 'border-collapse' : 'collapse' }, // Content of the style-attribute of the inserted table (default: null)
+			qtClass: 'test', // Class of the inserted table (default: '')
+			qtCellPadding: '0', // Cell padding of the inserted table (default: '1')
+			qtCellSpacing: '0', // Cell spacing of the inserted table (default: '1')
+			qtPreviewBorder: '4px double black', // Border of the preview table (default: '1px solid #aaa')
+			qtPreviewSize: '4px', // Cell size of the preview table (default: '14px')
+			qtPreviewBackground: '#c8def4' // Cell background of the preview table on hover (default: '#e5e5e5')
+		});
+	</script>
+	
+ + + + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/sharedspace/sharedspace.html b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/sharedspace/sharedspace.html index 30ac7fcfa..df7ff4d1d 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/sharedspace/sharedspace.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/sharedspace/sharedspace.html @@ -1,119 +1,119 @@ - - - - - - Shared-Space Plugin — CKEditor Sample - - - - - - - -

- CKEditor Samples » Sharing Toolbar and Bottom-bar Spaces -

-
-

- This sample shows several editor instances that share the very same spaces for both the toolbar and the bottom bar. -

-
-
- -
- -
- -
-
- -
- -
-

- Integer condimentum sit amet -

-

- Aenean nonummy a, mattis varius. Cras aliquet. - Praesent magna non mattis ac, rhoncus nunc, rhoncus eget, cursus pulvinar mollis.

-

Proin id nibh. Sed eu libero posuere sed, lectus. Phasellus dui gravida gravida feugiat mattis ac, felis.

-

Integer condimentum sit amet, tempor elit odio, a dolor non ante at sapien. Sed ac lectus. Nulla ligula quis eleifend mi, id leo velit pede cursus arcu id nulla ac lectus. Phasellus vestibulum. Nunc viverra enim quis diam.

-
-
-

- Praesent wisi accumsan sit amet nibh -

-

Donec ullamcorper, risus tortor, pretium porttitor. Morbi quam quis lectus non leo.

-

Integer faucibus scelerisque. Proin faucibus at, aliquet vulputate, odio at eros. Fusce gravida, erat vitae augue. Fusce urna fringilla gravida.

-

In hac habitasse platea dictumst. Praesent wisi accumsan sit amet nibh. Maecenas orci luctus a, lacinia quam sem, posuere commodo, odio condimentum tempor, pede semper risus. Suspendisse pede. In hac habitasse platea dictumst. Nam sed laoreet sit amet erat. Integer.

-
- -
- -
- -
- - - - - - + + + + + + Shared-Space Plugin — CKEditor Sample + + + + + + + +

+ CKEditor Samples » Sharing Toolbar and Bottom-bar Spaces +

+
+

+ This sample shows several editor instances that share the very same spaces for both the toolbar and the bottom bar. +

+
+
+ +
+ +
+ +
+
+ +
+ +
+

+ Integer condimentum sit amet +

+

+ Aenean nonummy a, mattis varius. Cras aliquet. + Praesent magna non mattis ac, rhoncus nunc, rhoncus eget, cursus pulvinar mollis.

+

Proin id nibh. Sed eu libero posuere sed, lectus. Phasellus dui gravida gravida feugiat mattis ac, felis.

+

Integer condimentum sit amet, tempor elit odio, a dolor non ante at sapien. Sed ac lectus. Nulla ligula quis eleifend mi, id leo velit pede cursus arcu id nulla ac lectus. Phasellus vestibulum. Nunc viverra enim quis diam.

+
+
+

+ Praesent wisi accumsan sit amet nibh +

+

Donec ullamcorper, risus tortor, pretium porttitor. Morbi quam quis lectus non leo.

+

Integer faucibus scelerisque. Proin faucibus at, aliquet vulputate, odio at eros. Fusce gravida, erat vitae augue. Fusce urna fringilla gravida.

+

In hac habitasse platea dictumst. Praesent wisi accumsan sit amet nibh. Maecenas orci luctus a, lacinia quam sem, posuere commodo, odio condimentum tempor, pede semper risus. Suspendisse pede. In hac habitasse platea dictumst. Nam sed laoreet sit amet erat. Integer.

+
+ +
+ +
+ +
+ + + + + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/tableresize/tableresize.html b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/tableresize/tableresize.html new file mode 100644 index 000000000..533b35cdc --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/tableresize/tableresize.html @@ -0,0 +1,104 @@ + + + + + + Using TableResize Plugin — CKEditor Sample + + + + + + + +

+ CKEditor Samples » Using the TableResize Plugin +

+
+

+ This sample shows how to configure CKEditor instances to use the + TableResize (tableresize) plugin that allows + the user to edit table columns by using the mouse. +

+

+ The TableResize plugin makes it possible to modify table column width. Hover + your mouse over the column border to see the cursor change to indicate that + the column can be resized. Click and drag your mouse to set the desired width. +

+

+ By default the plugin is turned off. To add a CKEditor instance using the + TableResize plugin, insert the following JavaScript call into your code: +

+
+CKEDITOR.replace( 'textarea_id', {
+	extraPlugins: 'tableresize'
+});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced with CKEditor. +

+
+
+

+ + + +

+

+ +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/toolbar/toolbar.html b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/toolbar/toolbar.html index 6cf2ddf13..79d230b26 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/plugins/toolbar/toolbar.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/plugins/toolbar/toolbar.html @@ -1,232 +1,232 @@ - - - - - - Toolbar Configuration — CKEditor Sample - - - - - - - -

- CKEditor Samples » Toolbar Configuration -

-
-

- This sample page demonstrates editor with loaded full toolbar (all registered buttons) and, if - current editor's configuration modifies default settings, also editor with modified toolbar. -

- -

Since CKEditor 4 there are two ways to configure toolbar buttons.

- -

By config.toolbar

- -

- You can explicitly define which buttons are displayed in which groups and in which order. - This is the more precise setting, but less flexible. If newly added plugin adds its - own button you'll have to add it manually to your config.toolbar setting as well. -

- -

To add a CKEditor instance with custom toolbar setting, insert the following JavaScript call to your code:

- -
-CKEDITOR.replace( 'textarea_id', {
-	toolbar: [
-		{ name: 'document', items: [ 'Source', '-', 'NewPage', 'Preview', '-', 'Templates' ] },	// Defines toolbar group with name (used to create voice label) and items in 3 subgroups.
-		[ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ],			// Defines toolbar group without name.
-		'/',																					// Line break - next group will be placed in new line.
-		{ name: 'basicstyles', items: [ 'Bold', 'Italic' ] }
-	]
-});
- -

By config.toolbarGroups

- -

- You can define which groups of buttons (like e.g. basicstyles, clipboard - and forms) are displayed and in which order. Registered buttons are associated - with toolbar groups by toolbar property in their definition. - This setting's advantage is that you don't have to modify toolbar configuration - when adding/removing plugins which register their own buttons. -

- -

To add a CKEditor instance with custom toolbar groups setting, insert the following JavaScript call to your code:

- -
-CKEDITOR.replace( 'textarea_id', {
-	toolbarGroups: [
-		{ name: 'document',	   groups: [ 'mode', 'document' ] },			// Displays document group with its two subgroups.
- 		{ name: 'clipboard',   groups: [ 'clipboard', 'undo' ] },			// Group's name will be used to create voice label.
- 		'/',																// Line break - next group will be placed in new line.
- 		{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
- 		{ name: 'links' }
-	]
-
-	// NOTE: Remember to leave 'toolbar' property with the default value (null).
-});
-
- - - -
-

Full toolbar configuration

-

Below you can see editor with full toolbar, generated automatically by the editor.

-

- Note: To create editor instance with full toolbar you don't have to set anything. - Just leave toolbar and toolbarGroups with the default, null values. -

- -

-	
- - - - - - + + + + + + Toolbar Configuration — CKEditor Sample + + + + + + + +

+ CKEditor Samples » Toolbar Configuration +

+
+

+ This sample page demonstrates editor with loaded full toolbar (all registered buttons) and, if + current editor's configuration modifies default settings, also editor with modified toolbar. +

+ +

Since CKEditor 4 there are two ways to configure toolbar buttons.

+ +

By config.toolbar

+ +

+ You can explicitly define which buttons are displayed in which groups and in which order. + This is the more precise setting, but less flexible. If newly added plugin adds its + own button you'll have to add it manually to your config.toolbar setting as well. +

+ +

To add a CKEditor instance with custom toolbar setting, insert the following JavaScript call to your code:

+ +
+CKEDITOR.replace( 'textarea_id', {
+	toolbar: [
+		{ name: 'document', items: [ 'Source', '-', 'NewPage', 'Preview', '-', 'Templates' ] },	// Defines toolbar group with name (used to create voice label) and items in 3 subgroups.
+		[ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ],			// Defines toolbar group without name.
+		'/',																					// Line break - next group will be placed in new line.
+		{ name: 'basicstyles', items: [ 'Bold', 'Italic' ] }
+	]
+});
+ +

By config.toolbarGroups

+ +

+ You can define which groups of buttons (like e.g. basicstyles, clipboard + and forms) are displayed and in which order. Registered buttons are associated + with toolbar groups by toolbar property in their definition. + This setting's advantage is that you don't have to modify toolbar configuration + when adding/removing plugins which register their own buttons. +

+ +

To add a CKEditor instance with custom toolbar groups setting, insert the following JavaScript call to your code:

+ +
+CKEDITOR.replace( 'textarea_id', {
+	toolbarGroups: [
+		{ name: 'document',	   groups: [ 'mode', 'document' ] },			// Displays document group with its two subgroups.
+ 		{ name: 'clipboard',   groups: [ 'clipboard', 'undo' ] },			// Group's name will be used to create voice label.
+ 		'/',																// Line break - next group will be placed in new line.
+ 		{ name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] },
+ 		{ name: 'links' }
+	]
+
+	// NOTE: Remember to leave 'toolbar' property with the default value (null).
+});
+
+ + + +
+

Full toolbar configuration

+

Below you can see editor with full toolbar, generated automatically by the editor.

+

+ Note: To create editor instance with full toolbar you don't have to set anything. + Just leave toolbar and toolbarGroups with the default, null values. +

+ +

+	
+ + + + + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/readonly.html b/rainloop/v/0.0.0/static/ckeditor/samples/readonly.html index 58f97069c..bbd9f69a9 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/readonly.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/readonly.html @@ -1,73 +1,73 @@ - - - - - - Using the CKEditor Read-Only API — CKEditor Sample - - - - - -

- CKEditor Samples » Using the CKEditor Read-Only API -

-
-

- This sample shows how to use the - setReadOnly - API to put editor into the read-only state that makes it impossible for users to change the editor contents. -

-

- For details on how to create this setup check the source code of this sample page. -

-
-
-

- -

-

- - -

-
- - - + + + + + + Using the CKEditor Read-Only API — CKEditor Sample + + + + + +

+ CKEditor Samples » Using the CKEditor Read-Only API +

+
+

+ This sample shows how to use the + setReadOnly + API to put editor into the read-only state that makes it impossible for users to change the editor contents. +

+

+ For details on how to create this setup check the source code of this sample page. +

+
+
+

+ +

+

+ + +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/replacebyclass.html b/rainloop/v/0.0.0/static/ckeditor/samples/replacebyclass.html index 6fc3e6fed..1547f33e1 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/replacebyclass.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/replacebyclass.html @@ -1,57 +1,57 @@ - - - - - - Replace Textareas by Class Name — CKEditor Sample - - - - -

- CKEditor Samples » Replace Textarea Elements by Class Name -

-
-

- This sample shows how to automatically replace all <textarea> elements - of a given class with a CKEditor instance. -

-

- To replace a <textarea> element, simply assign it the ckeditor - class, as in the code below: -

-
-<textarea class="ckeditor" name="editor1"></textarea>
-
-

- Note that other <textarea> attributes (like id or name) need to be adjusted to your document. -

-
-
-

- - -

-

- -

-
- - - + + + + + + Replace Textareas by Class Name — CKEditor Sample + + + + +

+ CKEditor Samples » Replace Textarea Elements by Class Name +

+
+

+ This sample shows how to automatically replace all <textarea> elements + of a given class with a CKEditor instance. +

+

+ To replace a <textarea> element, simply assign it the ckeditor + class, as in the code below: +

+
+<textarea class="ckeditor" name="editor1"></textarea>
+
+

+ Note that other <textarea> attributes (like id or name) need to be adjusted to your document. +

+
+
+

+ + +

+

+ +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/replacebycode.html b/rainloop/v/0.0.0/static/ckeditor/samples/replacebycode.html index e5a4c5baf..e25e915cc 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/replacebycode.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/replacebycode.html @@ -1,56 +1,56 @@ - - - - - - Replace Textarea by Code — CKEditor Sample - - - - -

- CKEditor Samples » Replace Textarea Elements Using JavaScript Code -

-
-
-

- This editor is using an <iframe> element-based editing area, provided by the Wysiwygarea plugin. -

-
-CKEDITOR.replace( 'textarea_id' )
-
-
- - -

- -

-
- - - + + + + + + Replace Textarea by Code — CKEditor Sample + + + + +

+ CKEditor Samples » Replace Textarea Elements Using JavaScript Code +

+
+
+

+ This editor is using an <iframe> element-based editing area, provided by the Wysiwygarea plugin. +

+
+CKEDITOR.replace( 'textarea_id' )
+
+
+ + +

+ +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/sample.css b/rainloop/v/0.0.0/static/ckeditor/samples/sample.css index 8fd71aaac..4d138a874 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/sample.css +++ b/rainloop/v/0.0.0/static/ckeditor/samples/sample.css @@ -1,365 +1,365 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ - -html, body, h1, h2, h3, h4, h5, h6, div, span, blockquote, p, address, form, fieldset, img, ul, ol, dl, dt, dd, li, hr, table, td, th, strong, em, sup, sub, dfn, ins, del, q, cite, var, samp, code, kbd, tt, pre -{ - line-height: 1.5; -} - -body -{ - padding: 10px 30px; -} - -input, textarea, select, option, optgroup, button, td, th -{ - font-size: 100%; -} - -pre -{ - -moz-tab-size: 4; - -o-tab-size: 4; - -webkit-tab-size: 4; - tab-size: 4; -} - -pre, code, kbd, samp, tt -{ - font-family: monospace,monospace; - font-size: 1em; -} - -body { - width: 960px; - margin: 0 auto; -} - -code -{ - background: #f3f3f3; - border: 1px solid #ddd; - padding: 1px 4px; - - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; -} - -abbr -{ - border-bottom: 1px dotted #555; - cursor: pointer; -} - -.new, .beta -{ - text-transform: uppercase; - font-size: 10px; - font-weight: bold; - padding: 1px 4px; - margin: 0 0 0 5px; - color: #fff; - float: right; - - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; -} - -.new -{ - background: #FF7E00; - border: 1px solid #DA8028; - text-shadow: 0 1px 0 #C97626; - - -moz-box-shadow: 0 2px 3px 0 #FFA54E inset; - -webkit-box-shadow: 0 2px 3px 0 #FFA54E inset; - box-shadow: 0 2px 3px 0 #FFA54E inset; -} - -.beta -{ - background: #18C0DF; - border: 1px solid #19AAD8; - text-shadow: 0 1px 0 #048CAD; - font-style: italic; - - -moz-box-shadow: 0 2px 3px 0 #50D4FD inset; - -webkit-box-shadow: 0 2px 3px 0 #50D4FD inset; - box-shadow: 0 2px 3px 0 #50D4FD inset; -} - -h1.samples -{ - color: #0782C1; - font-size: 200%; - font-weight: normal; - margin: 0; - padding: 0; -} - -h1.samples a -{ - color: #0782C1; - text-decoration: none; - border-bottom: 1px dotted #0782C1; -} - -.samples a:hover -{ - border-bottom: 1px dotted #0782C1; -} - -h2.samples -{ - color: #000000; - font-size: 130%; - margin: 15px 0 0 0; - padding: 0; -} - -p, blockquote, address, form, pre, dl, h1.samples, h2.samples -{ - margin-bottom: 15px; -} - -ul.samples -{ - margin-bottom: 15px; -} - -.clear -{ - clear: both; -} - -fieldset -{ - margin: 0; - padding: 10px; -} - -body, input, textarea -{ - color: #333333; - font-family: Arial, Helvetica, sans-serif; -} - -body -{ - font-size: 75%; -} - -a.samples -{ - color: #189DE1; - text-decoration: none; -} - -form -{ - margin: 0; - padding: 0; -} - -pre.samples -{ - background-color: #F7F7F7; - border: 1px solid #D7D7D7; - overflow: auto; - padding: 0.25em; - white-space: pre-wrap; /* CSS 2.1 */ - word-wrap: break-word; /* IE7 */ -} - -#footer -{ - clear: both; - padding-top: 10px; -} - -#footer hr -{ - margin: 10px 0 15px 0; - height: 1px; - border: solid 1px gray; - border-bottom: none; -} - -#footer p -{ - margin: 0 10px 10px 10px; - float: left; -} - -#footer #copy -{ - float: right; -} - -#outputSample -{ - width: 100%; - table-layout: fixed; -} - -#outputSample thead th -{ - color: #dddddd; - background-color: #999999; - padding: 4px; - white-space: nowrap; -} - -#outputSample tbody th -{ - vertical-align: top; - text-align: left; -} - -#outputSample pre -{ - margin: 0; - padding: 0; -} - -.description -{ - border: 1px dotted #B7B7B7; - margin-bottom: 10px; - padding: 10px 10px 0; - overflow: hidden; -} - -label -{ - display: block; - margin-bottom: 6px; -} - -/** - * CKEditor editables are automatically set with the "cke_editable" class - * plus cke_editable_(inline|themed) depending on the editor type. - */ - -/* Style a bit the inline editables. */ -.cke_editable.cke_editable_inline -{ - cursor: pointer; -} - -/* Once an editable element gets focused, the "cke_focus" class is - added to it, so we can style it differently. */ -.cke_editable.cke_editable_inline.cke_focus -{ - box-shadow: inset 0px 0px 20px 3px #ddd, inset 0 0 1px #000; - outline: none; - background: #eee; - cursor: text; -} - -/* Avoid pre-formatted overflows inline editable. */ -.cke_editable_inline pre -{ - white-space: pre-wrap; - word-wrap: break-word; -} - -/** - * Samples index styles. - */ - -.twoColumns, -.twoColumnsLeft, -.twoColumnsRight -{ - overflow: hidden; -} - -.twoColumnsLeft, -.twoColumnsRight -{ - width: 45%; -} - -.twoColumnsLeft -{ - float: left; -} - -.twoColumnsRight -{ - float: right; -} - -dl.samples -{ - padding: 0 0 0 40px; -} -dl.samples > dt -{ - display: list-item; - list-style-type: disc; - list-style-position: outside; - margin: 0 0 3px; -} -dl.samples > dd -{ - margin: 0 0 3px; -} -.warning -{ - color: #ff0000; - background-color: #FFCCBA; - border: 2px dotted #ff0000; - padding: 15px 10px; - margin: 10px 0; -} - -/* Used on inline samples */ - -blockquote -{ - font-style: italic; - font-family: Georgia, Times, "Times New Roman", serif; - padding: 2px 0; - border-style: solid; - border-color: #ccc; - border-width: 0; -} - -.cke_contents_ltr blockquote -{ - padding-left: 20px; - padding-right: 8px; - border-left-width: 5px; -} - -.cke_contents_rtl blockquote -{ - padding-left: 8px; - padding-right: 20px; - border-right-width: 5px; -} - -img.right { - border: 1px solid #ccc; - float: right; - margin-left: 15px; - padding: 5px; -} - -img.left { - border: 1px solid #ccc; - float: left; - margin-right: 15px; - padding: 5px; -} - -.marker -{ - background-color: Yellow; -} +/* +Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ + +html, body, h1, h2, h3, h4, h5, h6, div, span, blockquote, p, address, form, fieldset, img, ul, ol, dl, dt, dd, li, hr, table, td, th, strong, em, sup, sub, dfn, ins, del, q, cite, var, samp, code, kbd, tt, pre +{ + line-height: 1.5; +} + +body +{ + padding: 10px 30px; +} + +input, textarea, select, option, optgroup, button, td, th +{ + font-size: 100%; +} + +pre +{ + -moz-tab-size: 4; + -o-tab-size: 4; + -webkit-tab-size: 4; + tab-size: 4; +} + +pre, code, kbd, samp, tt +{ + font-family: monospace,monospace; + font-size: 1em; +} + +body { + width: 960px; + margin: 0 auto; +} + +code +{ + background: #f3f3f3; + border: 1px solid #ddd; + padding: 1px 4px; + + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +abbr +{ + border-bottom: 1px dotted #555; + cursor: pointer; +} + +.new, .beta +{ + text-transform: uppercase; + font-size: 10px; + font-weight: bold; + padding: 1px 4px; + margin: 0 0 0 5px; + color: #fff; + float: right; + + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; +} + +.new +{ + background: #FF7E00; + border: 1px solid #DA8028; + text-shadow: 0 1px 0 #C97626; + + -moz-box-shadow: 0 2px 3px 0 #FFA54E inset; + -webkit-box-shadow: 0 2px 3px 0 #FFA54E inset; + box-shadow: 0 2px 3px 0 #FFA54E inset; +} + +.beta +{ + background: #18C0DF; + border: 1px solid #19AAD8; + text-shadow: 0 1px 0 #048CAD; + font-style: italic; + + -moz-box-shadow: 0 2px 3px 0 #50D4FD inset; + -webkit-box-shadow: 0 2px 3px 0 #50D4FD inset; + box-shadow: 0 2px 3px 0 #50D4FD inset; +} + +h1.samples +{ + color: #0782C1; + font-size: 200%; + font-weight: normal; + margin: 0; + padding: 0; +} + +h1.samples a +{ + color: #0782C1; + text-decoration: none; + border-bottom: 1px dotted #0782C1; +} + +.samples a:hover +{ + border-bottom: 1px dotted #0782C1; +} + +h2.samples +{ + color: #000000; + font-size: 130%; + margin: 15px 0 0 0; + padding: 0; +} + +p, blockquote, address, form, pre, dl, h1.samples, h2.samples +{ + margin-bottom: 15px; +} + +ul.samples +{ + margin-bottom: 15px; +} + +.clear +{ + clear: both; +} + +fieldset +{ + margin: 0; + padding: 10px; +} + +body, input, textarea +{ + color: #333333; + font-family: Arial, Helvetica, sans-serif; +} + +body +{ + font-size: 75%; +} + +a.samples +{ + color: #189DE1; + text-decoration: none; +} + +form +{ + margin: 0; + padding: 0; +} + +pre.samples +{ + background-color: #F7F7F7; + border: 1px solid #D7D7D7; + overflow: auto; + padding: 0.25em; + white-space: pre-wrap; /* CSS 2.1 */ + word-wrap: break-word; /* IE7 */ +} + +#footer +{ + clear: both; + padding-top: 10px; +} + +#footer hr +{ + margin: 10px 0 15px 0; + height: 1px; + border: solid 1px gray; + border-bottom: none; +} + +#footer p +{ + margin: 0 10px 10px 10px; + float: left; +} + +#footer #copy +{ + float: right; +} + +#outputSample +{ + width: 100%; + table-layout: fixed; +} + +#outputSample thead th +{ + color: #dddddd; + background-color: #999999; + padding: 4px; + white-space: nowrap; +} + +#outputSample tbody th +{ + vertical-align: top; + text-align: left; +} + +#outputSample pre +{ + margin: 0; + padding: 0; +} + +.description +{ + border: 1px dotted #B7B7B7; + margin-bottom: 10px; + padding: 10px 10px 0; + overflow: hidden; +} + +label +{ + display: block; + margin-bottom: 6px; +} + +/** + * CKEditor editables are automatically set with the "cke_editable" class + * plus cke_editable_(inline|themed) depending on the editor type. + */ + +/* Style a bit the inline editables. */ +.cke_editable.cke_editable_inline +{ + cursor: pointer; +} + +/* Once an editable element gets focused, the "cke_focus" class is + added to it, so we can style it differently. */ +.cke_editable.cke_editable_inline.cke_focus +{ + box-shadow: inset 0px 0px 20px 3px #ddd, inset 0 0 1px #000; + outline: none; + background: #eee; + cursor: text; +} + +/* Avoid pre-formatted overflows inline editable. */ +.cke_editable_inline pre +{ + white-space: pre-wrap; + word-wrap: break-word; +} + +/** + * Samples index styles. + */ + +.twoColumns, +.twoColumnsLeft, +.twoColumnsRight +{ + overflow: hidden; +} + +.twoColumnsLeft, +.twoColumnsRight +{ + width: 45%; +} + +.twoColumnsLeft +{ + float: left; +} + +.twoColumnsRight +{ + float: right; +} + +dl.samples +{ + padding: 0 0 0 40px; +} +dl.samples > dt +{ + display: list-item; + list-style-type: disc; + list-style-position: outside; + margin: 0 0 3px; +} +dl.samples > dd +{ + margin: 0 0 3px; +} +.warning +{ + color: #ff0000; + background-color: #FFCCBA; + border: 2px dotted #ff0000; + padding: 15px 10px; + margin: 10px 0; +} + +/* Used on inline samples */ + +blockquote +{ + font-style: italic; + font-family: Georgia, Times, "Times New Roman", serif; + padding: 2px 0; + border-style: solid; + border-color: #ccc; + border-width: 0; +} + +.cke_contents_ltr blockquote +{ + padding-left: 20px; + padding-right: 8px; + border-left-width: 5px; +} + +.cke_contents_rtl blockquote +{ + padding-left: 8px; + padding-right: 20px; + border-right-width: 5px; +} + +img.right { + border: 1px solid #ccc; + float: right; + margin-left: 15px; + padding: 5px; +} + +img.left { + border: 1px solid #ccc; + float: left; + margin-right: 15px; + padding: 5px; +} + +.marker +{ + background-color: Yellow; +} diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/sample.js b/rainloop/v/0.0.0/static/ckeditor/samples/sample.js index b25482d3a..5026ab92f 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/sample.js +++ b/rainloop/v/0.0.0/static/ckeditor/samples/sample.js @@ -1,50 +1,50 @@ -/** - * Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or http://ckeditor.com/license - */ - -// Tool scripts for the sample pages. -// This file can be ignored and is not required to make use of CKEditor. - -( function() { - CKEDITOR.on( 'instanceReady', function( ev ) { - // Check for sample compliance. - var editor = ev.editor, - meta = CKEDITOR.document.$.getElementsByName( 'ckeditor-sample-required-plugins' ), - requires = meta.length ? CKEDITOR.dom.element.get( meta[ 0 ] ).getAttribute( 'content' ).split( ',' ) : [], - missing = [], - i; - - if ( requires.length ) { - for ( i = 0; i < requires.length; i++ ) { - if ( !editor.plugins[ requires[ i ] ] ) - missing.push( '' + requires[ i ] + '' ); - } - - if ( missing.length ) { - var warn = CKEDITOR.dom.element.createFromHtml( - '
' + - 'To fully experience this demo, the ' + missing.join( ', ' ) + ' plugin' + ( missing.length > 1 ? 's are' : ' is' ) + ' required.' + - '
' - ); - warn.insertBefore( editor.container ); - } - } - - // Set icons. - var doc = new CKEDITOR.dom.document( document ), - icons = doc.find( '.button_icon' ); - - for ( i = 0; i < icons.count(); i++ ) { - var icon = icons.getItem( i ), - name = icon.getAttribute( 'data-icon' ), - style = CKEDITOR.skin.getIconStyle( name, ( CKEDITOR.lang.dir == 'rtl' ) ); - - icon.addClass( 'cke_button_icon' ); - icon.addClass( 'cke_button__' + name + '_icon' ); - icon.setAttribute( 'style', style ); - icon.setStyle( 'float', 'none' ); - - } - } ); -} )(); +/** + * Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +// Tool scripts for the sample pages. +// This file can be ignored and is not required to make use of CKEditor. + +( function() { + CKEDITOR.on( 'instanceReady', function( ev ) { + // Check for sample compliance. + var editor = ev.editor, + meta = CKEDITOR.document.$.getElementsByName( 'ckeditor-sample-required-plugins' ), + requires = meta.length ? CKEDITOR.dom.element.get( meta[ 0 ] ).getAttribute( 'content' ).split( ',' ) : [], + missing = [], + i; + + if ( requires.length ) { + for ( i = 0; i < requires.length; i++ ) { + if ( !editor.plugins[ requires[ i ] ] ) + missing.push( '' + requires[ i ] + '' ); + } + + if ( missing.length ) { + var warn = CKEDITOR.dom.element.createFromHtml( + '
' + + 'To fully experience this demo, the ' + missing.join( ', ' ) + ' plugin' + ( missing.length > 1 ? 's are' : ' is' ) + ' required.' + + '
' + ); + warn.insertBefore( editor.container ); + } + } + + // Set icons. + var doc = new CKEDITOR.dom.document( document ), + icons = doc.find( '.button_icon' ); + + for ( i = 0; i < icons.count(); i++ ) { + var icon = icons.getItem( i ), + name = icon.getAttribute( 'data-icon' ), + style = CKEDITOR.skin.getIconStyle( name, ( CKEDITOR.lang.dir == 'rtl' ) ); + + icon.addClass( 'cke_button_icon' ); + icon.addClass( 'cke_button__' + name + '_icon' ); + icon.setAttribute( 'style', style ); + icon.setStyle( 'float', 'none' ); + + } + } ); +} )(); diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/sample_posteddata.php b/rainloop/v/0.0.0/static/ckeditor/samples/sample_posteddata.php index e4869b7cd..7775f079c 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/sample_posteddata.php +++ b/rainloop/v/0.0.0/static/ckeditor/samples/sample_posteddata.php @@ -1,16 +1,16 @@ -
-
--------------------------------------------------------------------------------------------
-  CKEditor - Posted Data
-
-  We are sorry, but your Web server does not support the PHP language used in this script.
-
-  Please note that CKEditor can be used with any other server-side language than just PHP.
-  To save the content created with CKEditor you need to read the POST data on the server
-  side and write it to a file or the database.
-
-  Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved.
-  For licensing, see LICENSE.md or http://ckeditor.com/license
--------------------------------------------------------------------------------------------
-
-
*/ include "assets/posteddata.php"; ?> +
+
+-------------------------------------------------------------------------------------------
+  CKEditor - Posted Data
+
+  We are sorry, but your Web server does not support the PHP language used in this script.
+
+  Please note that CKEditor can be used with any other server-side language than just PHP.
+  To save the content created with CKEditor you need to read the POST data on the server
+  side and write it to a file or the database.
+
+  Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
+  For licensing, see LICENSE.md or http://ckeditor.com/license
+-------------------------------------------------------------------------------------------
+
+
*/ include "assets/posteddata.php"; ?> diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/tabindex.html b/rainloop/v/0.0.0/static/ckeditor/samples/tabindex.html index 89521668b..5d1ab6325 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/tabindex.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/tabindex.html @@ -1,75 +1,75 @@ - - - - - - TAB Key-Based Navigation — CKEditor Sample - - - - - - -

- CKEditor Samples » TAB Key-Based Navigation -

-
-

- This sample shows how tab key navigation among editor instances is - affected by the tabIndex attribute from - the original page element. Use TAB key to move between the editors. -

-
-

- -

-
-

- -

-

- -

- - - + + + + + + TAB Key-Based Navigation — CKEditor Sample + + + + + + +

+ CKEditor Samples » TAB Key-Based Navigation +

+
+

+ This sample shows how tab key navigation among editor instances is + affected by the tabIndex attribute from + the original page element. Use TAB key to move between the editors. +

+
+

+ +

+
+

+ +

+

+ +

+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/uicolor.html b/rainloop/v/0.0.0/static/ckeditor/samples/uicolor.html index ce4b2a261..d7c5dead9 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/uicolor.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/uicolor.html @@ -1,69 +1,69 @@ - - - - - - UI Color Picker — CKEditor Sample - - - - -

- CKEditor Samples » UI Color -

-
-

- This sample shows how to automatically replace <textarea> elements - with a CKEditor instance with an option to change the color of its user interface.
- Note:The UI skin color feature depends on the CKEditor skin - compatibility. The Moono and Kama skins are examples of skins that work with it. -

-
-
-

- This editor instance has a UI color value defined in configuration to change the skin color, - To specify the color of the user interface, set the uiColor property: -

-
-CKEDITOR.replace( 'textarea_id', {
-	uiColor: '#14B8C4'
-});
-

- Note that textarea_id in the code above is the id attribute of - the <textarea> element to be replaced. -

-

- - -

-

- -

-
- - - + + + + + + UI Color Picker — CKEditor Sample + + + + +

+ CKEditor Samples » UI Color +

+
+

+ This sample shows how to automatically replace <textarea> elements + with a CKEditor instance with an option to change the color of its user interface.
+ Note:The UI skin color feature depends on the CKEditor skin + compatibility. The Moono and Kama skins are examples of skins that work with it. +

+
+
+

+ This editor instance has a UI color value defined in configuration to change the skin color, + To specify the color of the user interface, set the uiColor property: +

+
+CKEDITOR.replace( 'textarea_id', {
+	uiColor: '#14B8C4'
+});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+

+ + +

+

+ +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/uilanguages.html b/rainloop/v/0.0.0/static/ckeditor/samples/uilanguages.html index 66acca438..4e73dcc76 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/uilanguages.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/uilanguages.html @@ -1,119 +1,119 @@ - - - - - - User Interface Globalization — CKEditor Sample - - - - - -

- CKEditor Samples » User Interface Languages -

-
-

- This sample shows how to automatically replace <textarea> elements - with a CKEditor instance with an option to change the language of its user interface. -

-

- It pulls the language list from CKEditor _languages.js file that contains the list of supported languages and creates - a drop-down list that lets the user change the UI language. -

-

- By default, CKEditor automatically localizes the editor to the language of the user. - The UI language can be controlled with two configuration options: - language and - - defaultLanguage. The defaultLanguage setting specifies the - default CKEditor language to be used when a localization suitable for user's settings is not available. -

-

- To specify the user interface language that will be used no matter what language is - specified in user's browser or operating system, set the language property: -

-
-CKEDITOR.replace( 'textarea_id', {
-	// Load the German interface.
-	language: 'de'
-});
-

- Note that textarea_id in the code above is the id attribute of - the <textarea> element to be replaced. -

-
-
-

- Available languages ( languages!):
- -
- - (You may see strange characters if your system does not support the selected language) - -

-

- - -

-
- - - + + + + + + User Interface Globalization — CKEditor Sample + + + + + +

+ CKEditor Samples » User Interface Languages +

+
+

+ This sample shows how to automatically replace <textarea> elements + with a CKEditor instance with an option to change the language of its user interface. +

+

+ It pulls the language list from CKEditor _languages.js file that contains the list of supported languages and creates + a drop-down list that lets the user change the UI language. +

+

+ By default, CKEditor automatically localizes the editor to the language of the user. + The UI language can be controlled with two configuration options: + language and + + defaultLanguage. The defaultLanguage setting specifies the + default CKEditor language to be used when a localization suitable for user's settings is not available. +

+

+ To specify the user interface language that will be used no matter what language is + specified in user's browser or operating system, set the language property: +

+
+CKEDITOR.replace( 'textarea_id', {
+	// Load the German interface.
+	language: 'de'
+});
+

+ Note that textarea_id in the code above is the id attribute of + the <textarea> element to be replaced. +

+
+
+

+ Available languages ( languages!):
+ +
+ + (You may see strange characters if your system does not support the selected language) + +

+

+ + +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/samples/xhtmlstyle.html b/rainloop/v/0.0.0/static/ckeditor/samples/xhtmlstyle.html index f219d11d0..b53b431f9 100644 --- a/rainloop/v/0.0.0/static/ckeditor/samples/xhtmlstyle.html +++ b/rainloop/v/0.0.0/static/ckeditor/samples/xhtmlstyle.html @@ -1,231 +1,231 @@ - - - - - - XHTML Compliant Output — CKEditor Sample - - - - - - -

- CKEditor Samples » Producing XHTML Compliant Output -

-
-

- This sample shows how to configure CKEditor to output valid - XHTML 1.1 code. - Deprecated elements (<font>, <u>) or attributes - (size, face) will be replaced with XHTML compliant code. -

-

- To add a CKEditor instance outputting valid XHTML code, load the editor using a standard - JavaScript call and define CKEditor features to use the XHTML compliant elements and styles. -

-

- A snippet of the configuration code can be seen below; check the source of this page for - full definition: -

-
-CKEDITOR.replace( 'textarea_id', {
-	contentsCss: 'assets/outputxhtml.css',
-
-	coreStyles_bold: {
-		element: 'span',
-		attributes: { 'class': 'Bold' }
-	},
-	coreStyles_italic: {
-		element: 'span',
-		attributes: { 'class': 'Italic' }
-	},
-
-	...
-});
-
-
-

- - - -

-

- -

-
- - - + + + + + + XHTML Compliant Output — CKEditor Sample + + + + + + +

+ CKEditor Samples » Producing XHTML Compliant Output +

+
+

+ This sample shows how to configure CKEditor to output valid + XHTML 1.1 code. + Deprecated elements (<font>, <u>) or attributes + (size, face) will be replaced with XHTML compliant code. +

+

+ To add a CKEditor instance outputting valid XHTML code, load the editor using a standard + JavaScript call and define CKEditor features to use the XHTML compliant elements and styles. +

+

+ A snippet of the configuration code can be seen below; check the source of this page for + full definition: +

+
+CKEDITOR.replace( 'textarea_id', {
+	contentsCss: 'assets/outputxhtml.css',
+
+	coreStyles_bold: {
+		element: 'span',
+		attributes: { 'class': 'Bold' }
+	},
+	coreStyles_italic: {
+		element: 'span',
+		attributes: { 'class': 'Italic' }
+	},
+
+	...
+});
+
+
+

+ + + +

+

+ +

+
+ + + diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/bold.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/bold.png new file mode 100644 index 000000000..12d43c56c Binary files /dev/null and b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/bold.png differ diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog.css new file mode 100644 index 000000000..34b7bf210 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #bb4600;border-color:#bb4600 #bb4600 #bb4600;background:#ec6f00;background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff6000),to(#ff6000));background-image:-webkit-linear-gradient(top,#ff6000,#ff6000);background-image:-o-linear-gradient(top,#ff6000,#ff6000);background-image:linear-gradient(to bottom,#ff6000,#ff6000);background-image:-moz-linear-gradient(top,#ff6000,#ff6000);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ff6000',endColorstr='#ff6000')}a.cke_dialog_ui_button_ok:hover{border-color:#9a3a00 #9a3a00 #9a3a00;background:#e45600;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#e45600),color-stop(100%,#e45600));background:-webkit-linear-gradient(top,#e45600 0,#e45600 100%);background:-o-linear-gradient(top,#e45600 0,#e45600 100%);background:linear-gradient(to bottom,#e45600 0,#e45600 100%);background:-moz-linear-gradient(top,#e45600 0,#e45600 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e45600',endColorstr='#e45600v',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_ie.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_ie.css new file mode 100644 index 000000000..b17e17f56 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_ie.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #bb4600;border-color:#bb4600 #bb4600 #bb4600;background:#ec6f00;background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff6000),to(#ff6000));background-image:-webkit-linear-gradient(top,#ff6000,#ff6000);background-image:-o-linear-gradient(top,#ff6000,#ff6000);background-image:linear-gradient(to bottom,#ff6000,#ff6000);background-image:-moz-linear-gradient(top,#ff6000,#ff6000);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ff6000',endColorstr='#ff6000')}a.cke_dialog_ui_button_ok:hover{border-color:#9a3a00 #9a3a00 #9a3a00;background:#e45600;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#e45600),color-stop(100%,#e45600));background:-webkit-linear-gradient(top,#e45600 0,#e45600 100%);background:-o-linear-gradient(top,#e45600 0,#e45600 100%);background:linear-gradient(to bottom,#e45600 0,#e45600 100%);background:-moz-linear-gradient(top,#e45600 0,#e45600 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e45600',endColorstr='#e45600v',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_ie7.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_ie7.css new file mode 100644 index 000000000..cd15d7d08 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_ie7.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #bb4600;border-color:#bb4600 #bb4600 #bb4600;background:#ec6f00;background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff6000),to(#ff6000));background-image:-webkit-linear-gradient(top,#ff6000,#ff6000);background-image:-o-linear-gradient(top,#ff6000,#ff6000);background-image:linear-gradient(to bottom,#ff6000,#ff6000);background-image:-moz-linear-gradient(top,#ff6000,#ff6000);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ff6000',endColorstr='#ff6000')}a.cke_dialog_ui_button_ok:hover{border-color:#9a3a00 #9a3a00 #9a3a00;background:#e45600;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#e45600),color-stop(100%,#e45600));background:-webkit-linear-gradient(top,#e45600 0,#e45600 100%);background:-o-linear-gradient(top,#e45600 0,#e45600 100%);background:linear-gradient(to bottom,#e45600 0,#e45600 100%);background:-moz-linear-gradient(top,#e45600 0,#e45600 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e45600',endColorstr='#e45600v',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}.cke_dialog_title{zoom:1}.cke_dialog_footer{border-top:1px solid #bfbfbf}.cke_dialog_footer_buttons{position:static}.cke_dialog_footer_buttons a.cke_dialog_ui_button{vertical-align:top}.cke_dialog .cke_resizer_ltr{padding-left:4px}.cke_dialog .cke_resizer_rtl{padding-right:4px}.cke_dialog_ui_input_text,.cke_dialog_ui_input_password,.cke_dialog_ui_input_textarea,.cke_dialog_ui_input_select{padding:0!important}.cke_dialog_ui_checkbox_input,.cke_dialog_ui_ratio_input,.cke_btn_reset,.cke_btn_locked,.cke_btn_unlocked{border:1px solid transparent!important} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_ie8.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_ie8.css new file mode 100644 index 000000000..8c385b84e --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_ie8.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #bb4600;border-color:#bb4600 #bb4600 #bb4600;background:#ec6f00;background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff6000),to(#ff6000));background-image:-webkit-linear-gradient(top,#ff6000,#ff6000);background-image:-o-linear-gradient(top,#ff6000,#ff6000);background-image:linear-gradient(to bottom,#ff6000,#ff6000);background-image:-moz-linear-gradient(top,#ff6000,#ff6000);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ff6000',endColorstr='#ff6000')}a.cke_dialog_ui_button_ok:hover{border-color:#9a3a00 #9a3a00 #9a3a00;background:#e45600;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#e45600),color-stop(100%,#e45600));background:-webkit-linear-gradient(top,#e45600 0,#e45600 100%);background:-o-linear-gradient(top,#e45600 0,#e45600 100%);background:linear-gradient(to bottom,#e45600 0,#e45600 100%);background:-moz-linear-gradient(top,#e45600 0,#e45600 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e45600',endColorstr='#e45600v',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{display:block} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_iequirks.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_iequirks.css new file mode 100644 index 000000000..268664972 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_iequirks.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #bb4600;border-color:#bb4600 #bb4600 #bb4600;background:#ec6f00;background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff6000),to(#ff6000));background-image:-webkit-linear-gradient(top,#ff6000,#ff6000);background-image:-o-linear-gradient(top,#ff6000,#ff6000);background-image:linear-gradient(to bottom,#ff6000,#ff6000);background-image:-moz-linear-gradient(top,#ff6000,#ff6000);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ff6000',endColorstr='#ff6000')}a.cke_dialog_ui_button_ok:hover{border-color:#9a3a00 #9a3a00 #9a3a00;background:#e45600;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#e45600),color-stop(100%,#e45600));background:-webkit-linear-gradient(top,#e45600 0,#e45600 100%);background:-o-linear-gradient(top,#e45600 0,#e45600 100%);background:linear-gradient(to bottom,#e45600 0,#e45600 100%);background:-moz-linear-gradient(top,#e45600 0,#e45600 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e45600',endColorstr='#e45600v',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}.cke_dialog_footer{filter:""} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_opera.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_opera.css new file mode 100644 index 000000000..0c58465ff --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/dialog_opera.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_browser_gecko19 .cke_dialog_body{position:relative}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:0 0;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:3px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 12px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:20px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #bb4600;border-color:#bb4600 #bb4600 #bb4600;background:#ec6f00;background-image:-webkit-gradient(linear,0 0,0 100%,from(#ff6000),to(#ff6000));background-image:-webkit-linear-gradient(top,#ff6000,#ff6000);background-image:-o-linear-gradient(top,#ff6000,#ff6000);background-image:linear-gradient(to bottom,#ff6000,#ff6000);background-image:-moz-linear-gradient(top,#ff6000,#ff6000);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ff6000',endColorstr='#ff6000')}a.cke_dialog_ui_button_ok:hover{border-color:#9a3a00 #9a3a00 #9a3a00;background:#e45600;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#e45600),color-stop(100%,#e45600));background:-webkit-linear-gradient(top,#e45600 0,#e45600 100%);background:-o-linear-gradient(top,#e45600 0,#e45600 100%);background:linear-gradient(to bottom,#e45600 0,#e45600 100%);background:-moz-linear-gradient(top,#e45600 0,#e45600 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#e45600',endColorstr='#e45600v',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:2px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:24px;line-height:24px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:2px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_dialog_footer{display:block;height:38px}.cke_ltr .cke_dialog_footer>*{float:right}.cke_rtl .cke_dialog_footer>*{float:left} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor.css new file mode 100644 index 000000000..b29bbd135 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:2px solid #fff;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#e6e6e6;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#f0f0f0));background-image:-moz-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-webkit-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-o-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-ms-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:linear-gradient(top,#f0f0f0,#f0f0f0);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#f0f0f0')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#fff));background-image:-moz-linear-gradient(top,#f0f0f0,#fff);background-image:-webkit-linear-gradient(top,#f0f0f0,#fff);background-image:-o-linear-gradient(top,#f0f0f0,#fff);background-image:-ms-linear-gradient(top,#f0f0f0,#fff);background-image:linear-gradient(top,#f0f0f0,#fff);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#ffffff')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#fbfbfb));background-image:-moz-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-webkit-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-o-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-ms-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:linear-gradient(top,#f8f8f8,#fbfbfb);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f8f8f8',endColorstr='#fbfbfb')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:2px 0 0 0}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:6px 8px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 0 0 rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);-webkit-box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#e3e3e3),to(#e3e3e3));background-image:-moz-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-webkit-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-o-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-ms-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:linear-gradient(top,#e3e3e3,#e3e3e3);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#e3e3e3',endColorstr='#e3e3e3')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#ccc));background-image:-moz-linear-gradient(top,#f8f8f8,#ccc);background-image:-webkit-linear-gradient(top,#f8f8f8,#ccc);background-image:-o-linear-gradient(top,#f8f8f8,#ccc);background-image:-ms-linear-gradient(top,#f8f8f8,#ccc);background-image:linear-gradient(top,#f8f8f8,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:30px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__bidiltr_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bidirtl_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_button__fastimage_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;}.cke_hidpi .cke_button__bidiltr_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;}.cke_hidpi .cke_button__bidirtl_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1056px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1104px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1152px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -1200px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -1248px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1296px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1344px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1392px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1440px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -1488px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -1536px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1584px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1632px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -1680px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1728px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1776px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1824px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1872px !important;}.cke_hidpi .cke_button__fastimage_icon {background: url(icons_hidpi.png) no-repeat 0 -1920px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_gecko.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_gecko.css new file mode 100644 index 000000000..864d11751 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_gecko.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:2px solid #fff;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#e6e6e6;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#f0f0f0));background-image:-moz-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-webkit-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-o-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-ms-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:linear-gradient(top,#f0f0f0,#f0f0f0);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#f0f0f0')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#fff));background-image:-moz-linear-gradient(top,#f0f0f0,#fff);background-image:-webkit-linear-gradient(top,#f0f0f0,#fff);background-image:-o-linear-gradient(top,#f0f0f0,#fff);background-image:-ms-linear-gradient(top,#f0f0f0,#fff);background-image:linear-gradient(top,#f0f0f0,#fff);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#ffffff')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#fbfbfb));background-image:-moz-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-webkit-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-o-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-ms-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:linear-gradient(top,#f8f8f8,#fbfbfb);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f8f8f8',endColorstr='#fbfbfb')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:2px 0 0 0}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:6px 8px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 0 0 rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);-webkit-box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#e3e3e3),to(#e3e3e3));background-image:-moz-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-webkit-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-o-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-ms-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:linear-gradient(top,#e3e3e3,#e3e3e3);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#e3e3e3',endColorstr='#e3e3e3')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#ccc));background-image:-moz-linear-gradient(top,#f8f8f8,#ccc);background-image:-webkit-linear-gradient(top,#f8f8f8,#ccc);background-image:-o-linear-gradient(top,#f8f8f8,#ccc);background-image:-ms-linear-gradient(top,#f8f8f8,#ccc);background-image:linear-gradient(top,#f8f8f8,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:30px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}.cke_bottom{padding-bottom:3px}.cke_combo_text{margin-bottom:-1px;margin-top:1px}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__bidiltr_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bidirtl_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_button__fastimage_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;}.cke_hidpi .cke_button__bidiltr_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;}.cke_hidpi .cke_button__bidirtl_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1056px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1104px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1152px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -1200px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -1248px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1296px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1344px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1392px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1440px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -1488px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -1536px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1584px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1632px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -1680px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1728px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1776px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1824px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1872px !important;}.cke_hidpi .cke_button__fastimage_icon {background: url(icons_hidpi.png) no-repeat 0 -1920px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_ie.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_ie.css new file mode 100644 index 000000000..1f6eaa6b0 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_ie.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:2px solid #fff;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#e6e6e6;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#f0f0f0));background-image:-moz-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-webkit-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-o-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-ms-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:linear-gradient(top,#f0f0f0,#f0f0f0);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#f0f0f0')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#fff));background-image:-moz-linear-gradient(top,#f0f0f0,#fff);background-image:-webkit-linear-gradient(top,#f0f0f0,#fff);background-image:-o-linear-gradient(top,#f0f0f0,#fff);background-image:-ms-linear-gradient(top,#f0f0f0,#fff);background-image:linear-gradient(top,#f0f0f0,#fff);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#ffffff')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#fbfbfb));background-image:-moz-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-webkit-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-o-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-ms-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:linear-gradient(top,#f8f8f8,#fbfbfb);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f8f8f8',endColorstr='#fbfbfb')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:2px 0 0 0}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:6px 8px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 0 0 rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);-webkit-box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#e3e3e3),to(#e3e3e3));background-image:-moz-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-webkit-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-o-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-ms-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:linear-gradient(top,#e3e3e3,#e3e3e3);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#e3e3e3',endColorstr='#e3e3e3')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#ccc));background-image:-moz-linear-gradient(top,#f8f8f8,#ccc);background-image:-webkit-linear-gradient(top,#f8f8f8,#ccc);background-image:-o-linear-gradient(top,#f8f8f8,#ccc);background-image:-ms-linear-gradient(top,#f8f8f8,#ccc);background-image:linear-gradient(top,#f8f8f8,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:30px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__bidiltr_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bidirtl_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_button__fastimage_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;}.cke_hidpi .cke_button__bidiltr_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;}.cke_hidpi .cke_button__bidirtl_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1056px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1104px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1152px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -1200px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -1248px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1296px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1344px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1392px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1440px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -1488px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -1536px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1584px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1632px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -1680px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1728px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1776px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1824px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1872px !important;}.cke_hidpi .cke_button__fastimage_icon {background: url(icons_hidpi.png) no-repeat 0 -1920px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_ie7.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_ie7.css new file mode 100644 index 000000000..81c74a428 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_ie7.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:2px solid #fff;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#e6e6e6;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#f0f0f0));background-image:-moz-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-webkit-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-o-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-ms-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:linear-gradient(top,#f0f0f0,#f0f0f0);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#f0f0f0')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#fff));background-image:-moz-linear-gradient(top,#f0f0f0,#fff);background-image:-webkit-linear-gradient(top,#f0f0f0,#fff);background-image:-o-linear-gradient(top,#f0f0f0,#fff);background-image:-ms-linear-gradient(top,#f0f0f0,#fff);background-image:linear-gradient(top,#f0f0f0,#fff);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#ffffff')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#fbfbfb));background-image:-moz-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-webkit-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-o-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-ms-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:linear-gradient(top,#f8f8f8,#fbfbfb);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f8f8f8',endColorstr='#fbfbfb')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:2px 0 0 0}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:6px 8px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 0 0 rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);-webkit-box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#e3e3e3),to(#e3e3e3));background-image:-moz-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-webkit-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-o-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-ms-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:linear-gradient(top,#e3e3e3,#e3e3e3);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#e3e3e3',endColorstr='#e3e3e3')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#ccc));background-image:-moz-linear-gradient(top,#f8f8f8,#ccc);background-image:-webkit-linear-gradient(top,#f8f8f8,#ccc);background-image:-o-linear-gradient(top,#f8f8f8,#ccc);background-image:-ms-linear-gradient(top,#f8f8f8,#ccc);background-image:linear-gradient(top,#f8f8f8,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:30px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_button,.cke_rtl .cke_button *,.cke_rtl .cke_combo,.cke_rtl .cke_combo *,.cke_rtl .cke_path_item,.cke_rtl .cke_path_item *,.cke_rtl .cke_path_empty{float:none}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_combo_button,.cke_rtl .cke_combo_button *,.cke_rtl .cke_button,.cke_rtl .cke_button_icon,{display:inline-block;vertical-align:top}.cke_toolbox{display:inline-block;padding-bottom:5px;height:100%}.cke_rtl .cke_toolbox{padding-bottom:0}.cke_toolbar{margin-bottom:5px}.cke_rtl .cke_toolbar{margin-bottom:0}.cke_toolgroup{height:26px}.cke_toolgroup,.cke_combo{position:relative}a.cke_button{float:none;vertical-align:top}.cke_toolbar_separator{display:inline-block;float:none;vertical-align:top;background-color:#c0c0c0}.cke_toolbox_collapser .cke_arrow{margin-top:0}.cke_toolbox_collapser .cke_arrow{border-width:4px}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{border-width:3px}.cke_rtl .cke_button_arrow{padding-top:8px;margin-right:2px}.cke_rtl .cke_combo_inlinelabel{display:table-cell;vertical-align:middle}.cke_menubutton{display:block;height:24px}.cke_menubutton_inner{display:block;position:relative}.cke_menubutton_icon{height:16px;width:16px}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:inline-block}.cke_menubutton_label{width:auto;vertical-align:top;line-height:24px;height:24px;margin:0 10px 0 0}.cke_menuarrow{width:5px;height:6px;padding:0;position:absolute;right:8px;top:10px;background-position:0 0}.cke_rtl .cke_menubutton_icon{position:absolute;right:0;top:0}.cke_rtl .cke_menubutton_label{float:right;clear:both;margin:0 24px 0 10px}.cke_hc .cke_rtl .cke_menubutton_label{margin-right:0}.cke_rtl .cke_menuarrow{left:8px;right:auto;background-position:0 -24px}.cke_hc .cke_menuarrow{top:5px;padding:0 5px}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{position:relative}.cke_wysiwyg_div{padding-top:0!important;padding-bottom:0!important}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__bidiltr_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bidirtl_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_button__fastimage_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;}.cke_hidpi .cke_button__bidiltr_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;}.cke_hidpi .cke_button__bidirtl_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1056px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1104px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1152px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -1200px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -1248px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1296px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1344px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1392px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1440px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -1488px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -1536px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1584px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1632px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -1680px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1728px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1776px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1824px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1872px !important;}.cke_hidpi .cke_button__fastimage_icon {background: url(icons_hidpi.png) no-repeat 0 -1920px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_ie8.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_ie8.css new file mode 100644 index 000000000..84108fd23 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_ie8.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:2px solid #fff;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#e6e6e6;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#f0f0f0));background-image:-moz-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-webkit-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-o-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-ms-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:linear-gradient(top,#f0f0f0,#f0f0f0);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#f0f0f0')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#fff));background-image:-moz-linear-gradient(top,#f0f0f0,#fff);background-image:-webkit-linear-gradient(top,#f0f0f0,#fff);background-image:-o-linear-gradient(top,#f0f0f0,#fff);background-image:-ms-linear-gradient(top,#f0f0f0,#fff);background-image:linear-gradient(top,#f0f0f0,#fff);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#ffffff')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#fbfbfb));background-image:-moz-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-webkit-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-o-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-ms-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:linear-gradient(top,#f8f8f8,#fbfbfb);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f8f8f8',endColorstr='#fbfbfb')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:2px 0 0 0}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:6px 8px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 0 0 rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);-webkit-box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#e3e3e3),to(#e3e3e3));background-image:-moz-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-webkit-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-o-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-ms-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:linear-gradient(top,#e3e3e3,#e3e3e3);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#e3e3e3',endColorstr='#e3e3e3')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#ccc));background-image:-moz-linear-gradient(top,#f8f8f8,#ccc);background-image:-webkit-linear-gradient(top,#f8f8f8,#ccc);background-image:-o-linear-gradient(top,#f8f8f8,#ccc);background-image:-ms-linear-gradient(top,#f8f8f8,#ccc);background-image:linear-gradient(top,#f8f8f8,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:30px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_toolbox_collapser .cke_arrow{border-width:4px}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{border-width:3px}.cke_toolbox_collapser .cke_arrow{margin-top:0}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__bidiltr_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bidirtl_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_button__fastimage_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;}.cke_hidpi .cke_button__bidiltr_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;}.cke_hidpi .cke_button__bidirtl_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1056px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1104px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1152px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -1200px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -1248px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1296px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1344px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1392px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1440px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -1488px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -1536px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1584px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1632px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -1680px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1728px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1776px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1824px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1872px !important;}.cke_hidpi .cke_button__fastimage_icon {background: url(icons_hidpi.png) no-repeat 0 -1920px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_iequirks.css b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_iequirks.css new file mode 100644 index 000000000..2af775272 --- /dev/null +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/editor_iequirks.css @@ -0,0 +1,5 @@ +/* +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. +For licensing, see LICENSE.md or http://ckeditor.com/license +*/ +.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:2px solid #fff;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#e6e6e6;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#f0f0f0));background-image:-moz-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-webkit-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-o-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:-ms-linear-gradient(top,#f0f0f0,#f0f0f0);background-image:linear-gradient(top,#f0f0f0,#f0f0f0);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#f0f0f0')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f0f0f0),to(#fff));background-image:-moz-linear-gradient(top,#f0f0f0,#fff);background-image:-webkit-linear-gradient(top,#f0f0f0,#fff);background-image:-o-linear-gradient(top,#f0f0f0,#fff);background-image:-ms-linear-gradient(top,#f0f0f0,#fff);background-image:linear-gradient(top,#f0f0f0,#fff);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f0f0f0',endColorstr='#ffffff')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#fbfbfb));background-image:-moz-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-webkit-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-o-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:-ms-linear-gradient(top,#f8f8f8,#fbfbfb);background-image:linear-gradient(top,#f8f8f8,#fbfbfb);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f8f8f8',endColorstr='#fbfbfb')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup *:first-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:0}.cke_rtl .cke_toolgroup *:last-child{-moz-border-radius:0;-webkit-border-radius:0;border-radius:2px 0 0 0}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:6px 8px;outline:0;cursor:default;float:left;border:0}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 0 0 rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);-webkit-box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);box-shadow:0 0 1px rgba(0,0,0,.4) inset,0 0 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#e3e3e3),to(#e3e3e3));background-image:-moz-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-webkit-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-o-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:-ms-linear-gradient(top,#e3e3e3,#e3e3e3);background-image:linear-gradient(top,#e3e3e3,#e3e3e3);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#e3e3e3',endColorstr='#e3e3e3')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f8f8f8),to(#ccc));background-image:-moz-linear-gradient(top,#f8f8f8,#ccc);background-image:-webkit-linear-gradient(top,#f8f8f8,#ccc);background-image:-o-linear-gradient(top,#f8f8f8,#ccc);background-image:-ms-linear-gradient(top,#f8f8f8,#ccc);background-image:linear-gradient(top,#f8f8f8,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #fff;border-bottom-color:#fff;-moz-border-radius:0;-webkit-border-radius:0;border-radius:0;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:30px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_top,.cke_contents,.cke_bottom{width:100%}.cke_button_arrow{font-size:0}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_button,.cke_rtl .cke_button *,.cke_rtl .cke_combo,.cke_rtl .cke_combo *,.cke_rtl .cke_path_item,.cke_rtl .cke_path_item *,.cke_rtl .cke_path_empty{float:none}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_combo_button,.cke_rtl .cke_combo_button *,.cke_rtl .cke_button,.cke_rtl .cke_button_icon,{display:inline-block;vertical-align:top}.cke_rtl .cke_button_icon{float:none}.cke_resizer{width:10px}.cke_source{white-space:normal}.cke_bottom{position:static}.cke_colorbox{font-size:0}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__bidiltr_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bidirtl_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_button__table_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -888px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -912px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -936px !important;}.cke_button__fastimage_icon {background: url(icons.png) no-repeat 0 -960px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;}.cke_hidpi .cke_button__bidiltr_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;}.cke_hidpi .cke_button__bidirtl_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -912px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -960px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1008px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -1056px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1104px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -1152px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -1200px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -1248px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1296px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1344px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1392px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -1440px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -1488px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -1536px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1584px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -1632px !important;}.cke_hidpi .cke_button__table_icon {background: url(icons_hidpi.png) no-repeat 0 -1680px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1728px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -1776px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1824px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -1872px !important;}.cke_hidpi .cke_button__fastimage_icon {background: url(icons_hidpi.png) no-repeat 0 -1920px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/find-rtl.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/find-rtl.png new file mode 100644 index 000000000..bde4bc2f9 Binary files /dev/null and b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/find-rtl.png differ diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/icons.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/icons.png new file mode 100644 index 000000000..d03917f9a Binary files /dev/null and b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/icons.png differ diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/icons_hidpi.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/icons_hidpi.png new file mode 100644 index 000000000..e3d48cebb Binary files /dev/null and b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/icons_hidpi.png differ diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/images/arrow.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/arrow.png similarity index 100% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/images/arrow.png rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/arrow.png diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/images/close.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/close.png similarity index 100% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/images/close.png rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/close.png diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/images/hidpi/close.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/hidpi/close.png similarity index 100% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/images/hidpi/close.png rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/hidpi/close.png diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/images/hidpi/lock-open.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/hidpi/lock-open.png similarity index 100% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/images/hidpi/lock-open.png rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/hidpi/lock-open.png diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/images/hidpi/lock.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/hidpi/lock.png similarity index 100% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/images/hidpi/lock.png rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/hidpi/lock.png diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/images/hidpi/refresh.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/hidpi/refresh.png similarity index 100% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/images/hidpi/refresh.png rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/hidpi/refresh.png diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/images/lock-open.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/lock-open.png similarity index 100% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/images/lock-open.png rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/lock-open.png diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/images/lock.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/lock.png similarity index 100% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/images/lock.png rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/lock.png diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/images/refresh.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/refresh.png similarity index 100% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/images/refresh.png rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/images/refresh.png diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/readme.md b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/readme.md similarity index 95% rename from rainloop/v/0.0.0/static/ckeditor/skins/moono/readme.md rename to rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/readme.md index d086fe9b7..f24ec4f59 100644 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/readme.md +++ b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/readme.md @@ -1,51 +1,51 @@ -"Moono" Skin -==================== - -This skin has been chosen for the **default skin** of CKEditor 4.x, elected from the CKEditor -[skin contest](http://ckeditor.com/blog/new_ckeditor_4_skin) and further shaped by -the CKEditor team. "Moono" is maintained by the core developers. - -For more information about skins, please check the [CKEditor Skin SDK](http://docs.cksource.com/CKEditor_4.x/Skin_SDK) -documentation. - -Features -------------------- -"Moono" is a monochromatic skin, which offers a modern look coupled with gradients and transparency. -It comes with the following features: - -- Chameleon feature with brightness, -- high-contrast compatibility, -- graphics source provided in SVG. - -Directory Structure -------------------- - -CSS parts: -- **editor.css**: the main CSS file. It's simply loading several other files, for easier maintenance, -- **mainui.css**: the file contains styles of entire editor outline structures, -- **toolbar.css**: the file contains styles of the editor toolbar space (top), -- **richcombo.css**: the file contains styles of the rich combo ui elements on toolbar, -- **panel.css**: the file contains styles of the rich combo drop-down, it's not loaded -until the first panel open up, -- **elementspath.css**: the file contains styles of the editor elements path bar (bottom), -- **menu.css**: the file contains styles of all editor menus including context menu and button drop-down, -it's not loaded until the first menu open up, -- **dialog.css**: the CSS files for the dialog UI, it's not loaded until the first dialog open, -- **reset.css**: the file defines the basis of style resets among all editor UI spaces, -- **preset.css**: the file defines the default styles of some UI elements reflecting the skin preference, -- **editor_XYZ.css** and **dialog_XYZ.css**: browser specific CSS hacks. - -Other parts: -- **skin.js**: the only JavaScript part of the skin that registers the skin, its browser specific files and its icons and defines the Chameleon feature, -- **icons/**: contains all skin defined icons, -- **images/**: contains a fill general used images, -- **dev/**: contains SVG source of the skin icons. - -License -------- - -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. - -Licensed under the terms of any of the following licenses at your choice: [GPL](http://www.gnu.org/licenses/gpl.html), [LGPL](http://www.gnu.org/licenses/lgpl.html) and [MPL](http://www.mozilla.org/MPL/MPL-1.1.html). - -See LICENSE.md for more information. +"Moono" Skin +==================== + +This skin has been chosen for the **default skin** of CKEditor 4.x, elected from the CKEditor +[skin contest](http://ckeditor.com/blog/new_ckeditor_4_skin) and further shaped by +the CKEditor team. "Moono" is maintained by the core developers. + +For more information about skins, please check the [CKEditor Skin SDK](http://docs.cksource.com/CKEditor_4.x/Skin_SDK) +documentation. + +Features +------------------- +"Moono" is a monochromatic skin, which offers a modern look coupled with gradients and transparency. +It comes with the following features: + +- Chameleon feature with brightness, +- high-contrast compatibility, +- graphics source provided in SVG. + +Directory Structure +------------------- + +CSS parts: +- **editor.css**: the main CSS file. It's simply loading several other files, for easier maintenance, +- **mainui.css**: the file contains styles of entire editor outline structures, +- **toolbar.css**: the file contains styles of the editor toolbar space (top), +- **richcombo.css**: the file contains styles of the rich combo ui elements on toolbar, +- **panel.css**: the file contains styles of the rich combo drop-down, it's not loaded +until the first panel open up, +- **elementspath.css**: the file contains styles of the editor elements path bar (bottom), +- **menu.css**: the file contains styles of all editor menus including context menu and button drop-down, +it's not loaded until the first menu open up, +- **dialog.css**: the CSS files for the dialog UI, it's not loaded until the first dialog open, +- **reset.css**: the file defines the basis of style resets among all editor UI spaces, +- **preset.css**: the file defines the default styles of some UI elements reflecting the skin preference, +- **editor_XYZ.css** and **dialog_XYZ.css**: browser specific CSS hacks. + +Other parts: +- **skin.js**: the only JavaScript part of the skin that registers the skin, its browser specific files and its icons and defines the Chameleon feature, +- **icons/**: contains all skin defined icons, +- **images/**: contains a fill general used images, +- **dev/**: contains SVG source of the skin icons. + +License +------- + +Copyright (c) 2003-2013, CKSource - Frederico Knabben. All rights reserved. + +Licensed under the terms of any of the following licenses at your choice: [GPL](http://www.gnu.org/licenses/gpl.html), [LGPL](http://www.gnu.org/licenses/lgpl.html) and [MPL](http://www.mozilla.org/MPL/MPL-1.1.html). + +See LICENSE.md for more information. diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/scayt.png b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/scayt.png new file mode 100644 index 000000000..de0e7b8cd Binary files /dev/null and b/rainloop/v/0.0.0/static/ckeditor/skins/icy_orange/scayt.png differ diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog.css deleted file mode 100644 index 76c4651b1..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_ie.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_ie.css deleted file mode 100644 index 1474e9053..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_ie.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_ie7.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_ie7.css deleted file mode 100644 index ebe0b7870..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_ie7.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}.cke_dialog_title{zoom:1}.cke_dialog_footer{border-top:1px solid #bfbfbf}.cke_dialog_footer_buttons{position:static}.cke_dialog_footer_buttons a.cke_dialog_ui_button{vertical-align:top}.cke_dialog .cke_resizer_ltr{padding-left:4px}.cke_dialog .cke_resizer_rtl{padding-right:4px}.cke_dialog_ui_input_text,.cke_dialog_ui_input_password,.cke_dialog_ui_input_textarea,.cke_dialog_ui_input_select{padding:0!important}.cke_dialog_ui_checkbox_input,.cke_dialog_ui_ratio_input,.cke_btn_reset,.cke_btn_locked,.cke_btn_unlocked{border:1px solid transparent!important} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_ie8.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_ie8.css deleted file mode 100644 index 566f3c2d3..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_ie8.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{display:block} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_iequirks.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_iequirks.css deleted file mode 100644 index f8887c792..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/dialog_iequirks.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_dialog{visibility:visible}.cke_dialog_body{z-index:1;background:#eaeaea;border:1px solid #b2b2b2;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_dialog strong{font-weight:bold}.cke_dialog_title{font-weight:bold;font-size:13px;cursor:move;position:relative;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #999;padding:6px 10px;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_dialog_contents{background-color:#fff;overflow:auto;padding:15px 10px 5px 10px;margin-top:30px;border-top:1px solid #bfbfbf;-moz-border-radius:0 0 3px 3px;-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}.cke_dialog_contents_body{overflow:auto;padding:17px 10px 5px 10px;margin-top:22px}.cke_dialog_footer{text-align:right;position:relative;border:0;outline:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;-moz-border-radius:0 0 2px 2px;-webkit-border-radius:0 0 2px 2px;border-radius:0 0 2px 2px;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_rtl .cke_dialog_footer{text-align:left}.cke_hc .cke_dialog_footer{outline:0;border-top:1px solid #fff}.cke_dialog .cke_resizer{margin-top:22px}.cke_dialog .cke_resizer_rtl{margin-left:5px}.cke_dialog .cke_resizer_ltr{margin-right:5px}.cke_dialog_tabs{height:24px;display:inline-block;margin:5px 0 0;position:absolute;z-index:2;left:10px}.cke_rtl .cke_dialog_tabs{right:10px}a.cke_dialog_tab{height:16px;padding:4px 8px;margin-right:3px;display:inline-block;cursor:pointer;line-height:16px;outline:0;color:#595959;border:1px solid #bfbfbf;-moz-border-radius:3px 3px 0 0;-webkit-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;background:#d4d4d4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fafafa),to(#ededed));background-image:-moz-linear-gradient(top,#fafafa,#ededed);background-image:-webkit-linear-gradient(top,#fafafa,#ededed);background-image:-o-linear-gradient(top,#fafafa,#ededed);background-image:-ms-linear-gradient(top,#fafafa,#ededed);background-image:linear-gradient(top,#fafafa,#ededed);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#fafafa',endColorstr='#ededed')}.cke_rtl a.cke_dialog_tab{margin-right:0;margin-left:3px}a.cke_dialog_tab:hover{background:#ebebeb;background:-moz-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ebebeb),color-stop(100%,#dfdfdf));background:-webkit-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-o-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:-ms-linear-gradient(top,#ebebeb 0,#dfdfdf 100%);background:linear-gradient(to bottom,#ebebeb 0,#dfdfdf 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ebebeb',endColorstr='#dfdfdf',GradientType=0)}a.cke_dialog_tab_selected{background:#fff;color:#383838;border-bottom-color:#fff;cursor:default;filter:none}a.cke_dialog_tab_selected:hover{background:#ededed;background:-moz-linear-gradient(top,#ededed 0,#fff 100%);background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#ededed),color-stop(100%,#fff));background:-webkit-linear-gradient(top,#ededed 0,#fff 100%);background:-o-linear-gradient(top,#ededed 0,#fff 100%);background:-ms-linear-gradient(top,#ededed 0,#fff 100%);background:linear-gradient(to bottom,#ededed 0,#fff 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ededed',endColorstr='#ffffff',GradientType=0)}.cke_hc a.cke_dialog_tab:hover,.cke_hc a.cke_dialog_tab_selected{border:3px solid;padding:2px 6px}a.cke_dialog_tab_disabled{color:#bababa;cursor:default}.cke_single_page .cke_dialog_tabs{display:none}.cke_single_page .cke_dialog_contents{padding-top:5px;margin-top:0;border-top:0}.cke_dialog_close_button{background-image:url(images/close.png);background-repeat:no-repeat;background-position:50%;position:absolute;cursor:pointer;text-align:center;height:20px;width:20px;top:5px;z-index:5;opacity:.8;filter:alpha(opacity = 80)}.cke_dialog_close_button:hover{opacity:1;filter:alpha(opacity = 100)}.cke_hidpi .cke_dialog_close_button{background-image:url(images/hidpi/close.png);background-size:16px}.cke_dialog_close_button span{display:none}.cke_hc .cke_dialog_close_button span{display:inline;cursor:pointer;font-weight:bold;position:relative;top:3px}.cke_ltr .cke_dialog_close_button{right:5px}.cke_rtl .cke_dialog_close_button{left:6px}.cke_dialog_close_button{top:4px}div.cke_disabled .cke_dialog_ui_labeled_content div *{background-color:#ddd;cursor:default}.cke_dialog_ui_vbox table,.cke_dialog_ui_hbox table{margin:auto}.cke_dialog_ui_vbox_child{padding:5px 0}.cke_dialog_ui_hbox{width:100%}.cke_dialog_ui_hbox_first,.cke_dialog_ui_hbox_child,.cke_dialog_ui_hbox_last{vertical-align:top}.cke_ltr .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_ui_hbox_child{padding-right:10px}.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_child{padding-left:10px}.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_ltr .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-right:5px}.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_footer_buttons .cke_dialog_ui_hbox_child{padding-left:5px;padding-right:0}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:1px solid}textarea.cke_dialog_ui_input_textarea{overflow:auto;resize:none}input.cke_dialog_ui_input_text,input.cke_dialog_ui_input_password,textarea.cke_dialog_ui_input_textarea{background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:4px 6px;outline:0;width:100%;*width:95%;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}input.cke_dialog_ui_input_text:hover,input.cke_dialog_ui_input_password:hover,textarea.cke_dialog_ui_input_textarea:hover{border:1px solid #aeb3b9;border-top-color:#a0a6ad}input.cke_dialog_ui_input_text:focus,input.cke_dialog_ui_input_password:focus,textarea.cke_dialog_ui_input_textarea:focus,select.cke_dialog_ui_input_select:focus{outline:0;border:1px solid #139ff7;border-top-color:#1392e9}a.cke_dialog_ui_button{display:inline-block;*display:inline;*zoom:1;padding:4px 0;margin:0;text-align:center;color:#333;vertical-align:middle;cursor:pointer;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}span.cke_dialog_ui_button{padding:0 10px}a.cke_dialog_ui_button:hover{border-color:#9e9e9e;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}a.cke_dialog_ui_button:focus,a.cke_dialog_ui_button:active{border-color:#969696;outline:0;-moz-box-shadow:0 0 6px rgba(0,0,0,.4) inset;-webkit-box-shadow:0 0 6px rgba(0,0,0,.4) inset;box-shadow:0 0 6px rgba(0,0,0,.4) inset}.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button:focus,.cke_hc a.cke_dialog_ui_button:active{border:3px solid;padding-top:1px;padding-bottom:1px}.cke_hc a.cke_dialog_ui_button:hover span,.cke_hc a.cke_dialog_ui_button:focus span,.cke_hc a.cke_dialog_ui_button:active span{padding-left:10px;padding-right:10px}.cke_dialog_footer_buttons a.cke_dialog_ui_button span{color:inherit;font-size:12px;font-weight:bold;line-height:18px;padding:0 12px}a.cke_dialog_ui_button_ok{color:#fff;text-shadow:0 -1px 0 #55830c;border-color:#62a60a #62a60a #4d9200;background:#69b10b;background-image:-webkit-gradient(linear,0 0,0 100%,from(#9ad717),to(#69b10b));background-image:-webkit-linear-gradient(top,#9ad717,#69b10b);background-image:-o-linear-gradient(top,#9ad717,#69b10b);background-image:linear-gradient(to bottom,#9ad717,#69b10b);background-image:-moz-linear-gradient(top,#9ad717,#69b10b);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#9ad717',endColorstr='#69b10b')}a.cke_dialog_ui_button_ok:hover{border-color:#5b9909 #5b9909 #478500;background:#88be14;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#88be14),color-stop(100%,#5d9c0a));background:-webkit-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:-o-linear-gradient(top,#88be14 0,#5d9c0a 100%);background:linear-gradient(to bottom,#88be14 0,#5d9c0a 100%);background:-moz-linear-gradient(top,#88be14 0,#5d9c0a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#88be14',endColorstr='#5d9c0a',GradientType=0)}a.cke_dialog_ui_button span{text-shadow:0 1px 0 #fff}a.cke_dialog_ui_button_ok span{text-shadow:0 -1px 0 #55830c}span.cke_dialog_ui_button{cursor:pointer}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active,a.cke_dialog_ui_button_cancel:focus,a.cke_dialog_ui_button_cancel:active{border-width:2px;padding:3px 0}a.cke_dialog_ui_button_ok:focus,a.cke_dialog_ui_button_ok:active{border-color:#568c0a}a.cke_dialog_ui_button_ok:focus span,a.cke_dialog_ui_button_ok:active span,a.cke_dialog_ui_button_cancel:focus span,a.cke_dialog_ui_button_cancel:active span{padding:0 11px}.cke_dialog_footer_buttons{display:inline-table;margin:5px;width:auto;position:relative;vertical-align:middle}div.cke_dialog_ui_input_select{display:table}select.cke_dialog_ui_input_select{height:25px;line-height:25px;background-color:#fff;border:1px solid #c9cccf;border-top-color:#aeb3b9;padding:3px 3px 3px 6px;outline:0;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.15) inset;box-shadow:0 1px 2px rgba(0,0,0,.15) inset}.cke_dialog_ui_input_file{width:100%;height:25px}.cke_hc .cke_dialog_ui_labeled_content input:focus,.cke_hc .cke_dialog_ui_labeled_content select:focus,.cke_hc .cke_dialog_ui_labeled_content textarea:focus{outline:1px dotted}.cke_dialog .cke_dark_background{background-color:#dedede}.cke_dialog .cke_light_background{background-color:#ebebeb}.cke_dialog .cke_centered{text-align:center}.cke_dialog a.cke_btn_reset{float:right;background:url(images/refresh.png) top left no-repeat;width:16px;height:16px;border:1px none;font-size:1px}.cke_hidpi .cke_dialog a.cke_btn_reset{background-size:16px;background-image:url(images/hidpi/refresh.png)}.cke_rtl .cke_dialog a.cke_btn_reset{float:left}.cke_dialog a.cke_btn_locked,.cke_dialog a.cke_btn_unlocked{float:left;width:16px;height:16px;background-repeat:no-repeat;border:none 1px;font-size:1px}.cke_dialog a.cke_btn_locked .cke_icon{display:none}.cke_rtl .cke_dialog a.cke_btn_locked,.cke_rtl .cke_dialog a.cke_btn_unlocked{float:right}.cke_dialog a.cke_btn_locked{background-image:url(images/lock.png)}.cke_dialog a.cke_btn_unlocked{background-image:url(images/lock-open.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked,.cke_hidpi .cke_dialog a.cke_btn_locked{background-size:16px}.cke_hidpi .cke_dialog a.cke_btn_locked{background-image:url(images/hidpi/lock.png)}.cke_hidpi .cke_dialog a.cke_btn_unlocked{background-image:url(images/hidpi/lock-open.png)}.cke_dialog .cke_btn_over{border:outset 1px;cursor:pointer}.cke_dialog .ImagePreviewBox{border:2px ridge black;overflow:scroll;height:200px;width:300px;padding:2px;background-color:white}.cke_dialog .ImagePreviewBox table td{white-space:normal}.cke_dialog .ImagePreviewLoader{position:absolute;white-space:normal;overflow:hidden;height:160px;width:230px;margin:2px;padding:2px;opacity:.9;filter:alpha(opacity = 90);background-color:#e4e4e4}.cke_dialog .FlashPreviewBox{white-space:normal;border:2px ridge black;overflow:auto;height:160px;width:390px;padding:2px;background-color:white}.cke_dialog .cke_pastetext{width:346px;height:170px}.cke_dialog .cke_pastetext textarea{width:340px;height:170px;resize:none}.cke_dialog iframe.cke_pasteframe{width:346px;height:130px;background-color:white;border:1px solid #aeb3b9;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px}.cke_dialog .cke_hand{cursor:pointer}.cke_disabled{color:#a0a0a0}.cke_dialog_body .cke_label{display:none}.cke_dialog_body label{display:inline;margin-bottom:auto;cursor:default}.cke_dialog_body label.cke_required{font-weight:bold}a.cke_smile{overflow:hidden;display:block;text-align:center;padding:.3em 0}a.cke_smile img{vertical-align:middle}a.cke_specialchar{cursor:inherit;display:block;height:1.25em;padding:.2em .3em;text-align:center}a.cke_smile,a.cke_specialchar{border:1px solid transparent}a.cke_smile:hover,a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:hover,a.cke_specialchar:focus,a.cke_specialchar:active{background:#fff;outline:0}a.cke_smile:hover,a.cke_specialchar:hover{border-color:#888}a.cke_smile:focus,a.cke_smile:active,a.cke_specialchar:focus,a.cke_specialchar:active{border-color:#139ff7}.cke_dialog_contents a.colorChooser{display:block;margin-top:6px;margin-left:10px;width:80px}.cke_rtl .cke_dialog_contents a.colorChooser{margin-right:10px}.cke_dialog_ui_checkbox_input:focus,.cke_dialog_ui_radio_input:focus,.cke_btn_over{outline:1px dotted #696969}.cke_iframe_shim{display:block;position:absolute;top:0;left:0;z-index:-1;filter:alpha(opacity = 0);width:100%;height:100%}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{padding-right:2px}.cke_rtl div.cke_dialog_ui_input_text,.cke_rtl div.cke_dialog_ui_input_password{padding-left:2px}.cke_rtl div.cke_dialog_ui_input_text{padding-right:1px}.cke_rtl .cke_dialog_ui_vbox_child,.cke_rtl .cke_dialog_ui_hbox_child,.cke_rtl .cke_dialog_ui_hbox_first,.cke_rtl .cke_dialog_ui_hbox_last{padding-right:2px!important}.cke_hc .cke_dialog_title,.cke_hc .cke_dialog_footer,.cke_hc a.cke_dialog_tab,.cke_hc a.cke_dialog_ui_button,.cke_hc a.cke_dialog_ui_button:hover,.cke_hc a.cke_dialog_ui_button_ok,.cke_hc a.cke_dialog_ui_button_ok:hover{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_hc div.cke_dialog_ui_input_text,.cke_hc div.cke_dialog_ui_input_password,.cke_hc div.cke_dialog_ui_input_textarea,.cke_hc div.cke_dialog_ui_input_select,.cke_hc div.cke_dialog_ui_input_file{border:0}.cke_dialog_footer{filter:""} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor.css deleted file mode 100644 index 2189726ae..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_gecko.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_gecko.css deleted file mode 100644 index f286b6de9..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_gecko.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}.cke_bottom{padding-bottom:3px}.cke_combo_text{margin-bottom:-1px;margin-top:1px}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_ie.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_ie.css deleted file mode 100644 index 88829a3d8..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_ie.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_ie7.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_ie7.css deleted file mode 100644 index c17e8ea7c..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_ie7.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_button,.cke_rtl .cke_button *,.cke_rtl .cke_combo,.cke_rtl .cke_combo *,.cke_rtl .cke_path_item,.cke_rtl .cke_path_item *,.cke_rtl .cke_path_empty{float:none}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_combo_button,.cke_rtl .cke_combo_button *,.cke_rtl .cke_button,.cke_rtl .cke_button_icon{display:inline-block;vertical-align:top}.cke_toolbox{display:inline-block;padding-bottom:5px;height:100%}.cke_rtl .cke_toolbox{padding-bottom:0}.cke_toolbar{margin-bottom:5px}.cke_rtl .cke_toolbar{margin-bottom:0}.cke_toolgroup{height:26px}.cke_toolgroup,.cke_combo{position:relative}a.cke_button{float:none;vertical-align:top}.cke_toolbar_separator{display:inline-block;float:none;vertical-align:top;background-color:#c0c0c0}.cke_toolbox_collapser .cke_arrow{margin-top:0}.cke_toolbox_collapser .cke_arrow{border-width:4px}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{border-width:3px}.cke_rtl .cke_button_arrow{padding-top:8px;margin-right:2px}.cke_rtl .cke_combo_inlinelabel{display:table-cell;vertical-align:middle}.cke_menubutton{display:block;height:24px}.cke_menubutton_inner{display:block;position:relative}.cke_menubutton_icon{height:16px;width:16px}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:inline-block}.cke_menubutton_label{width:auto;vertical-align:top;line-height:24px;height:24px;margin:0 10px 0 0}.cke_menuarrow{width:5px;height:6px;padding:0;position:absolute;right:8px;top:10px;background-position:0 0}.cke_rtl .cke_menubutton_icon{position:absolute;right:0;top:0}.cke_rtl .cke_menubutton_label{float:right;clear:both;margin:0 24px 0 10px}.cke_hc .cke_rtl .cke_menubutton_label{margin-right:0}.cke_rtl .cke_menuarrow{left:8px;right:auto;background-position:0 -24px}.cke_hc .cke_menuarrow{top:5px;padding:0 5px}.cke_rtl input.cke_dialog_ui_input_text,.cke_rtl input.cke_dialog_ui_input_password{position:relative}.cke_wysiwyg_div{padding-top:0!important;padding-bottom:0!important}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_ie8.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_ie8.css deleted file mode 100644 index ff669e823..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_ie8.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_toolbox_collapser .cke_arrow{border-width:4px}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{border-width:3px}.cke_toolbox_collapser .cke_arrow{margin-top:0}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_iequirks.css b/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_iequirks.css deleted file mode 100644 index ecb38ff50..000000000 --- a/rainloop/v/0.0.0/static/ckeditor/skins/moono/editor_iequirks.css +++ /dev/null @@ -1,5 +0,0 @@ -/* -Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. -For licensing, see LICENSE.md or http://ckeditor.com/license -*/ -.cke_reset{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none}.cke_reset_all,.cke_reset_all *{margin:0;padding:0;border:0;background:transparent;text-decoration:none;width:auto;height:auto;vertical-align:baseline;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;position:static;-webkit-transition:none;-moz-transition:none;-ms-transition:none;transition:none;border-collapse:collapse;font:normal normal normal 12px Arial,Helvetica,Tahoma,Verdana,Sans-Serif;color:#000;text-align:left;white-space:nowrap;cursor:auto;float:none}.cke_reset_all .cke_rtl *{text-align:right}.cke_reset_all iframe{vertical-align:inherit}.cke_reset_all textarea{white-space:pre}.cke_reset_all textarea,.cke_reset_all input[type="text"],.cke_reset_all input[type="password"]{cursor:text}.cke_reset_all textarea[disabled],.cke_reset_all input[type="text"][disabled],.cke_reset_all input[type="password"][disabled]{cursor:default}.cke_reset_all fieldset{padding:10px;border:2px groove #e0dfe3}.cke_reset_all select{box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box}.cke_chrome{display:block;border:1px solid #b6b6b6;padding:0;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_inner{display:block;-webkit-touch-callout:none;background:#fff;padding:0}.cke_float{border:0}.cke_float .cke_inner{padding-bottom:0}.cke_top,.cke_contents,.cke_bottom{display:block;overflow:hidden}.cke_top{border-bottom:1px solid #b6b6b6;padding:6px 8px 2px;white-space:normal;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_float .cke_top{border:1px solid #b6b6b6;border-bottom-color:#999}.cke_bottom{padding:6px 8px 2px;position:relative;border-top:1px solid #bfbfbf;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#cfd1cf));background-image:-moz-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-webkit-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-o-linear-gradient(top,#ebebeb,#cfd1cf);background-image:-ms-linear-gradient(top,#ebebeb,#cfd1cf);background-image:linear-gradient(top,#ebebeb,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ebebeb',endColorstr='#cfd1cf')}.cke_browser_ios .cke_contents{overflow-y:auto;-webkit-overflow-scrolling:touch}.cke_resizer{width:0;height:0;overflow:hidden;width:0;height:0;overflow:hidden;border-width:10px 10px 0 0;border-color:transparent #666 transparent transparent;border-style:dashed solid dashed dashed;font-size:0;vertical-align:bottom;margin-top:6px;margin-bottom:2px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.3);-webkit-box-shadow:0 1px 0 rgba(255,255,255,.3);box-shadow:0 1px 0 rgba(255,255,255,.3)}.cke_hc .cke_resizer{font-size:15px;width:auto;height:auto;border-width:0}.cke_resizer_ltr{cursor:se-resize;float:right;margin-right:-4px}.cke_resizer_rtl{border-width:10px 0 0 10px;border-color:transparent transparent transparent #a5a5a5;border-style:dashed dashed dashed solid;cursor:sw-resize;float:left;margin-left:-4px;right:auto}.cke_wysiwyg_div{display:block;height:100%;overflow:auto;padding:0 8px;outline-style:none;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.cke_panel{visibility:visible;width:120px;height:100px;overflow:hidden;background-color:#fff;border:1px solid #b6b6b6;border-bottom-color:#999;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 0 3px rgba(0,0,0,.15);-webkit-box-shadow:0 0 3px rgba(0,0,0,.15);box-shadow:0 0 3px rgba(0,0,0,.15)}.cke_menu_panel{padding:0;margin:0}.cke_combopanel{width:150px;height:170px}.cke_panel_frame{width:100%;height:100%;font-size:12px;overflow:auto;overflow-x:hidden}.cke_panel_container{overflow-y:auto;overflow-x:hidden}.cke_panel_list{list-style-type:none;margin:3px;padding:0;white-space:nowrap}.cke_panel_listItem{margin:0;padding-bottom:1px}.cke_panel_listItem a{padding:3px 4px;display:block;border:1px solid #fff;color:inherit!important;text-decoration:none;overflow:hidden;text-overflow:ellipsis;-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px}* html .cke_panel_listItem a{width:100%;color:#000}*:first-child+html .cke_panel_listItem a{color:#000}.cke_panel_listItem.cke_selected a{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_panel_listItem a:hover,.cke_panel_listItem a:focus,.cke_panel_listItem a:active{border-color:#dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_hc .cke_panel_listItem a{border-style:none}.cke_hc .cke_panel_listItem a:hover,.cke_hc .cke_panel_listItem a:focus,.cke_hc .cke_panel_listItem a:active{border:2px solid;padding:1px 2px}.cke_panel_grouptitle{cursor:default;font-size:11px;font-weight:bold;white-space:nowrap;margin:0;padding:4px 6px;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.75);border-bottom:1px solid #b6b6b6;-moz-border-radius:2px 2px 0 0;-webkit-border-radius:2px 2px 0 0;border-radius:2px 2px 0 0;-moz-box-shadow:0 1px 0 #fff inset;-webkit-box-shadow:0 1px 0 #fff inset;box-shadow:0 1px 0 #fff inset;background:#cfd1cf;background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#cfd1cf));background-image:-moz-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-webkit-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-o-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:-ms-linear-gradient(top,#f5f5f5,#cfd1cf);background-image:linear-gradient(top,#f5f5f5,#cfd1cf);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f5f5f5',endColorstr='#cfd1cf')}.cke_panel_listItem p,.cke_panel_listItem h1,.cke_panel_listItem h2,.cke_panel_listItem h3,.cke_panel_listItem h4,.cke_panel_listItem h5,.cke_panel_listItem h6,.cke_panel_listItem pre{margin-top:0;margin-bottom:0}.cke_colorblock{padding:3px;font-size:11px;font-family:'Microsoft Sans Serif',Tahoma,Arial,Verdana,Sans-Serif}.cke_colorblock,.cke_colorblock a{text-decoration:none;color:#000}span.cke_colorbox{width:10px;height:10px;border:#808080 1px solid;float:left}.cke_rtl span.cke_colorbox{float:right}a.cke_colorbox{border:#fff 1px solid;padding:2px;float:left;width:12px;height:12px}.cke_rtl a.cke_colorbox{float:right}a:hover.cke_colorbox,a:focus.cke_colorbox,a:active.cke_colorbox{border:#b6b6b6 1px solid;background-color:#e5e5e5}a.cke_colorauto,a.cke_colormore{border:#fff 1px solid;padding:2px;display:block;cursor:pointer}a:hover.cke_colorauto,a:hover.cke_colormore,a:focus.cke_colorauto,a:focus.cke_colormore,a:active.cke_colorauto,a:active.cke_colormore{border:#b6b6b6 1px solid;background-color:#e5e5e5}.cke_toolbar{float:left}.cke_rtl .cke_toolbar{float:right}.cke_toolgroup{float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_hc .cke_toolgroup{border:0;margin-right:10px;margin-bottom:10px}.cke_rtl .cke_toolgroup{float:right;margin-left:6px;margin-right:0}a.cke_button{display:inline-block;height:18px;padding:4px 6px;outline:0;cursor:default;float:left;border:0}.cke_ltr .cke_button:last-child,.cke_rtl .cke_button:first-child{-moz-border-radius:0 2px 2px 0;-webkit-border-radius:0 2px 2px 0;border-radius:0 2px 2px 0}.cke_ltr .cke_button:first-child,.cke_rtl .cke_button:last-child{-moz-border-radius:2px 0 0 2px;-webkit-border-radius:2px 0 0 2px;border-radius:2px 0 0 2px}.cke_rtl .cke_button{float:right}.cke_hc .cke_button{border:1px solid black;padding:3px 5px;margin:-2px 4px 0 -2px}.cke_button_on{-moz-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 5px rgba(0,0,0,.6) inset,0 1px 0 rgba(0,0,0,.2);background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc a.cke_button_disabled:hover,.cke_hc a.cke_button_disabled:focus,.cke_hc a.cke_button_disabled:active{border-width:3px;padding:1px 3px}.cke_button_disabled .cke_button_icon{opacity:.3}.cke_hc .cke_button_disabled{opacity:.5}a.cke_button_on:hover,a.cke_button_on:focus,a.cke_button_on:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}a.cke_button_off:hover,a.cke_button_off:focus,a.cke_button_off:active,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{-moz-box-shadow:0 0 1px rgba(0,0,0,.3) inset;-webkit-box-shadow:0 0 1px rgba(0,0,0,.3) inset;box-shadow:0 0 1px rgba(0,0,0,.3) inset;background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_button_icon{cursor:inherit;background-repeat:no-repeat;margin-top:1px;width:16px;height:16px;float:left;display:inline-block}.cke_rtl .cke_button_icon{float:right}.cke_hc .cke_button_icon{display:none}.cke_button_label{display:none;padding-left:3px;margin-top:1px;line-height:17px;vertical-align:middle;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5)}.cke_rtl .cke_button_label{padding-right:3px;padding-left:0;float:right}.cke_hc .cke_button_label{padding:0;display:inline-block;font-size:12px}.cke_button_arrow{display:inline-block;margin:8px 0 0 1px;width:0;height:0;cursor:default;vertical-align:top;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_rtl .cke_button_arrow{margin-right:5px;margin-left:0}.cke_hc .cke_button_arrow{font-size:10px;margin:3px -2px 0 3px;width:auto;border:0}.cke_toolbar_separator{float:left;background-color:#c0c0c0;background-color:rgba(0,0,0,.2);margin:5px 2px 0;height:18px;width:1px;-webkit-box-shadow:1px 0 1px rgba(255,255,255,.5);-moz-box-shadow:1px 0 1px rgba(255,255,255,.5);box-shadow:1px 0 1px rgba(255,255,255,.5)}.cke_rtl .cke_toolbar_separator{float:right;-webkit-box-shadow:-1px 0 1px rgba(255,255,255,.1);-moz-box-shadow:-1px 0 1px rgba(255,255,255,.1);box-shadow:-1px 0 1px rgba(255,255,255,.1)}.cke_hc .cke_toolbar_separator{width:0;border-left:1px solid;margin:1px 5px 0 0}.cke_toolbar_break{display:block;clear:left}.cke_rtl .cke_toolbar_break{clear:right}.cke_toolbox_collapser{width:12px;height:11px;float:right;margin:11px 0 0;font-size:0;cursor:default;text-align:center;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_toolbox_collapser:hover{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc')}.cke_toolbox_collapser.cke_toolbox_collapser_min{margin:0 2px 4px}.cke_rtl .cke_toolbox_collapser{float:left}.cke_toolbox_collapser .cke_arrow{display:inline-block;height:0;width:0;font-size:0;margin-top:1px;border-left:3px solid transparent;border-right:3px solid transparent;border-bottom:3px solid #474747;border-top:3px solid transparent}.cke_toolbox_collapser.cke_toolbox_collapser_min .cke_arrow{margin-top:4px;border-bottom-color:transparent;border-top-color:#474747}.cke_hc .cke_toolbox_collapser .cke_arrow{font-size:8px;width:auto;border:0;margin-top:0;margin-right:2px}.cke_menubutton{display:block}.cke_menuitem span{cursor:default}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#d3d3d3;display:block}.cke_hc .cke_menubutton{padding:2px}.cke_hc .cke_menubutton:hover,.cke_hc .cke_menubutton:focus,.cke_hc .cke_menubutton:active{border:2px solid;padding:0}.cke_menubutton_inner{display:table-row}.cke_menubutton_icon,.cke_menubutton_label,.cke_menuarrow{display:table-cell}.cke_menubutton_icon{background-color:#d7d8d7;opacity:.70;filter:alpha(opacity=70);padding:4px}.cke_hc .cke_menubutton_icon{height:16px;width:0;padding:4px 0}.cke_menubutton:hover .cke_menubutton_icon,.cke_menubutton:focus .cke_menubutton_icon,.cke_menubutton:active .cke_menubutton_icon{background-color:#d0d2d0}.cke_menubutton_disabled:hover .cke_menubutton_icon,.cke_menubutton_disabled:focus .cke_menubutton_icon,.cke_menubutton_disabled:active .cke_menubutton_icon{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_label{padding:0 5px;background-color:transparent;width:100%;vertical-align:middle}.cke_menubutton_disabled .cke_menubutton_label{opacity:.3;filter:alpha(opacity=30)}.cke_menubutton_on{border:1px solid #dedede;background-color:#f2f2f2;-moz-box-shadow:0 0 2px rgba(0,0,0,.1) inset;-webkit-box-shadow:0 0 2px rgba(0,0,0,.1) inset;box-shadow:0 0 2px rgba(0,0,0,.1) inset}.cke_menubutton_on .cke_menubutton_icon{padding-right:3px}.cke_menubutton:hover,.cke_menubutton:focus,.cke_menubutton:active{background-color:#eff0ef}.cke_panel_frame .cke_menubutton_label{display:none}.cke_menuseparator{background-color:#d3d3d3;height:1px;filter:alpha(opacity=70);opacity:.70}.cke_menuarrow{background-image:url(images/arrow.png);background-position:0 10px;background-repeat:no-repeat;padding:0 5px}.cke_rtl .cke_menuarrow{background-position:5px -13px;background-repeat:no-repeat}.cke_menuarrow span{display:none}.cke_hc .cke_menuarrow span{vertical-align:middle;display:inline}.cke_combo{display:inline-block;float:left}.cke_rtl .cke_combo{float:right}.cke_hc .cke_combo{margin-top:-2px}.cke_combo_label{display:none;float:left;line-height:26px;vertical-align:top;margin-right:5px}.cke_rtl .cke_combo_label{float:right;margin-left:5px;margin-right:0}.cke_combo_button{cursor:default;display:inline-block;float:left;margin:0 6px 5px 0;border:1px solid #a6a6a6;border-bottom-color:#979797;-moz-border-radius:3px;-webkit-border-radius:3px;border-radius:3px;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 0 2px rgba(255,255,255,.15) inset,0 1px 0 rgba(255,255,255,.15) inset;background:#e4e4e4;background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e4e4e4));background-image:-moz-linear-gradient(top,#fff,#e4e4e4);background-image:-webkit-linear-gradient(top,#fff,#e4e4e4);background-image:-o-linear-gradient(top,#fff,#e4e4e4);background-image:-ms-linear-gradient(top,#fff,#e4e4e4);background-image:linear-gradient(top,#fff,#e4e4e4);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffff',endColorstr='#e4e4e4')}.cke_combo_off a.cke_combo_button:hover,.cke_combo_off a.cke_combo_button:focus{background:#ccc;background-image:-webkit-gradient(linear,left top,left bottom,from(#f2f2f2),to(#ccc));background-image:-moz-linear-gradient(top,#f2f2f2,#ccc);background-image:-webkit-linear-gradient(top,#f2f2f2,#ccc);background-image:-o-linear-gradient(top,#f2f2f2,#ccc);background-image:-ms-linear-gradient(top,#f2f2f2,#ccc);background-image:linear-gradient(top,#f2f2f2,#ccc);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#f2f2f2',endColorstr='#cccccc');outline:0}.cke_combo_off a.cke_combo_button:active,.cke_combo_on a.cke_combo_button{border:1px solid #777;-moz-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;-webkit-box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;box-shadow:0 1px 0 rgba(255,255,255,.5),0 1px 5px rgba(0,0,0,.6) inset;background:#b5b5b5;background-image:-webkit-gradient(linear,left top,left bottom,from(#aaa),to(#cacaca));background-image:-moz-linear-gradient(top,#aaa,#cacaca);background-image:-webkit-linear-gradient(top,#aaa,#cacaca);background-image:-o-linear-gradient(top,#aaa,#cacaca);background-image:-ms-linear-gradient(top,#aaa,#cacaca);background-image:linear-gradient(top,#aaa,#cacaca);filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#aaaaaa',endColorstr='#cacaca')}.cke_combo_on a.cke_combo_button:hover,.cke_combo_on a.cke_combo_button:focus,.cke_combo_on a.cke_combo_button:active{-moz-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);-webkit-box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2);box-shadow:0 1px 6px rgba(0,0,0,.7) inset,0 1px 0 rgba(0,0,0,.2)}.cke_rtl .cke_combo_button{float:right;margin-left:5px;margin-right:0}.cke_hc a.cke_combo_button{padding:3px}.cke_hc .cke_combo_on a.cke_combo_button,.cke_hc .cke_combo_off a.cke_combo_button:hover,.cke_hc .cke_combo_off a.cke_combo_button:focus,.cke_hc .cke_combo_off a.cke_combo_button:active{border-width:3px;padding:1px}.cke_combo_text{line-height:26px;padding-left:10px;text-overflow:ellipsis;overflow:hidden;float:left;cursor:default;color:#474747;text-shadow:0 1px 0 rgba(255,255,255,.5);width:60px}.cke_rtl .cke_combo_text{float:right;text-align:right;padding-left:0;padding-right:10px}.cke_hc .cke_combo_text{line-height:18px;font-size:12px}.cke_combo_open{cursor:default;display:inline-block;font-size:0;height:19px;line-height:17px;margin:1px 7px 1px;width:5px}.cke_hc .cke_combo_open{height:12px}.cke_combo_arrow{cursor:default;margin:11px 0 0;float:left;height:0;width:0;font-size:0;border-left:3px solid transparent;border-right:3px solid transparent;border-top:3px solid #474747}.cke_hc .cke_combo_arrow{font-size:10px;width:auto;border:0;margin-top:3px}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{opacity:.3}.cke_path{float:left;margin:-2px 0 2px}.cke_path_item,.cke_path_empty{display:inline-block;float:left;padding:3px 4px;margin-right:2px;cursor:default;text-decoration:none;outline:0;border:0;color:#4c4c4c;text-shadow:0 1px 0 #fff;font-weight:bold;font-size:11px}.cke_rtl .cke_path,.cke_rtl .cke_path_item,.cke_rtl .cke_path_empty{float:right}a.cke_path_item:hover,a.cke_path_item:focus,a.cke_path_item:active{background-color:#bfbfbf;color:#333;text-shadow:0 1px 0 rgba(255,255,255,.5);-moz-border-radius:2px;-webkit-border-radius:2px;border-radius:2px;-moz-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);-webkit-box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5);box-shadow:0 0 4px rgba(0,0,0,.5) inset,0 1px 0 rgba(255,255,255,.5)}.cke_hc a.cke_path_item:hover,.cke_hc a.cke_path_item:focus,.cke_hc a.cke_path_item:active{border:2px solid;padding:1px 2px}.cke_button__source_label,.cke_button__sourcedialog_label{display:inline}.cke_combo__fontsize .cke_combo_text{width:30px}.cke_combopanel__fontsize{width:120px}.cke_source{font-family:'Courier New',Monospace;font-size:small;background-color:#fff;white-space:pre}.cke_wysiwyg_frame,.cke_wysiwyg_div{background-color:#fff}.cke_chrome{visibility:inherit}.cke_voice_label{display:none}legend.cke_voice_label{display:none}a.cke_button_disabled,a.cke_button_disabled:hover,a.cke_button_disabled:focus,a.cke_button_disabled:active{filter:alpha(opacity = 30)}.cke_button_disabled .cke_button_icon{filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#00ffffff,endColorstr=#00ffffff)}.cke_button_off:hover,.cke_button_off:focus,.cke_button_off:active{filter:alpha(opacity = 100)}.cke_combo_disabled .cke_combo_inlinelabel,.cke_combo_disabled .cke_combo_open{filter:alpha(opacity = 30)}.cke_toolbox_collapser{border:1px solid #a6a6a6}.cke_toolbox_collapser .cke_arrow{margin-top:1px}.cke_hc .cke_top,.cke_hc .cke_bottom,.cke_hc .cke_combo_button,.cke_hc a.cke_combo_button:hover,.cke_hc a.cke_combo_button:focus,.cke_hc .cke_toolgroup,.cke_hc .cke_button_on,.cke_hc a.cke_button_off:hover,.cke_hc a.cke_button_off:focus,.cke_hc a.cke_button_off:active,.cke_hc .cke_toolbox_collapser,.cke_hc .cke_toolbox_collapser:hover,.cke_hc .cke_panel_grouptitle{filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.cke_top,.cke_contents,.cke_bottom{width:100%}.cke_button_arrow{font-size:0}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_button,.cke_rtl .cke_button *,.cke_rtl .cke_combo,.cke_rtl .cke_combo *,.cke_rtl .cke_path_item,.cke_rtl .cke_path_item *,.cke_rtl .cke_path_empty{float:none}.cke_rtl .cke_toolgroup,.cke_rtl .cke_toolbar_separator,.cke_rtl .cke_combo_button,.cke_rtl .cke_combo_button *,.cke_rtl .cke_button,.cke_rtl .cke_button_icon{display:inline-block;vertical-align:top}.cke_rtl .cke_button_icon{float:none}.cke_resizer{width:10px}.cke_source{white-space:normal}.cke_bottom{position:static}.cke_colorbox{font-size:0}.cke_button__base64image_icon {background: url(icons.png) no-repeat 0 -0px !important;}.cke_button__bold_icon {background: url(icons.png) no-repeat 0 -24px !important;}.cke_button__italic_icon {background: url(icons.png) no-repeat 0 -48px !important;}.cke_button__strike_icon {background: url(icons.png) no-repeat 0 -72px !important;}.cke_button__subscript_icon {background: url(icons.png) no-repeat 0 -96px !important;}.cke_button__superscript_icon {background: url(icons.png) no-repeat 0 -120px !important;}.cke_button__underline_icon {background: url(icons.png) no-repeat 0 -144px !important;}.cke_button__blockquote_icon {background: url(icons.png) no-repeat 0 -168px !important;}.cke_button__bgcolor_icon {background: url(icons.png) no-repeat 0 -192px !important;}.cke_button__textcolor_icon {background: url(icons.png) no-repeat 0 -216px !important;}.cke_button__image_icon {background: url(icons.png) no-repeat 0 -240px !important;}.cke_rtl .cke_button__copy_icon, .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -264px !important;}.cke_ltr .cke_button__copy_icon {background: url(icons.png) no-repeat 0 -288px !important;}.cke_rtl .cke_button__cut_icon, .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -312px !important;}.cke_ltr .cke_button__cut_icon {background: url(icons.png) no-repeat 0 -336px !important;}.cke_rtl .cke_button__paste_icon, .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -360px !important;}.cke_ltr .cke_button__paste_icon {background: url(icons.png) no-repeat 0 -384px !important;}.cke_rtl .cke_button__indent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -408px !important;}.cke_ltr .cke_button__indent_icon {background: url(icons.png) no-repeat 0 -432px !important;}.cke_rtl .cke_button__outdent_icon, .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -456px !important;}.cke_ltr .cke_button__outdent_icon {background: url(icons.png) no-repeat 0 -480px !important;}.cke_rtl .cke_button__anchor_icon, .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -504px !important;}.cke_ltr .cke_button__anchor_icon {background: url(icons.png) no-repeat 0 -528px !important;}.cke_button__link_icon {background: url(icons.png) no-repeat 0 -552px !important;}.cke_button__unlink_icon {background: url(icons.png) no-repeat 0 -576px !important;}.cke_rtl .cke_button__bulletedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -600px !important;}.cke_ltr .cke_button__bulletedlist_icon {background: url(icons.png) no-repeat 0 -624px !important;}.cke_rtl .cke_button__numberedlist_icon, .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -648px !important;}.cke_ltr .cke_button__numberedlist_icon {background: url(icons.png) no-repeat 0 -672px !important;}.cke_button__removeformat_icon {background: url(icons.png) no-repeat 0 -696px !important;}.cke_button__selectall_icon {background: url(icons.png) no-repeat 0 -720px !important;}.cke_rtl .cke_button__source_icon, .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons.png) no-repeat 0 -744px !important;}.cke_ltr .cke_button__source_icon {background: url(icons.png) no-repeat 0 -768px !important;}.cke_rtl .cke_button__redo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -792px !important;}.cke_ltr .cke_button__redo_icon {background: url(icons.png) no-repeat 0 -816px !important;}.cke_rtl .cke_button__undo_icon, .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -840px !important;}.cke_ltr .cke_button__undo_icon {background: url(icons.png) no-repeat 0 -864px !important;}.cke_hidpi .cke_button__base64image_icon {background: url(icons_hidpi.png) no-repeat 0 -0px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bold_icon {background: url(icons_hidpi.png) no-repeat 0 -24px !important;background-size: 16px !important;}.cke_hidpi .cke_button__italic_icon {background: url(icons_hidpi.png) no-repeat 0 -48px !important;background-size: 16px !important;}.cke_hidpi .cke_button__strike_icon {background: url(icons_hidpi.png) no-repeat 0 -72px !important;background-size: 16px !important;}.cke_hidpi .cke_button__subscript_icon {background: url(icons_hidpi.png) no-repeat 0 -96px !important;background-size: 16px !important;}.cke_hidpi .cke_button__superscript_icon {background: url(icons_hidpi.png) no-repeat 0 -120px !important;background-size: 16px !important;}.cke_hidpi .cke_button__underline_icon {background: url(icons_hidpi.png) no-repeat 0 -144px !important;background-size: 16px !important;}.cke_hidpi .cke_button__blockquote_icon {background: url(icons_hidpi.png) no-repeat 0 -168px !important;background-size: 16px !important;}.cke_hidpi .cke_button__bgcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -192px !important;background-size: 16px !important;}.cke_hidpi .cke_button__textcolor_icon {background: url(icons_hidpi.png) no-repeat 0 -216px !important;background-size: 16px !important;}.cke_hidpi .cke_button__image_icon {background: url(icons_hidpi.png) no-repeat 0 -240px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__copy_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -264px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__copy_icon,.cke_ltr.cke_hidpi .cke_button__copy_icon {background: url(icons_hidpi.png) no-repeat 0 -288px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__cut_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -312px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__cut_icon,.cke_ltr.cke_hidpi .cke_button__cut_icon {background: url(icons_hidpi.png) no-repeat 0 -336px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__paste_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -360px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__paste_icon,.cke_ltr.cke_hidpi .cke_button__paste_icon {background: url(icons_hidpi.png) no-repeat 0 -384px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__indent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -408px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__indent_icon,.cke_ltr.cke_hidpi .cke_button__indent_icon {background: url(icons_hidpi.png) no-repeat 0 -432px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__outdent_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -456px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__outdent_icon,.cke_ltr.cke_hidpi .cke_button__outdent_icon {background: url(icons_hidpi.png) no-repeat 0 -480px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__anchor_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -504px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__anchor_icon,.cke_ltr.cke_hidpi .cke_button__anchor_icon {background: url(icons_hidpi.png) no-repeat 0 -528px !important;background-size: 16px !important;}.cke_hidpi .cke_button__link_icon {background: url(icons_hidpi.png) no-repeat 0 -552px !important;background-size: 16px !important;}.cke_hidpi .cke_button__unlink_icon {background: url(icons_hidpi.png) no-repeat 0 -576px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__bulletedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -600px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__bulletedlist_icon,.cke_ltr.cke_hidpi .cke_button__bulletedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -624px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__numberedlist_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -648px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__numberedlist_icon,.cke_ltr.cke_hidpi .cke_button__numberedlist_icon {background: url(icons_hidpi.png) no-repeat 0 -672px !important;background-size: 16px !important;}.cke_hidpi .cke_button__removeformat_icon {background: url(icons_hidpi.png) no-repeat 0 -696px !important;background-size: 16px !important;}.cke_hidpi .cke_button__selectall_icon {background: url(icons_hidpi.png) no-repeat 0 -720px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__source_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -744px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__source_icon,.cke_ltr.cke_hidpi .cke_button__source_icon {background: url(icons_hidpi.png) no-repeat 0 -768px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__redo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -792px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__redo_icon,.cke_ltr.cke_hidpi .cke_button__redo_icon {background: url(icons_hidpi.png) no-repeat 0 -816px !important;background-size: 16px !important;}.cke_rtl.cke_hidpi .cke_button__undo_icon, .cke_hidpi .cke_mixed_dir_content .cke_rtl .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -840px !important;background-size: 16px !important;}.cke_hidpi .cke_ltr .cke_button__undo_icon,.cke_ltr.cke_hidpi .cke_button__undo_icon {background: url(icons_hidpi.png) no-repeat 0 -864px !important;background-size: 16px !important;} \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/icons.png b/rainloop/v/0.0.0/static/ckeditor/skins/moono/icons.png deleted file mode 100644 index 311efb048..000000000 Binary files a/rainloop/v/0.0.0/static/ckeditor/skins/moono/icons.png and /dev/null differ diff --git a/rainloop/v/0.0.0/static/ckeditor/skins/moono/icons_hidpi.png b/rainloop/v/0.0.0/static/ckeditor/skins/moono/icons_hidpi.png deleted file mode 100644 index ccf58365d..000000000 Binary files a/rainloop/v/0.0.0/static/ckeditor/skins/moono/icons_hidpi.png and /dev/null differ diff --git a/rainloop/v/0.0.0/static/ckeditor/styles.js b/rainloop/v/0.0.0/static/ckeditor/styles.js index 18e4316b0..38bb6804b 100644 --- a/rainloop/v/0.0.0/static/ckeditor/styles.js +++ b/rainloop/v/0.0.0/static/ckeditor/styles.js @@ -1,111 +1,111 @@ -/** - * Copyright (c) 2003-2014, CKSource - Frederico Knabben. All rights reserved. - * For licensing, see LICENSE.md or http://ckeditor.com/license - */ - -// This file contains style definitions that can be used by CKEditor plugins. -// -// The most common use for it is the "stylescombo" plugin, which shows a combo -// in the editor toolbar, containing all styles. Other plugins instead, like -// the div plugin, use a subset of the styles on their feature. -// -// If you don't have plugins that depend on this file, you can simply ignore it. -// Otherwise it is strongly recommended to customize this file to match your -// website requirements and design properly. - -CKEDITOR.stylesSet.add( 'default', [ - /* Block Styles */ - - // These styles are already available in the "Format" combo ("format" plugin), - // so they are not needed here by default. You may enable them to avoid - // placing the "Format" combo in the toolbar, maintaining the same features. - /* - { name: 'Paragraph', element: 'p' }, - { name: 'Heading 1', element: 'h1' }, - { name: 'Heading 2', element: 'h2' }, - { name: 'Heading 3', element: 'h3' }, - { name: 'Heading 4', element: 'h4' }, - { name: 'Heading 5', element: 'h5' }, - { name: 'Heading 6', element: 'h6' }, - { name: 'Preformatted Text',element: 'pre' }, - { name: 'Address', element: 'address' }, - */ - - { name: 'Italic Title', element: 'h2', styles: { 'font-style': 'italic' } }, - { name: 'Subtitle', element: 'h3', styles: { 'color': '#aaa', 'font-style': 'italic' } }, - { - name: 'Special Container', - element: 'div', - styles: { - padding: '5px 10px', - background: '#eee', - border: '1px solid #ccc' - } - }, - - /* Inline Styles */ - - // These are core styles available as toolbar buttons. You may opt enabling - // some of them in the Styles combo, removing them from the toolbar. - // (This requires the "stylescombo" plugin) - /* - { name: 'Strong', element: 'strong', overrides: 'b' }, - { name: 'Emphasis', element: 'em' , overrides: 'i' }, - { name: 'Underline', element: 'u' }, - { name: 'Strikethrough', element: 'strike' }, - { name: 'Subscript', element: 'sub' }, - { name: 'Superscript', element: 'sup' }, - */ - - { name: 'Marker', element: 'span', attributes: { 'class': 'marker' } }, - - { name: 'Big', element: 'big' }, - { name: 'Small', element: 'small' }, - { name: 'Typewriter', element: 'tt' }, - - { name: 'Computer Code', element: 'code' }, - { name: 'Keyboard Phrase', element: 'kbd' }, - { name: 'Sample Text', element: 'samp' }, - { name: 'Variable', element: 'var' }, - - { name: 'Deleted Text', element: 'del' }, - { name: 'Inserted Text', element: 'ins' }, - - { name: 'Cited Work', element: 'cite' }, - { name: 'Inline Quotation', element: 'q' }, - - { name: 'Language: RTL', element: 'span', attributes: { 'dir': 'rtl' } }, - { name: 'Language: LTR', element: 'span', attributes: { 'dir': 'ltr' } }, - - /* Object Styles */ - - { - name: 'Styled image (left)', - element: 'img', - attributes: { 'class': 'left' } - }, - - { - name: 'Styled image (right)', - element: 'img', - attributes: { 'class': 'right' } - }, - - { - name: 'Compact table', - element: 'table', - attributes: { - cellpadding: '5', - cellspacing: '0', - border: '1', - bordercolor: '#ccc' - }, - styles: { - 'border-collapse': 'collapse' - } - }, - - { name: 'Borderless Table', element: 'table', styles: { 'border-style': 'hidden', 'background-color': '#E6E6FA' } }, - { name: 'Square Bulleted List', element: 'ul', styles: { 'list-style-type': 'square' } } -] ); - +/** + * Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved. + * For licensing, see LICENSE.md or http://ckeditor.com/license + */ + +// This file contains style definitions that can be used by CKEditor plugins. +// +// The most common use for it is the "stylescombo" plugin, which shows a combo +// in the editor toolbar, containing all styles. Other plugins instead, like +// the div plugin, use a subset of the styles on their feature. +// +// If you don't have plugins that depend on this file, you can simply ignore it. +// Otherwise it is strongly recommended to customize this file to match your +// website requirements and design properly. + +CKEDITOR.stylesSet.add( 'default', [ + /* Block Styles */ + + // These styles are already available in the "Format" combo ("format" plugin), + // so they are not needed here by default. You may enable them to avoid + // placing the "Format" combo in the toolbar, maintaining the same features. + /* + { name: 'Paragraph', element: 'p' }, + { name: 'Heading 1', element: 'h1' }, + { name: 'Heading 2', element: 'h2' }, + { name: 'Heading 3', element: 'h3' }, + { name: 'Heading 4', element: 'h4' }, + { name: 'Heading 5', element: 'h5' }, + { name: 'Heading 6', element: 'h6' }, + { name: 'Preformatted Text',element: 'pre' }, + { name: 'Address', element: 'address' }, + */ + + { name: 'Italic Title', element: 'h2', styles: { 'font-style': 'italic' } }, + { name: 'Subtitle', element: 'h3', styles: { 'color': '#aaa', 'font-style': 'italic' } }, + { + name: 'Special Container', + element: 'div', + styles: { + padding: '5px 10px', + background: '#eee', + border: '1px solid #ccc' + } + }, + + /* Inline Styles */ + + // These are core styles available as toolbar buttons. You may opt enabling + // some of them in the Styles combo, removing them from the toolbar. + // (This requires the "stylescombo" plugin) + /* + { name: 'Strong', element: 'strong', overrides: 'b' }, + { name: 'Emphasis', element: 'em' , overrides: 'i' }, + { name: 'Underline', element: 'u' }, + { name: 'Strikethrough', element: 'strike' }, + { name: 'Subscript', element: 'sub' }, + { name: 'Superscript', element: 'sup' }, + */ + + { name: 'Marker', element: 'span', attributes: { 'class': 'marker' } }, + + { name: 'Big', element: 'big' }, + { name: 'Small', element: 'small' }, + { name: 'Typewriter', element: 'tt' }, + + { name: 'Computer Code', element: 'code' }, + { name: 'Keyboard Phrase', element: 'kbd' }, + { name: 'Sample Text', element: 'samp' }, + { name: 'Variable', element: 'var' }, + + { name: 'Deleted Text', element: 'del' }, + { name: 'Inserted Text', element: 'ins' }, + + { name: 'Cited Work', element: 'cite' }, + { name: 'Inline Quotation', element: 'q' }, + + { name: 'Language: RTL', element: 'span', attributes: { 'dir': 'rtl' } }, + { name: 'Language: LTR', element: 'span', attributes: { 'dir': 'ltr' } }, + + /* Object Styles */ + + { + name: 'Styled image (left)', + element: 'img', + attributes: { 'class': 'left' } + }, + + { + name: 'Styled image (right)', + element: 'img', + attributes: { 'class': 'right' } + }, + + { + name: 'Compact table', + element: 'table', + attributes: { + cellpadding: '5', + cellspacing: '0', + border: '1', + bordercolor: '#ccc' + }, + styles: { + 'border-collapse': 'collapse' + } + }, + + { name: 'Borderless Table', element: 'table', styles: { 'border-style': 'hidden', 'background-color': '#E6E6FA' } }, + { name: 'Square Bulleted List', element: 'ul', styles: { 'list-style-type': 'square' } } +] ); + diff --git a/rainloop/v/0.0.0/static/css/default-skin.png b/rainloop/v/0.0.0/static/css/default-skin.png new file mode 100644 index 000000000..d74959689 Binary files /dev/null and b/rainloop/v/0.0.0/static/css/default-skin.png differ diff --git a/rainloop/v/0.0.0/static/css/default-skin.svg b/rainloop/v/0.0.0/static/css/default-skin.svg new file mode 100644 index 000000000..61f52f102 --- /dev/null +++ b/rainloop/v/0.0.0/static/css/default-skin.svg @@ -0,0 +1,38 @@ + + + + default-skin 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/rainloop/v/0.0.0/static/css/fonts/rainloop.eot b/rainloop/v/0.0.0/static/css/fonts/rainloop.eot index e9b726c3f..cb64d4537 100644 Binary files a/rainloop/v/0.0.0/static/css/fonts/rainloop.eot and b/rainloop/v/0.0.0/static/css/fonts/rainloop.eot differ diff --git a/rainloop/v/0.0.0/static/css/fonts/rainloop.svg b/rainloop/v/0.0.0/static/css/fonts/rainloop.svg index 23c7203e7..3357f3364 100644 --- a/rainloop/v/0.0.0/static/css/fonts/rainloop.svg +++ b/rainloop/v/0.0.0/static/css/fonts/rainloop.svg @@ -117,4 +117,9 @@ + + + + + diff --git a/rainloop/v/0.0.0/static/css/fonts/rainloop.ttf b/rainloop/v/0.0.0/static/css/fonts/rainloop.ttf index e3f590a59..3cc1d79cc 100644 Binary files a/rainloop/v/0.0.0/static/css/fonts/rainloop.ttf and b/rainloop/v/0.0.0/static/css/fonts/rainloop.ttf differ diff --git a/rainloop/v/0.0.0/static/css/fonts/rainloop.woff b/rainloop/v/0.0.0/static/css/fonts/rainloop.woff index a85dce219..3ff6793d9 100644 Binary files a/rainloop/v/0.0.0/static/css/fonts/rainloop.woff and b/rainloop/v/0.0.0/static/css/fonts/rainloop.woff differ diff --git a/rainloop/v/0.0.0/static/css/preloader.gif b/rainloop/v/0.0.0/static/css/preloader.gif new file mode 100644 index 000000000..7cdc671db Binary files /dev/null and b/rainloop/v/0.0.0/static/css/preloader.gif differ diff --git a/rainloop/v/0.0.0/themes/Blurred/styles.less b/rainloop/v/0.0.0/themes/Blurred/styles.less index 7972cfb89..2ae788971 100644 --- a/rainloop/v/0.0.0/themes/Blurred/styles.less +++ b/rainloop/v/0.0.0/themes/Blurred/styles.less @@ -16,6 +16,8 @@ @login-box-shadow: 0px 2px 10px rgba(0,0,0,0.5); @login-border: none; @login-border-radius: 7px; +@powered-color: #ddd; +@languages-color: #ddd; // MENU @dropdown-menu-color: #333; diff --git a/rainloop/v/0.0.0/themes/DarkShine/images/background.jpg b/rainloop/v/0.0.0/themes/DarkShine/images/background.jpg new file mode 100644 index 000000000..85211aa50 Binary files /dev/null and b/rainloop/v/0.0.0/themes/DarkShine/images/background.jpg differ diff --git a/rainloop/v/0.0.0/themes/DarkShine/images/preview.png b/rainloop/v/0.0.0/themes/DarkShine/images/preview.png new file mode 100644 index 000000000..e0b350760 Binary files /dev/null and b/rainloop/v/0.0.0/themes/DarkShine/images/preview.png differ diff --git a/rainloop/v/0.0.0/themes/DarkShine/styles.less b/rainloop/v/0.0.0/themes/DarkShine/styles.less new file mode 100644 index 000000000..28e148c21 --- /dev/null +++ b/rainloop/v/0.0.0/themes/DarkShine/styles.less @@ -0,0 +1,58 @@ + +// MAIN +@main-color: #333; +@main-background-color: #AD455C; +@main-background-image: "images/background.jpg"; +@main-background-size: cover; + +// LOADING +@loading-color: #ddd; +@loading-text-shadow: 0px 1px 0px rgba(0, 0, 0, 0.5); + +// LOGIN +@login-color: #eee; +@login-background-color: #2b333d; +@login-rgba-background-color: rgba(0,0,0,0.5); +@login-box-shadow: 0px 2px 10px rgba(0,0,0,0.5); +@login-border: none; +@login-border-radius: 7px; +@powered-color: #ddd; +@languages-color: #ddd; + +// MENU +@dropdown-menu-color: #333; +@dropdown-menu-background-color: #fff; +@dropdown-menu-hover-background-color: #48525C; +@dropdown-menu-hover-color: #eee; +@dropdown-menu-disable-color: #999; + +// FOLDERS +@folders-color: #fff; +@folders-disabled-color: #aaa; +@folders-selected-color: #fff; +@folders-selected-background-color: #2b333d; +@folders-selected-rgba-background-color: rgba(255,255,255,0.3); +@folders-focused-color: #fff; +@folders-focused-background-color: #2b333d; +@folders-focused-rgba-background-color: rgba(255,255,255,0.3); +@folders-hover-color: #fff; +@folders-hover-background-color: #2b333d; +@folders-hover-rgba-background-color: rgba(255,255,255,0.3); +@folders-drop-color: #fff; +@folders-drop-background-color: #2b333d; +@folders-drop-rgba-background-color: rgba(255,255,255,0.3); + +// SETTINGS +@settings-menu-color: #fff; +@settings-menu-disabled-color: #aaa; +@settings-menu-selected-color: #fff; +@settings-menu-selected-background-color: #2b333d; +@settings-menu-selected-rgba-background-color: rgba(255,255,255,0.3); +@settings-menu-hover-color: #fff; +@settings-menu-hover-background-color: #2b333d; +@settings-menu-hover-rgba-background-color: rgba(255,255,255,0.3); + +// MESSAGE LIST +@message-list-toolbar-background-color: #eee; +@message-list-toolbar-gradient-start: #f4f4f4; +@message-list-toolbar-gradient-end: #dfdfdf; diff --git a/rainloop/v/0.0.0/themes/Default/styles.less b/rainloop/v/0.0.0/themes/Default/styles.less index 70b831ebb..907a2d7ae 100644 --- a/rainloop/v/0.0.0/themes/Default/styles.less +++ b/rainloop/v/0.0.0/themes/Default/styles.less @@ -15,6 +15,8 @@ @login-box-shadow: 0px 2px 10px rgba(0,0,0,0.5); @login-border: none; @login-border-radius: 7px; +@powered-color: #ddd; +@languages-color: #ddd; // MENU @dropdown-menu-color: #333; diff --git a/rainloop/v/0.0.0/themes/Love/styles.less b/rainloop/v/0.0.0/themes/Love/styles.less index 89a0134c6..171a7fe20 100644 --- a/rainloop/v/0.0.0/themes/Love/styles.less +++ b/rainloop/v/0.0.0/themes/Love/styles.less @@ -15,6 +15,8 @@ @login-box-shadow: 0px 2px 10px rgba(0,0,0,0.5); @login-border: none; @login-border-radius: 7px; +@powered-color: #ddd; +@languages-color: #ddd; // MENU @dropdown-menu-color: #333; diff --git a/rainloop/v/0.0.0/themes/Snow/styles.less b/rainloop/v/0.0.0/themes/Snow/styles.less index 91aa78497..47abc486f 100644 --- a/rainloop/v/0.0.0/themes/Snow/styles.less +++ b/rainloop/v/0.0.0/themes/Snow/styles.less @@ -15,6 +15,8 @@ @login-box-shadow: 0px 2px 10px rgba(0,0,0,0.5); @login-border: none; @login-border-radius: 7px; +@powered-color: #fff; +@languages-color: #fff; // MENU @dropdown-menu-color: #333; diff --git a/rainloop/v/0.0.0/themes/SquaresDark/styles.less b/rainloop/v/0.0.0/themes/SquaresDark/styles.less index 70b831ebb..907a2d7ae 100644 --- a/rainloop/v/0.0.0/themes/SquaresDark/styles.less +++ b/rainloop/v/0.0.0/themes/SquaresDark/styles.less @@ -15,6 +15,8 @@ @login-box-shadow: 0px 2px 10px rgba(0,0,0,0.5); @login-border: none; @login-border-radius: 7px; +@powered-color: #ddd; +@languages-color: #ddd; // MENU @dropdown-menu-color: #333; diff --git a/rainloop/v/0.0.0/themes/Stripes/styles.less b/rainloop/v/0.0.0/themes/Stripes/styles.less index 15fdd455b..0c77e4d97 100644 --- a/rainloop/v/0.0.0/themes/Stripes/styles.less +++ b/rainloop/v/0.0.0/themes/Stripes/styles.less @@ -14,6 +14,8 @@ @login-rgba-background-color: rgba(255,255,255,0.5); @login-border: none; @login-border-radius: 7px; +@powered-color: #ddd; +@languages-color: #ddd; // MENU @dropdown-menu-color: #333; diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index 4e0ac9ad2..000000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/tests/data/mails/uid-01.eml b/tests/data/mails/uid-01.eml deleted file mode 100644 index 945568682..000000000 --- a/tests/data/mails/uid-01.eml +++ /dev/null @@ -1,232 +0,0 @@ -MIME-Version: 1.0 -Message-ID: <45a92f7.0e89f233c32fbd2b9d191d4cb28775c9@domain.com> -From: from@domain.com -To: test@domain.com -Subject: TEST HTML MESSAGE WITH ATTACHMENT -Date: Thu, 6 May 2010 07:26:37 -0700 (PDT) -Content-Type: multipart/mixed; - boundary="--=_NextPart_11de424d3d872ac110be4ecfffc45411" - -----=_NextPart_11de424d3d872ac110be4ecfffc45411 -Content-Type: text/html; - charset="utf-8" -Content-Transfer-Encoding: Quoted-Printable - -TEST HTML MESSAGE WITH ATTACHMENT - -----=_NextPart_11de424d3d872ac110be4ecfffc45411 -Content-Type: image/gif; - name="av-7.gif" -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; - filename="av-7.gif" - -R0lGODdhMgAyAOfXAAAAADMAAGYAAJkAAMwAAP8AAAAzADMzAGYzAJkzAMwzAP8zAABmADNmAGZm -AJlmAMxmAP9mAACZADOZAGaZAJmZAMyZAP+ZAADMADPMAGbMAJnMAMzMAP/MAAD/ADP/AGb/AJn/ -AMz/AP//AAAAMzMAM2YAM5kAM8wAM/8AMwAzMzMzM2YzM5kzM8wzM/8zMwBmMzNmM2ZmM5lmM8xm -M/9mMwCZMzOZM2aZM5mZM8yZM/+ZMwDMMzPMM2bMM5nMM8zMM//MMwD/MzP/M2b/M5n/M8z/M/// -MwAAZjMAZmYAZpkAZswAZv8AZgAzZjMzZmYzZpkzZswzZv8zZgBmZjNmZmZmZplmZsxmZv9mZgCZ -ZjOZZmaZZpmZZsyZZv+ZZgDMZjPMZmbMZpnMZszMZv/MZgD/ZjP/Zmb/Zpn/Zsz/Zv//ZgAAmTMA -mWYAmZkAmcwAmf8AmQAzmTMzmWYzmZkzmcwzmf8zmQBmmTNmmWZmmZlmmcxmmf9mmQCZmTOZmWaZ -mZmZmcyZmf+ZmQDMmTPMmWbMmZnMmczMmf/MmQD/mTP/mWb/mZn/mcz/mf//mQAAzDMAzGYAzJkA -zMwAzP8AzAAzzDMzzGYzzJkzzMwzzP8zzABmzDNmzGZmzJlmzMxmzP9mzACZzDOZzGaZzJmZzMyZ -zP+ZzADMzDPMzGbMzJnMzMzMzP/MzAD/zDP/zGb/zJn/zMz/zP//zAAA/zMA/2YA/5kA/8wA//8A -/wAz/zMz/2Yz/5kz/8wz//8z/wBm/zNm/2Zm/5lm/8xm//9m/wCZ/zOZ/2aZ/5mZ/8yZ//+Z/wDM -/zPM/2bM/5nM/8zM///M/wD//zP//2b//5n//8z//////////wAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEA -AAAh+QQFCADYACwAAAAAMgAyAAAI/gCvCRxIsKDBgwgTKlzIsKHDhxAjSpxIsaLFixgzaswYiNVG -ioFWAAg0kOTHhitYsQJwjZVIKycXBoJ5jeXMa1ZMxjyYUyDLkddS7kTYM2RIn0MRCl0xk+TRpARV -Mg0EwCXJnklZWRG5oqtXkSpZDp0JAKtBqmV3umTqUSbQj1p1NrS68aZEl20v2qW4AuPKi1ovMsXY -12JVVh0R530YSG7Em1a2rogs0AqAy5gx86y4V3BFsxfFThycUbRE0BVdcqZ5ETXEvxhNj3Z8V/bd -whVJW6RMcatG3Q+1Cv3NumHIro1BGtzaUaZXxJIXN9z6tuTlyY1VJrY8mGtl3A7Zcb5sihw6gK6S -mwtMadmjb8aDETc1SRa8QaFUPQJneDyn9pkizVTcfW2FBF1EUnnFXFv7FTQcTjBV9dGDB5n2l2uc -2WeQbDl11hptUdk3k2oZ4aUQiSX1ZRtIGhbkoWorUiSdQz9BxZBVLdpIkIADDhQQACH5BAUIANgA -LAYAIwAlAA8AAAivALEJHEiwoMGD2AAAWBGI4LWHEB8inCgwECtW1wIBsDIwokdsKygWXEFy48MV -HLF5XJkQJMmQE6+xIomRocqVEGeqFNnxoRUr137exGllhcyiDUVG5HgNAEaPrALRZAUgKc+IVIOm -DBSo6EJWLsHyXPEUohWLIVF2bRh1I0+HH4ECwCb1bNeNYt8KXMlKbsWuZ/Pq3etRatPBVwufhYl4 -sFSGOCNLljyz4eTLmCMGBAAh+QQFCADYACwHAAYAJQArAAAI/gCxCRxIsCBBVgYTKlxYMBCAFQwj -SmS14poViBQBIJTIEZuVQNewQbRiZWNHiSsQBiqpERuAazBjyjwpMCW2lYEqIpTJMyRNmytw3rRC -M+JLVi9fXgtatODFFQAAuIwK9RrFnjwXrnzIilVPilCJNs2JzSvWmKwumuxI9OzZnGuNgnTr1mZE -h2bp1o2INFBTgisZktRLOGRcgi3/Avab8CrUqA8FOoRMWSlPiAkHFyaMWXFHqZ5Diy5IcXTEjws7 -h05MMGZZ0WS1ivUMgHHBmbabrrBy9iDosRoDBTrsOzfHi8itACi5EKxxhZOFd4WpnLhAVjlrCze4 -9fVDqCmvbwXGRhfpiseUg5otCzNtRfHPnVZs61bgTKW/EzoU3/EySNQGZYTdbhxlVdF2Ti3Hn0cF -8vTSTR6d99BcJAlk2VkXxhQUThqZNR5NWOFE1EcInUeeaw3ytBKJu2HWU34LYeWcaSf2RGCKm+Wo -Y2EBAQAh+QQFCADYACwHACEAJQAOAAAImACxCRxIsKBBgwASKgx0sKFDg6yuSZQYyMrDiwcjCgwU -8VpFjCCxKVyxwgqAFRJXsAoJcqJEKyitWHQYCAA2hhhdXmOlkqdOnSs4hvwp8xqAnxJ5slo5VGcg -lEd/1gx0TeUKlgR9Rk1a8+rTkCohQqW4AoCViDWZYi049ilVVlPVtkR60so1mVZq5l2LDelOqhvz -ysXqd2JAACH5BAUIANgALAgABgAlACsAAAj+AK8JHEiwoMGDCBMqDMQKm8OHECM6VHgw0AoAgQZm -pMix4ApWrABgY3VRosmTJgNZEQjgmkpsVgKhnGkyJstrGK+NpMnzYUxsFi2y7En047UVKmVapNnx -GkigMklm/DlTISsrF1do3XoRpEimFbEBoCoxEICxHCWSRNqQptmNCSNelUl0JNKmL+s+JNnWpEYr -eiWuiDvya2CIDRE6RHpYsEIAIBmyYtiUYCC6ZR1awbpis+azoEPXbBx4BenTEitTNIx6JkmUgFvD -xCwXm+qErAXTbhzSte3bHndL3Ax8IGCuH03evX2VreSZnVVbHGyQbESslBdyDT22sHCHFgFldL4c -GaQV8XQZpqTZcPNWzup54uw7fHBs0kYPhnSZm2jv/AUtRVJ/fhkkEoAaQXbUfWAV9BUrAzUknnkM -VnUQZBOZ1RmE2DCGGoMK8mdFQ6/Jp5hmbGlFn2wOhYQeiyitCONDAQEAIfkEBQgA2AAsCAAgACUA -EQAACKAAsQkcSLCgwYNWWAVidbChw4cEA0GcePCaRYusVkikyDGQlRUgA1kEwJEiAI2sUmITGWhF -yYkXL67MyJEkxJgXXWK75tCKlZI4La5gBYCnQ5UUg16zslEpT5YvCzIVqHTFz4xDHxatyhCnQmxW -hEYtqNGlRY8AAonMyHDswEA2r8FdeBZASps3ncb8adHKybY/3RaUC+BkQsEP9SperDQgACH5BAUI -ANgALAkABgAkACoAAAj+ALEJHEiwoEBWBhMqXEgwEIAVDCNGZLXimhWIFAEglMjRSqBr2CBasbKR -o8QVCAOR1IgNwLWXMGOCNIkSm8pAFRHK3GkyJMIVN21a6RnRJSuXLq8BJVrw4goAAFpCfXqN4k6e -BlU+ZMVqJ8WnQ5nixNb1KkxWF0t2xGbWLE61DAF8bNu2JkOHZenWZXg0ENOCKheO1EsYJNyBLP8C -9mvQ6lOoDwU6hEw5qUyIBgcXJoxZscmonkOLbtx5tEKPCkuHTlzQ8OixCgOLfug0UMmXNkM/tckq -kEPGB0H/xelVeHDgJtP69jj3YsGvyBdOXvH05sWPrA/ilOs7q5Wo0GVWhrQ41yv1x5SBliXbOKrK -tjaJw1/YO2xWlNRDo5aOzT5RkdGxxxVUntkFGEn9+ZfaYZIZBACCATJFUUn5veRQT4SF9RJaVuBm -WkgN9oebaqaV56FpAQEAIfkEBQgA2AAsCQAfACQAEgAACKkAsQkcSLCgQYMrElpJyOqgw4cErVhh -1ZBVoBUQMx5kda2jR1YANIrEFsgKgJMnA10jORLiQowcr7FaYRFjy4MhsXn0KHHmQ5Mjd3oseS3n -QSsCkd4UOLOoQ5DXlC7F1hSAUI8kA7GcKvHi1ahIbU4VGIioUGw2V1j9itUhAIk6d2LLaXRsWa0f -E1Id+1CmWpUXKYrlyxRAII4mGxI+uKLk28U/SyqGbDAgACH5BAUIANgALAkABgAlACwAAAj+AK8J -HEiwoMGDCBMmDMQKm8OHECM+VGgw0AoAgQZmpMix4ApWrABgY3VRosmTJgNZEQjgmkpsVgKhnGky -JstrGK+NpMnzYUxsFi2y7En047UVKmVa5NkRJFCZJDP+pJmQlZWLK7JqvQhSJNOK2ABMlRgIgNiO -EUkibUiz7EaFEK3KJDoSaUeXVujG/ThT4Eu9EVfADQlYYsOE2JAWlig4IQCQDFkxvEsw0FyyDq1c -XaE5s9nPoGsuBrxitGmJlCl6PU2TJMq8rGFeTostteOZik+HtMybtm2DWFVexQhR8++BVyXmZevQ -rm2rH602zwz7IefUFjlntcKcYc21FLNtJ+YLUehjslhjRo5sBUDutwVFjk1rsf32rN4hIqzLKiHn -2SjtRxJzEQlV2lcGPdTfQWsRtV9YCxakkk48tYRQSHlV1BJhCKb2GEmrsUaQdNdoFuJpGhmVXGwT -lfjYUaU5xSJ/as2YVlbE2fhQQAAh+QQFCADYACwKAB4AJQAUAAAIrgCxCRxIsKDBg6wSsjrIsCHD -FQBWSLQSyKHFgxJZXdt4LRDFiyAJsgqk8BorAAtDXgwUUeKKQB1XqHTIEpvGjSexXQMws6BLiTo5 -brRi5drLngatMAy0omNFgwl5ghRKdcVImQitTqXKkShTrhuxedwK9ho2AE8L5gxZti3HlxVTPrzp -FuxXpCpzpsVrsGpRrHz7vi1aM7DgaxRNAjZMcCjMk3sZCzSpk9VRyQUDAgAh+QQFCADYACwJAAYA -JwAsAAAI/gCxCRxIsKBAVgYTKlyYMBCAFQwjSjy44poViKxWAEA4sSM2K4GuYYNoxQpHjxNXIAxk -ciM2ANdiypw506NKbCwDVURIs6dMmwhX5MRpBeVEmKxgwrwm1GjCixoBvATwEGlFnz4bWnnIilXP -jBqLOhWoE5tXrDFZXTyJsihanzrZHg35FuvNiQ7P1rU7MWmgsQNZSiy5d6/ZiC4BB/67MCNTqlQh -4oRMmapPyQoJF96LWXFHqZ5DizaYcfRgxgs7K3bZVS42ka6N/tW4grbYwLcBAwik9xrLuzFxKl5h -RWSgnMFzJwUcdyvIklItsvVr9KLD4jV3qgaLmqFDqbt9Y4ZvSpCVzt3HDbKUuhytTot0v26NSlno -WbNvJWP3ufYqVoFvrdddQRsNqNBmCv11DWgRIZgQeaYlCJNbb3mWVFcRZlZcbhmSZZaBGSqYVEkd -UhRUSCUKJBlLm7VIU0nEuehiQAAh+QQFCADYACwMAB0AJQAVAAAIogCxCRxIsKDBg4FYsTrIsKFD -KysAALDisGLDaxgxsoK40KJHbIEkSlwR6Bo2AIE+OgxJUqMVlNesUFRpcGTJjBpboqRp8CbOjKwA -LJzJU6DPnxllyixKEOlPVhE7EkwZiOhFpzgVHiQplalFANisem0oM+bYjwu7nsWGU6DZtQxTYlML -tyBGsXXnzr0WdEXeghShav07FwDGFXjrBiKJmDDBgAAh+QQFCADYACwJAAYAKAAsAAAI/gCvCRxI -sKDBgwgTKhQYiBW2hxAjSpy4sGCgFQACDdRYsaPBFaxYAcDGCuPEkyhTYgtkRSCAayyxWQmksibK -mS6vZbxG0qbPiDNXrrjo8qdRbCCvDQ160WjHkCtpltQY9GdCVlYwrtjKFWPIkU4N0gRQdWIgAGQ9 -CpRYcqjDmmc5doyIlabRqWoZWjkKseTbmnr5SlwxV6RgttgqIrV7GCLhigBCNmTVMC9DxijtWsm6 -YvNDK2hDiz65t7HgFaZTq7ScEKxqnyVtllaNU6FD1gdXht5a9uFQ1SJ/k6TceWBP3ASHCqRcm+Xf -zcivZYWJceZOnoPlqsU6tPNfpCBPaBZXe7FzRtKEs0rMWllhebRWvg8WLvEigM6BJk8GLRwzyq3+ -9SUSZ50BKN9rFwWYWkIk5fcaXKih9uBDCK3E00KveabghA+JxIpSHFJYUFAHvmbRY1uFmNhGj8Wk -Yn0kufYiRNdMNWNAACH5BAUIANgALAwAHgAlABQAAAiGALEJHEiwoMGCK1YAAJAQG6uDECMStOLw -2rWBAB5K3DhxYKCPGDmKdIiN4cAVgbAFAjBSohWWKT1aSbmi5cGFKzQWjBnT5k6LQINeC0SRpc+B -rIQqHUry6MClUCHqdMoRJUWqHFkZxbqRVU2uIr+CHUu27MarZgU+1LjV7EVsFnOmPTjVbEAAIfkE -BQgA2AAsCQAGACkALAAACP4AsQkcSLCgQFYGEypcuDAQgBUMI0okyGrFNSsQKwJAOLHjQCuBrmGD -aMUKR48eVyAMZHIjNgDXYsqcSVNkR5XYWAayiLCmT5kpEa7QmdMKSo8wWcGEeW3o0YUYVwAA8HKq -1GsVf2pNyPIhK1Y1K0o1+pTgTmxgtWLFePKpUbU1d7ZFGhKuT5weHaa1e9ej0kBlB7LsWJIvXLQT -XQYWDDhiVqlTHwp0GLkyU5oQIxY2DDfz4qNUP4sezbAiacKNI3r+DCDQTsgrTBIUOfcp4NhfYwol -O5l34NZYWQX6ii0m2+IiUz+N/Tr2iucbL3pWGninQ7Q0X6INfbD1UStW91fSrCiX4nPlDSNbsTt1 -rnCprtEXpUqes1ZW4CFXHppWoP2tOKl1GjYlDejYRoMZyNVqCi6El0KXGcZVgxSW9WCFAzn1km8N -shTTSBgOhJBS/3EmolkVBgQAIfkEBQgA2AAsDAAcACYAFgAACJgAsQkcSLCgQYOBAq1YaCUQq4MQ -IxJcAcCKFVYYA1lZEUiiR4SsrokceY0VgI4fPz7ERhGAy4ohOab0yMrKSZI1V1xTONOjFWwkR7I0 -2dPgip+BggbF1rBowYVKo4p0OrAi1acrrjoFoLWr14JKBTb9+vEnWYkPuUpcsZKqxbNwuz5EGfeg -2YV1BxIVm3egzL4Er2GjCxhbQAAh+QQFCADYACwJAAYAKQAsAAAI/gCvCRxIsKDBgwgTKhwYiBW2 -hxAjSpyIbWHBQCsABGJosSPCFaxYAcDGKiPFkyhPBrIiEMC1ldisBEpJE6XMltc0XiNZs6dEmdgw -Ymzps+hDkNdWrJyJ0WjHkEFnltwItGhCVlYyrtjKNWPIkU4NzgRQdWIgAGQ9DpRYUqlDmmc3qo2I -dWbRqWoZWjEKseTbmgJh8o24wiNJsIP7VrSITWliiYU7AgjZkFXDvIHtprRrJeuKzg+toB1NmuLe -x4lXoF4NGDNCxKx7lhRomXLBmLFjdgbwmStsh64PorXyF6LmxscTi2T1cmmgy8t5BieodOZnmZ4b -Ruw8/ZrorcmDYXLVrNQ11pGnT9YmzFItxuGJs15WiHGrRtTvPz+nHPK73fCDQfVdb27lxpZqBtbU -WXoJUhRdg4sZBFqECOX2IIQHgcaggQYttxOEFArkGICxXdQebCAeJtCEKUb0EoIgBgQAIfkEBQgA -2AAsDQAcACUAFgAACJsAsQkcSLCgwYKsrKwAwBCAFVYHI0oUuKJioEDXMl4LBCDQxI8EA7HKyKrk -yIwAIIL8eC3hwooLrWALJHPlRIVWMGpMyLCmzYhWNAod+tMgw5NDkxYtuMLj0p9Hn0qdig1mRaoF -kwLAutIpV4NEvX49KHYigKRo0+YsO3Ygq61ts2bMqTIuQYV2DbJaAZHt2Gt8Z8LNi1dgXbsBAQAh -+QQFEADYACwJAAYAKQAsAAAI/gCxCRxIsKBAVgYTKly4MBCAFQwjSiTIasU1KxArAkA4seNAK4Gu -YYNoxQpHjx5XIAxkciM2ANdiypxJU2RHldhYBrKIsKZPmSkRrtCZ0wpKjzBZwYR5bejRhRhXAADw -cqrUaxV/ak3I8iErVjUrSjX6lOBObGC1YsV48qlRtTV3tkUaEq5PnB4dprV716PSQGUHsuxYki9c -tBNdBhYMOGJWqVMfCnQYuTJTmhAjFjYMN/Pio1Q/ix7NsOLknStWgCRdsGRUk6wCaeTo+TNV2T6V -gkX8eWfMQJStAh482LbshyZlooYYM+di1Wxzy+aoNLDDoXwH/j1qZWpduxRTVjeOGHwveIo7AQAf -L7g72tSctbKKavkhbpFzI6pVb0Ut66Ls/ZfQUMAJqFBPtRlI0GoS4WVbfgpORlaEBTkkUoIROkjh -QCphFdqGgMXk1IanqRdffAEBACH5BAUIANgALAgABgAqACwAAAj+AK8JHEiwoMGDCA1iW8iwoUNW -CSMedEiRoRUAViRqFFhx4YoVrLAFWoGN5EaJHa0ECgQAm0qRK05G7Aiy5MqM1wDITJgyEExWMXPu -RNgxkJWFAFglvWZyaMGO2JaKtJJRpdOTVK9Z+Qiga9cVKiFePah0BYBAIRuyGonRJ1SKCT+6hRoS -QM23DBGCxftwxdy3B41eXcnXICucg48CJgh07ECqiweudCxQadqKBYNS1qoY88DDFC96Hd0Sm9LR -rIBCZRxo88DSngVadZ3zskOCgmmD1JjbtemNOmkH1zibcsmTI10Xlwi0tePjMkeKveo3MsGtY43i -NczW+cnDnfmXmjar8mLTiGv3il+o1OfhpNqtTK+8dmv1hixJdjwr0q7fyluRZhdVbaW2klngVQSZ -dCXNV5BSVk31EVXuJUWRUj955CBjZ7VEGEPpWRiaT/xpGJGFRpk12n0V+TUSXAktJ5Nq6i3UW2CI -7WSUZbjleNBwOsqnWWVDHiTjRjsW2VhELDml0o2V8bYefn75SBtRZxV55UEBAQAh+QQFCADYACwH -ACMAJgAPAAAIsACxCRQYyMqKgwMTKlzIcCGrg4FYsbrWsKLFhACsXGNlBYDHQIEIXhR4raRJkwKt -aHy4YqIVhR5jesQGYEWgkzhLYmMFYCIAbChHSrSZEyc2lddaniRZVKdSg01L8mR11OjIQCuAvtRY -tGDSiUtHCjw4NGNEqgQ1AsgpdmCgjDuxHrSJreBDtm0FcjQrESvVl3et5tWrcm7Iui1X4B188efa -xYwb8lQctbLly5gzlwwIACH5BAUIANgALAcABwAlACoAAAj+ALEJHEiwIKsVVgoqXMhw4MGBAAJZ -Sdiw4kJWVgIBYMVK4EOLIAteWxEo0Ipr17B1DAlSokoA1zZiW7GSZcWDKLEF0okto02BKIMKHdkx -o5UVOin+bDhR54oVAJ5GzRio5lKPCAFkZFX1GleoCK1etcJq6NCdWq/uDGS2LcqjYitKdEv3msm4 -F63UrXtwp8WTe/lGlOv36kCNFQEH3kuSIUaFGgFInixZYOTJXxuyXRwYAEOlhlm6DM0SL2nHnk9D -ZFqY9MeFJlX3BG2Q52maA9siJt24YdmmhnfixRh1Mu2QJs1ajij04OCQzk179gpVMsKnZB1H3qyc -pletbB9qej36FHv1qNx1h0cq1HtQjly/2sW2N+PI9ClBnuxZESYrmENZdFBTT0lnl14BgvQfbjPF -BRNJykGXEEo7IVRSRzABmKBNSJXFVUJ6ZdhWaOx5RZ9nCG4I0lnnUaTViLIBVVeM9HFmY1sBAQAh -+QQFCADYACwGACEAJQAOAAAImACxCcQWaMUKKwZXDFzIsKHDhSsCsZI4UeHDixhXsLrGsSPGjw6t -BLqGbQWAkyhBqsQGgBUrACM7kmS1EiOrFdesWJHJsebHQNiuteRJEqQVlkAfBrJyk2jRjBEFbnQq -cqnTlRpfSiS6lOZVldeiImQqs6tFnwwPkrQCICLHggfRNmSFcGRFnCU1ynV4E2bFqU577qV78GhA -ACH5BAUIANgALAYABwAlACoAAAj+AK8JHEiwoEBWBhMqXFjQCgBsDCMqXLECYSBs2FZcxMixo8eP -IAMFAnDNysWLEguC5Fgxo0grAkmmHLgSo0lsF1lBvCZz5rWaOGHyZAVAZ0ugSD0WdWkFpkmfP4E2 -LUkRgFWrK0wiZJg0I4BAOjuyCrTiYSCoBClurDkWQEW0WbtyZKXRZyArcj2KVAmSFd68eoUK7LsC -MMimQEUa/kg0bMIVaBUiXvm3o8OrmB9iI4qZFd2VYRcDvik679rSXe+i9lh4Zc/IBTUfPl2abE2U -sAmSJkzb8Ge9d9duhY2TMjaTeHOr/tjb4VmffitznEqVYtayOCWOjetR58/Cnj1en7z7dfjBsVbU -HpbO9nLmsrs/kk6YtCLuiWNLf72WdaH93l1ZdRZ1BmkUX15NFcXfTgW9xB5gIyGEnXkcAZgXXWf5 -5dZzQSlkmENCZQTZaoS91RiJoF3VGoqggYVNQAAh+QQFCADYACwGACEAJQAQAAAInwCxCcS2AoBB -ANiuKVzIsKHDawMjFky4UCCriBgzaowYyIrCQCCtXNxIkiSrFRdXWLHScYXLkjAxrsDGsmHMmwNB -UrSJE2ZHlj0jAoB4EyXJhxWt3Aw09CHOa6wAjMyo1GjQgSsCXWM6FaMVq1cFotwq1eFFqWEHshIJ -tSBbhdhYdU17kiIrph6JppUY1+XbrHr3ZuwI4GtWwTEDyVUYEAAh+QQFCADYACwGAAcAJAAqAAAI -/gCxCRxIsCA2ViusGFzIcCHCgQACWVHYsKJBVlYCAWDFSuBDiyAJXlsRKNCKa9cOhgQp8SCAaxux -rei4siJClNgC5cSWsSbKn0BRzuQpcUVOijUbTsy5YgWApk8zBqKZdCBCKwAyspp6bavThFSr8mQV -NKjOrFV1BirLFqWVoSEltp17zWRYhhjp0kWos+JJvXsjNiwptqDGhn8B6yXpEOlAjQAiS44sELJk -rwzXKgYMYKHjwiw/g254dzTpzqYhMuyZ+qNBk6l5Ot7aUrBpuDmzZnSKGjTjyjOBYhS9UidNVhFR -cuSY0kppiyaBTqzbtHpnp31BIowJkexG4diyajZ1jheyZqAvdbI92PVt9YS8O58P+vL3QuEcp9pl -37b+87iwMfTSTlUhF1N2F9lXFWNGLUQYaBO9dA1rBHUkYVkhTfSXSQjqpd1AwZnUIDYerlQXd+H1 -lpqEyCkYm0BAmdTZgC9exBE2AQEAIfkEBQgA2AAsBQAfACUAEgAACK4AsQlkZQWblUAFBSpcyLCh -w4ErrByUuOKhxYvYWK1gda2jR4wgGa4IdC3QipMRWQn0yLKly4asAFyzspEVQoErAAAIeRHhNQAc -WWILxBPjQZMuOw4t+hChyqQziWJcAbXjwadJQVKVaNEkNplZQSa8KJMpQ502jR40q/AkQapQTapk -iy1iQZovQ1ZVWpcoTZIf6TrUSDIQgIN8BTs8aRUAScUWrThOC7knzZHYAgIAIfkEBQgA2AAsBQAH -ACQAKwAACP4ArwkcSLDgQFYGEypcSNAKAGwMIyZcsQJhIGzYVlzEyLGjx48fAwUCcM3KxYsSB4Ls -WDGjSCsCSaa8tpKjSWwXWUG8JjNlTYyBYPJkBUBny59IOxZ1aQWmyZlJm5akCKBq1RUmESpMinEF -gEA6O7IK5BXnzIIUN9YcC6Di2WtYuXJkpXFmULkeRapcycoK3pBC+a74C7JpTZGEPxLVanDF24WG -C390aLXyQ2xEK7OiCzJsYrw3P8tVKzrp3dIsV/Z8bPDyZNKiyRLcvFk264KhR1Kk+hU1Z2wO/Xa0 -4vi2QJwYlwIHjtCv8dNkM950iNzs2b7CgQ/eOTBo24eBJHCOjctxcGSPi8liZSywdkaNHiuG/ni0 -r1fLXq14Zkl3f2Llrx21EkOYSZWQdwtxBVNRBxKXYFJFoWQQTuwVxNVDJQlFEGocVRVeXRtyCJxD -NOkXooiYOUZXeMehiBFxCFGEkIs2YQgcTTRiRFRTfgUEACH5BAUIANgALAMAHwAlABMAAAiuALEJ -xMbKCoCDVlasGMiwocOHAxMGukYxkBWIGDNiCwSAoseKBDWKJAhg4oqDCFmNHMnxWiBsHxVaubgS -4wqTLx0qrAkRgMqOHz1iu2mxJE+S16xMDCq0IcqnABY+ZOVzBSumTTVaxHqN1UkrXCmOvOYTIqur -Ya+xDLQirduVZFU2dJtVK1umRzW2LRk0b0afVNGK9WvTJdDBhB/eVNsWcWKHfIc6fsyQKkyv1wIC -ACH5BAUIANgALAMABwAmACsAAAj+ALEJHEiwIEFWK6wYXMiwIcKBAAJZUdiwIkNWVgIBYMVK4EOL -IA1eWxEo0Ipr17B1DBlSokoA1zZiW7GSpUWEKLEF0oktI0uUQIMKpdlT4gqdFG1anKhzxQoATqFm -DFRT6cGEADKyonpt69OEVa0KtMJKqNCdWcXyDGS2LUorRG1KdEv3msmwDq3UrYtwJ8iTe/lGtFhS -LUGNFgEH3ksyr0GNACJLjiwQsmSvFdkuDgygYVLDLF2CZol3tMPOpgWiZujT9MfKViI7dZqaqUqn -XDnG9gs6LkmUW8t2Xa22cU9sXZ3CdYrxs9KdHRFy/A1c406yVk0CpXlNoVlWUJ9o8r75tOZG8MLN -xl6OfeHWyJqBwpxYV2DX5bPhStbZNnz6tvYBxVFwd8HkFnKBgQSTcSItdpNOJjG02V96yZRaRZhd -iCFPGhpE2V0dGuRUd86F2BNZrZno0UYkqjiQS+gFuNmMEoGITUAAIfkEBQgA2AAsAQAeACYAFAAA -CKYAsQnExsrKCgAIAQxcyLChQ4asVlhhda3itYcYMwq0AiDQNVaBDK4IpLHkQlYdrwXqCJIVto4m -S1qxck2iRYvYDiq8ybMnzoEAQK6g6JMkwpgNUdbE5vMm0ocShzbF6fLpSZpTf2oMtMJh1p4luwa1 -6nUq05lkn45N+3TmNbRsY1JUGNeklYFO6zoEUJGkXo0rKg79i3HkR5iEk47lejExw5WslAYEACH5 -BAUIANgALAEABwAoACsAAAj+AK8JHEiwoMFrrA4qXMiQoBUA2BpKXLhiRcJA2LCtwJixo8ePIEN2 -DBQIwDUrGDFOFCgypEWNJK0INLmyJUiU2DCyiniN5kSbHwPJ7MkKwM6XQJOCNArTikyUNZVmdHqy -IoCrV1egTKhQqscVAALt9MgqENicKyluBFoWgMW0BbV6xcZqI1yBQueODDRQKSsrej0KZcl2ReCP -TpWSPEzWqMQVdxUmBgoYMdbLVzMWvcyqLlvGh3GCnstxtNe8pjMaBuozcsHWCElW3Ch6tFmCs610 -NuvWdUGoArUKLMu16FjGnj0KfPhQuBXIvq/l7Gi4buWcD4W+jYx6bM/SI8FjuuWb9u/1jpND8oZI -nmFZuR4hJt0ZMyzXgbs1rgUJ3u9DzG5pdVxqLe20EIGaGbYfgjbddhCDGWHEFIQuXWPWgBR2dFR/ -GWpEF2zSZbiTgQRlCNhy7YVIIVVFnSdiWBauhk1AACH5BAUIANgALAEAHgAlABQAAAiDALEJxMYq -UKAVK6wYHMiwocOHAwMBUBgIWyArEDNqtAiAlUBWrK6JxLai4saNBz2yWkHypEuGJS3KbGgF48uH -1wiuFMlTJCubNx1exNYxqNEVHXsqHWmU4dKnPJtKdXlwatCiVrNq3cq1q1eXNb+iBCo2oUCWYgcm -zRkSqtu3PdF6DAgAIfkEBQgA2AAsAQAHACgAKwAACP4AsQkcSLCgQVYrrBhcyLChQIQDAQSyotCh -RYasrAQCwIrVwxUXQy68tiJQoBXXrmHzKFLkxJUArnHEtoJly4sIU2ILtBObRpEpgwodmrKmz4kg -X94MSXHnihUAnkbVGMjm0oIIrQDQyKrqta5QE1q9OtAKK6JCeW4lK5BnILRwrRhdOhGu3Wsnx1rM -ePcuQp4iUfb1KzGkSbYDN4YUPLhvyb0VCW4EQLky5baWOZ68+LbxYAAWIyNuqXS0SL2mHbICnRob -64dIS1b9aRqiwKcKO1IMm7rpamxw3Zqu+Rtv5q1VX5N9XFcr16Abd7Pl6bGkWLhSRbtkjLvv5LUh -EVHOhN34JEWJgA9O7ux5qMCvcp/ihnqZvdCbGoV27Aq2LtzWBMmEGll3gUYbgA11pByCDD3G4IOj -aQchTUUNCCFCFrYmV1sgTVhWRQ56aFNpAQEAIfkEBQgA2AAsAAAcACYAFgAACKAAsQkUyMpKIIOs -BipcyLDhwkAAVgg8uGKFFYkOMzoMtCLQtY8gWQXSSHLhRVYFK1ZEeK1kSVYArsG0glIkx44jXWY8 -yGoFK5BAsVm0YkVnw4QAfgIFmvAitoRGBy6dCrSoxJxRqWptCUCoRahRjYLNiDGsQp9m0+qcqpZk -2bYuOcKNOnZuw6J2o2LNa7IozL18G3YNnJEo4YxJDysc7DMgACH5BAUIANgALAAABwApACsAAAj+ -AK8JHEiwoEGBrA4qXMiwoBUA2BpKZLhiRcJA2LCtwJixo8ePIEN6DBQIwDUrGDFOFChSpEWNJK0I -NLmyZUiU2DCyiniN5kSbIAPJ7MkKwM6XQJOGNArTikyUNZV2dHqyIoCrV1egTKhQ6scVAALt9Mgq -ENicKyluBFoWgMW0BrV6xcZqI9yBQud2JDlQKisrekcOVVo38EenUkkaJmt04oq7ChEnBXwYq+Wr -GYtaZlWY7eLFOD/r5Sjao8S8pTOu6NjWMjYrRSErhPja7diMQq+S/mzW9DXOYhO+5il7YOhAJ91q -PSt27efCdR/C/mi24uPi13JGRC2yJMTi3AlUY0We9i/luXUxk2dYVi7v3GILAtfoPPXfy1m13k5t -Mzx/kXXF1tV/HklGoE0CGnSgR3YpuOB2PhH0oHZaOfjgb29J+OBadRU0YYA5XcfShFRl51FAACH5 -BAUIANgALAAAHAAmABYAAAiiALEJZGVlxQorVgKxEsiwocOHEAVaAYCNFatAgQquCBSxY8dAK66J -HCmSoMeTDVmFxFYQgMuErK5xRHnyGrYVFGNeu4jTCjYAC2lG3GmRJEmEA4VCNMq05AqlUCH6jNqw -qVWbVLOyAspwataoOF2KBYD0q9mvV69RfHpW6Ma2KDGyhUsXbtm6EbcGxevwqc+RfLsuvBsYG0eR -bwsLBMBx67WAACH5BAUQANgALAEABwAoACsAAAj+ALEJHEiwoEFWK6wYXMiwoUCEAwEEsqLQoUWG -rKwEAsCK1cMVF0MuvLYiUKAV165h8yhS5MSVAK5xxLaCZcuLCFNiC7QTm0aRKYMKHZqyps+JIF/e -DElx54oVAJ5G1RjI5tKCCK0A0Miq6rWuUBNavTrQCiuiQnluJSuQZyC0cK0YXToRrt1rJ8dazHj3 -LkKeIlH29SsxpEm2AzeGFDy4b8m9FQluBEC5MuW2ljmevPi28WAAFiMjbql0tEGTVFnqNX2y5E/K -JkGbFiixJtrNgEcjNIo0ocmUcmc33Viyo9qSKnOzrTmx89CTGmUv5zm4NUiyPD02ZkVZtEvGnmVU -whaJcCZdn0/NYpzsPHxQkF/lPk0PNepOtFeFduwKVqPd2QZdB2BDOZk3oEObHeiQTAqG5l2DBHEH -IUMkPQhhTMlNSFBCRa12oEceJajhThKpZFNAADs= - - -----=_NextPart_11de424d3d872ac110be4ecfffc45411-- - - diff --git a/tests/data/mails/uid-02.eml b/tests/data/mails/uid-02.eml deleted file mode 100644 index 5c3a73f39..000000000 --- a/tests/data/mails/uid-02.eml +++ /dev/null @@ -1,16 +0,0 @@ -MIME-Version: 1.0 -From: test@domain.com -To: test@domain.com -Subject: TEST MESSAGE -Content-Type: text/plain; charset="UTF-8" -Content-Transfer-Encoding: 8bit -Date: Wed, 28 Apr 2010 02:04:20 -0700 -Message-ID: <16800462619664130307453@TEST-TEST> - -TEST - -. - -. MESSGAE - -BODY diff --git a/tests/data/mails/uid-03.eml b/tests/data/mails/uid-03.eml deleted file mode 100644 index 71d3108a5..000000000 --- a/tests/data/mails/uid-03.eml +++ /dev/null @@ -1,18 +0,0 @@ -Envelope-to: webmail@test.com -Delivery-date: Tue, 15 Jun 2010 22:48:56 -0700 -Received: from [84.51.195.105] (helo=local) - by mail.test.com with esmtpa (Exim 4.71) - (envelope-from ) - id 1OOlUe-0002EQ-IJ - for webmail@test.com; Tue, 15 Jun 2010 22:48:56 -0700 -Date: Wed, 16 Jun 2010 09:48:42 +0400 -From: sss -X-Mailer: The Bat! (v4.0.24) Professional -Reply-To: sss -X-Priority: 3 (Normal) -Message-ID: <708372127.20100616094842@test.com> -To: webmail@test.com -Subject: =?iso-8859-5?B?v+DY4eLj39DvIA==?= =?iso-8859-5?B?2iDg0NHe4tUg4Q==?= =?iso-8859-5?B?3iDh2+PW0d7ZIA==?= =?iso-8859-5?B?V2luZG93cyBMaQ==?= =?iso-8859-5?B?dmUgSG90bWFpbA==?= -MIME-Version: 1.0 -Content-Type: text/plain; charset=windows-1251 -Content-Transfer-Encoding: 8bit diff --git a/tests/data/mails/uid-04.eml b/tests/data/mails/uid-04.eml deleted file mode 100644 index e5f692188..000000000 --- a/tests/data/mails/uid-04.eml +++ /dev/null @@ -1,55 +0,0 @@ -MIME-Version: 1.0 -Message-ID: <45a92f7.0e89f233c32fbd2b9d191d4cb28775c9@domain.com> -From: from@domain.com -To: test@domain.com -Subject: TEST HTML MESSAGE WITH ATTACHMENT -Date: Thu, 6 May 2010 07:26:37 -0700 (PDT) -Content-Type: multipart/mixed; - boundary="--=_NextPart_11de424d3d872ac110be4ecfffc45411" - -----=_NextPart_11de424d3d872ac110be4ecfffc45411 -Content-Type: text/html; - charset="utf-8" -Content-Transfer-Encoding: Quoted-Printable - -TEST HTML MESSAGE WITH ATTACHMENT - -----=_NextPart_11de424d3d872ac110be4ecfffc45411 -Content-Type: image/gif; - name="av-7.gif" -Content-Transfer-Encoding: base64 -Content-Disposition: attachment; - filename="av-7.gif" - -R0lGODdhMgAyAOfXAAAAADMAAGYAAJkAAMwAAP8AAAAzADMzAGYzAJkzAMwzAP8zAABmADNmAGZm -AJlmAMxmAP9mAACZADOZAGaZAJmZAMyZAP+ZAADMADPMAGbMAJnMAMzMAP/MAAD/ADP/AGb/AJn/ -AMz/AP//AAAAMzMAM2YAM5kAM8wAM/8AMwAzMzMzM2YzM5kzM8wzM/8zMwBmMzNmM2ZmM5lmM8xm -JkGbFiixJtrNgEcjNIo0ocmUcmc33Viyo9qSKnOzrTmx89CTGmUv5zm4NUiyPD02ZkVZtEvGnmVU -whaJcCZdn0/NYpzsPHxQkF/lPk0PNepOtFeFduwKVqPd2QZdB2BDOZk3oEObHeiQTAqG5l2DBHEH -IUMkPQhhTMlNSFBCRa12oEceJajhThKpZFNAADs= - - -----=_NextPart_11de424d3d872ac110be4ecfffc45411 -Content-Type: image/gif; -Content-Transfer-Encoding: base64 - -R0lGODdhMgAyAOfXAAAAADMAAGYAAJkAAMwAAP8AAAAzADMzAGYzAJkzAMwzAP8zAABmADNmAGZm -AJlmAMxmAP9mAACZADOZAGaZAJmZAMyZAP+ZAADMADPMAGbMAJnMAMzMAP/MAAD/ADP/AGb/AJn/ -AMz/AP//AAAAMzMAM2YAM5kAM8wAM/8AMwAzMzMzM2YzM5kzM8wzM/8zMwBmMzNmM2ZmM5lmM8xm -JkGbFiixJtrNgEcjNIo0ocmUcmc33Viyo9qSKnOzrTmx89CTGmUv5zm4NUiyPD02ZkVZtEvGnmVU -whaJcCZdn0/NYpzsPHxQkF/lPk0PNepOtFeFduwKVqPd2QZdB2BDOZk3oEObHeiQTAqG5l2DBHEH -IUMkPQhhTMlNSFBCRa12oEceJajhThKpZFNAADs= - - -----=_NextPart_11de424d3d872ac110be4ecfffc45411 -Content-Type: image/png -Content-Transfer-Encoding: Quoted-Printable -Content-Disposition: attachment; - filename*0*=111; filename*1*=222.gif; - -TEST HTML MESSAGE WITH ATTACHMENT - - -----=_NextPart_11de424d3d872ac110be4ecfffc45411-- - - diff --git a/tests/data/mails/uid-05.eml b/tests/data/mails/uid-05.eml deleted file mode 100644 index 07bd6be69..000000000 --- a/tests/data/mails/uid-05.eml +++ /dev/null @@ -1,54 +0,0 @@ -From: alex -To: alex -Date: Thu, 22 Nov 2013 09:42:17 +0100 -Subject: - -------1---------2---------3---------4---------5---------1---------2---------3---------4---------5---------1---------2---------3---------4---------5---------1---------2---------3---------4---------5---------1---------2---------3---------4---------5----... -Thread-Topic: - -------1---------2---------3---------4---------5---------1---------2---------3---------4---------5---------1---------2---------3---------4---------5---------1---------2---------3---------4---------5---------1---------2---------3---------4---------5----... -Message-ID: <50ADE569.2000807@local> -Accept-Language: de-DE -Content-Language: de-DE -X-MS-Has-Attach: -X-MS-TNEF-Correlator: -user-agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:16.0) Gecko/20121026 - Thunderbird/16.0.2 -Content-Type: multipart/alternative; - boundary="_000_50ADE5692000807omninetlocal_" -MIME-Version: 1.0 - ---_000_50ADE5692000807omninetlocal_ -Content-Type: text/plain; charset="iso-8859-15" -Content-Transfer-Encoding: quoted-printable - -Test --------1---------2---------3---------4---------5---------1---------2-------= ---3---------4---------5---------1---------2---------3---------4---------5--= --------1---------2---------3---------4---------5---------1---------2-------= ---3---------4---------5---------1---------2---------3---------4---------5-- - ---_000_50ADE5692000807omninetlocal_ -Content-Type: text/html; charset="iso-8859-15" -Content-Transfer-Encoding: quoted-printable - - - - - - - Test
- - -------1---------2---------3---------4---------5---------1-------= ---2---------3---------4---------5---------1---------2---------3---------4--= --------5---------1---------2---------3---------4---------5---------1-------= ---2---------3---------4---------5---------1---------2---------3---------4--= --------5-- - HTML - - HTML - - - ---_000_50ADE5692000807omninetlocal_-- diff --git a/tests/lib/MailSo/Base/LinkFinderTest.php b/tests/lib/MailSo/Base/LinkFinderTest.php deleted file mode 100644 index efd189626..000000000 --- a/tests/lib/MailSo/Base/LinkFinderTest.php +++ /dev/null @@ -1,96 +0,0 @@ -object = \MailSo\Base\LinkFinder::NewInstance(); - } - - protected function tearDown() - { - $this->object = null; - } - - public function testNewInstance() - { - $this->assertTrue($this->object instanceof \MailSo\Base\LinkFinder); - } - - public function testClear() - { - $this->object->Text('111'); - $this->assertEquals('111', $this->object->CompileText()); - $this->object->Clear(); - $this->assertEquals('', $this->object->CompileText()); - } - - public function testText() - { - $this->object->Text('222'); - $this->assertEquals('222', $this->object->CompileText()); - } - - public function testLinkWrapper() - { - $this->object - ->Text('333 http://domain.com 333') - ->LinkWrapper(function ($sLink) { - return '!'.$sLink.'!'; - }) - ; - - $this->assertEquals('333 !http://domain.com! 333', $this->object->CompileText()); - } - - public function testMailWrapper() - { - $this->object - ->Text('444 user@domain.com 444') - ->MailWrapper(function ($sMail) { - return '!'.$sMail.'!'; - }) - ; - - $this->assertEquals('444 !user@domain.com! 444', $this->object->CompileText()); - } - - public function testUseDefaultWrappers() - { - $this->object - ->Text('555 http://domain.com user@domain.com 555') - ->UseDefaultWrappers() - ; - - $this->assertEquals('555 http://domain.com user@domain.com 555', - $this->object->CompileText()); - - $this->object->UseDefaultWrappers(true); - - $this->assertEquals('555 http://domain.com user@domain.com 555', - $this->object->CompileText()); - } - - public function testCompileText() - { - $this->object - ->Text('777 http://domain.com user@domain.com <> 777') - ->LinkWrapper(function ($sLink) { - return '~'.$sLink.'~'; - }) - ->MailWrapper(function ($sMail) { - return '~'.$sMail.'~'; - }) - ; - - $this->assertEquals('777 ~http://domain.com~ ~user@domain.com~ <> 777', $this->object->CompileText(true)); - $this->assertEquals('777 ~http://domain.com~ ~user@domain.com~ <> 777', $this->object->CompileText(false)); - } -} diff --git a/tests/lib/MailSo/Imap/ImapClientTest.php b/tests/lib/MailSo/Imap/ImapClientTest.php deleted file mode 100644 index 92d994029..000000000 --- a/tests/lib/MailSo/Imap/ImapClientTest.php +++ /dev/null @@ -1,85 +0,0 @@ -TestSetValues($rConnect, array('NAMESPACE')); - $oResult = $oImapClient->GetNamespace(); - - $this->assertTrue($oResult instanceof \MailSo\Imap\NamespaceResult); - } - - public function testQuota() - { - $rConnect = \MailSo\Base\StreamWrappers\Test::CreateStream( - '* QUOTAROOT "INBOX" ""'.self::CRLF. - '* QUOTA "" (STORAGE 55163 10511217)'.self::CRLF. - 'TAG1 OK Success'.self::CRLF - ); - - $oImapClient = \MailSo\Imap\ImapClient::NewInstance()->TestSetValues($rConnect, array('QUOTA')); - - $aResult = $oImapClient->Quota(); - $this->assertTrue(is_array($aResult)); - $this->assertEquals(4, count($aResult)); - $this->assertEquals(55163, $aResult[0]); - $this->assertEquals(10511217, $aResult[1]); - } - - public function testFolderList() - { - $rConnect = \MailSo\Base\StreamWrappers\Test::CreateStream( -'* LIST (\Noselect) "/" 0'.self::CRLF. -'* LIST (\UnMarked) "/" 0/1'.self::CRLF. -'* LIST (\Noselect) "/" 1'.self::CRLF. -'* LIST (\Noselect) "/" 1/2'.self::CRLF. -'* LIST (\UnMarked) "/" 1/2/3'.self::CRLF. -'* LIST (\UnMarked \Inbox) "/" INBOX'.self::CRLF. -'* LIST (\UnMarked) "/" "INBOX/XXX XXX"'.self::CRLF. -'* LIST (\UnMarked) "/" &-BT,MAQBDoEM'.self::CRLF. -'* LIST (\UnMarked) "NIL" NILDelimiteFolder'.self::CRLF. -'* LIST (\UnMarked) "" EmptyDelimiteFolder'.self::CRLF. -'TAG1 OK Success'.self::CRLF - ); - - $oImapClient = \MailSo\Imap\ImapClient::NewInstance()->TestSetValues($rConnect); - - $aResult = $oImapClient->FolderList(); - $this->assertTrue(is_array($aResult) && 0 < count($aResult)); - $this->assertTrue($aResult[0] instanceof \MailSo\Imap\Folder); - - $this->assertEquals('0', $aResult[0]->FullNameRaw()); - $this->assertEquals('0', $aResult[0]->NameRaw()); - $this->assertEquals('0/1', $aResult[1]->FullNameRaw()); - $this->assertEquals('1', $aResult[1]->NameRaw()); - $this->assertEquals('1', $aResult[2]->FullNameRaw()); - $this->assertEquals('1/2', $aResult[3]->FullNameRaw()); - $this->assertEquals('1/2/3', $aResult[4]->FullNameRaw()); - $this->assertEquals('3', $aResult[4]->NameRaw()); - $this->assertEquals('INBOX', $aResult[5]->FullNameRaw()); - $this->assertEquals('INBOX/XXX XXX', $aResult[6]->FullNameRaw()); - $this->assertEquals('XXX XXX', $aResult[6]->NameRaw()); - $this->assertEquals('&-BT,MAQBDoEM', $aResult[7]->FullNameRaw()); - - $this->assertTrue($aResult[5] instanceof \MailSo\Imap\Folder); - $this->assertEquals('/', $aResult[5]->Delimiter()); - $this->assertEquals(2, count($aResult[5]->FlagsLowerCase())); - $this->assertTrue(in_array('\inbox', $aResult[5]->FlagsLowerCase())); - - $this->assertTrue($aResult[8] instanceof \MailSo\Imap\Folder); - $this->assertEquals('.', $aResult[8]->Delimiter()); - - $this->assertTrue($aResult[9] instanceof \MailSo\Imap\Folder); - $this->assertEquals('.', $aResult[8]->Delimiter()); - } -} diff --git a/tests/lib/MailSo/Mime/EmailCollectionTest.php b/tests/lib/MailSo/Mime/EmailCollectionTest.php deleted file mode 100644 index 382a16381..000000000 --- a/tests/lib/MailSo/Mime/EmailCollectionTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertEquals(1, $oMails->Count()); - } - - public function testNewInstance1() - { - $oMails = \MailSo\Mime\EmailCollection::NewInstance('User Name , User D\'Name , "User Name" '); - $this->assertEquals(3, $oMails->Count()); - } -} diff --git a/tests/lib/MailSo/Mime/EmailTest.php b/tests/lib/MailSo/Mime/EmailTest.php deleted file mode 100644 index e4a777e16..000000000 --- a/tests/lib/MailSo/Mime/EmailTest.php +++ /dev/null @@ -1,131 +0,0 @@ -assertEquals('admin@example.com', $oEmail->GetEmail()); - $this->assertEquals('Administrator', $oEmail->GetDisplayName()); - $this->assertEquals('Remark', $oEmail->GetRemark()); - $this->assertEquals('admin', $oEmail->GetAccountName()); - $this->assertEquals('example.com', $oEmail->GetDomain()); - $this->assertEquals('"Administrator" (Remark)', $oEmail->ToString()); - $this->assertEquals(array('Administrator', 'admin@example.com', 'Remark'), $oEmail->ToArray()); - } - - public function testNewInstance1() - { - $oEmail = \MailSo\Mime\Email::NewInstance('admin@example.com'); - $this->assertEquals('admin@example.com', $oEmail->GetEmail()); - $this->assertEquals('', $oEmail->GetDisplayName()); - $this->assertEquals('', $oEmail->GetRemark()); - $this->assertEquals('admin@example.com', $oEmail->ToString()); - $this->assertEquals(array('', 'admin@example.com', ''), $oEmail->ToArray()); - } - - public function testNewInstance2() - { - $oEmail = \MailSo\Mime\Email::NewInstance('admin@example.com', 'Administrator'); - $this->assertEquals('admin@example.com', $oEmail->GetEmail()); - $this->assertEquals('Administrator', $oEmail->GetDisplayName()); - $this->assertEquals('', $oEmail->GetRemark()); - $this->assertEquals('"Administrator" ', $oEmail->ToString()); - $this->assertEquals(array('Administrator', 'admin@example.com', ''), $oEmail->ToArray()); - } - - public function testNewInstance3() - { - $oEmail = \MailSo\Mime\Email::NewInstance('admin@example.com', '', 'Remark'); - $this->assertEquals('admin@example.com', $oEmail->GetEmail()); - $this->assertEquals('', $oEmail->GetDisplayName()); - $this->assertEquals('Remark', $oEmail->GetRemark()); - $this->assertEquals(' (Remark)', $oEmail->ToString()); - $this->assertEquals(array('', 'admin@example.com', 'Remark'), $oEmail->ToArray()); - } - - /** - * @expectedException \MailSo\Base\Exceptions\InvalidArgumentException - */ - public function testNewInstance4() - { - \MailSo\Mime\Email::NewInstance(''); - } - - public function testParse1() - { - $oEmail = \MailSo\Mime\Email::Parse('help@example.com'); - $this->assertEquals('help@example.com', $oEmail->GetEmail()); - - $oEmail = \MailSo\Mime\Email::Parse(''); - $this->assertEquals('help@example.com', $oEmail->GetEmail()); - } - - public function testParse2() - { - $oEmail = \MailSo\Mime\Email::Parse('"Тест" (Ремарка)'); - $this->assertEquals('"Тест" (Ремарка)', $oEmail->ToString()); - $this->assertEquals('"=?utf-8?B?0KLQtdGB0YI=?=" (=?utf-8?B?0KDQtdC80LDRgNC60LA=?=)', $oEmail->ToString(true)); - } - - /** - * @expectedException \MailSo\Base\Exceptions\InvalidArgumentException - */ - public function testParse5() - { - \MailSo\Mime\Email::Parse(''); - } - - /** - * @expectedException \MailSo\Base\Exceptions\InvalidArgumentException - */ - public function testParse6() - { - \MailSo\Mime\Email::Parse('example.com'); - } - - public function testParsePuny1() - { - $oMail = \MailSo\Mime\Email::Parse('help@xn--d1acufc.xn--p1ai'); - $this->assertEquals('help@xn--d1acufc.xn--p1ai', $oMail->ToString()); - $this->assertEquals('help@домен.рф', $oMail->ToString(false, true)); - } - - public function testParsePuny2() - { - $oMail = \MailSo\Mime\Email::Parse('help@домен.рф'); - $this->assertEquals('help@xn--d1acufc.xn--p1ai', $oMail->ToString()); - $this->assertEquals('help@домен.рф', $oMail->ToString(false, true)); - } - - public static function providerForParse() - { - return array( - array('test ', - array('test', 'help@example.com', '')), - array('test', - array('test', 'help@example.com', '')), - array('test< help@example.com >', - array('test', 'help@example.com', '')), - array(' (Remark)', - array('', 'help@example.com', 'Remark')), - array('"New \" Admin" (Rem)', - array('New " Admin', 'help@example.com', 'Rem')), - array('"Тест" (Ремарка)', - array('Тест', 'help@example.com', 'Ремарка')), - array('Microsoft Outlook', - array('Microsoft Outlook', 'microsoftexchange329e71ec88ae4615bbc36ab6ce41109e@ppth.private', '')), - ); - } - - /** - * @dataProvider providerForParse - */ - public function testParseWithProvider($sValue, $aResult) - { - $oMail = \MailSo\Mime\Email::Parse($sValue); - $this->assertEquals($aResult, $oMail->ToArray()); - } -} diff --git a/vendors/Autolinker/Autolinker.js b/vendors/Autolinker/Autolinker.js index bfd4af1de..1e04834c1 100644 --- a/vendors/Autolinker/Autolinker.js +++ b/vendors/Autolinker/Autolinker.js @@ -1,25 +1,28 @@ -/*! - * Autolinker.js - * 0.11.0 - * - * Copyright(c) 2014 Gregory Jacobs - * MIT Licensed. http://www.opensource.org/licenses/mit-license.php - * - * https://github.com/gregjacobs/Autolinker.js - */ -/*global define, module */ -/*jshint undef:true, smarttabs:true */ -// Set up Autolinker appropriately for the environment. -( function( root, factory ) { - if( typeof define === 'function' && define.amd ) { - define( factory ); // Define as AMD module if an AMD loader is present (ex: RequireJS). - } else if( typeof exports !== 'undefined' ) { - module.exports = factory(); // Define as CommonJS module for Node.js, if available. - } else { - root.Autolinker = factory(); // Finally, define as a browser global if no module loader. - } -}( this, function() { - +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define([], function () { + return (root.returnExportsGlobal = factory()); + }); + } else if (typeof exports === 'object') { + // Node. Does not work with strict CommonJS, but + // only CommonJS-like enviroments that support module.exports, + // like Node. + module.exports = factory(); + } else { + root['Autolinker'] = factory(); + } +}(this, function () { + + /*! + * Autolinker.js + * 0.15.2 + * + * Copyright(c) 2015 Gregory Jacobs + * MIT Licensed. http://www.opensource.org/licenses/mit-license.php + * + * https://github.com/gregjacobs/Autolinker.js + */ /** * @class Autolinker * @extends Object @@ -50,67 +53,124 @@ * } ); * // produces: 'Joe went to yahoo.com' * + * + * ## Custom Replacements of Links + * + * If the configuration options do not provide enough flexibility, a {@link #replaceFn} may be provided to fully customize + * the output of Autolinker. This function is called once for each URL/Email/Twitter handle match that is encountered. + * + * For example: + * + * var input = "..."; // string with URLs, Email Addresses, and Twitter Handles + * + * var linkedText = Autolinker.link( input, { + * replaceFn : function( autolinker, match ) { + * console.log( "href = ", match.getAnchorHref() ); + * console.log( "text = ", match.getAnchorText() ); + * + * switch( match.getType() ) { + * case 'url' : + * console.log( "url: ", match.getUrl() ); + * + * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes + * tag.setAttr( 'rel', 'nofollow' ); + * tag.addClass( 'external-link' ); + * + * return tag; + * + * } else { + * return true; // let Autolinker perform its normal anchor tag replacement + * } + * + * case 'email' : + * var email = match.getEmail(); + * console.log( "email: ", email ); + * + * if( email === "my@own.address" ) { + * return false; // don't auto-link this particular email address; leave as-is + * } else { + * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`) + * } + * + * case 'twitter' : + * var twitterHandle = match.getTwitterHandle(); + * console.log( twitterHandle ); + * + * return '' + twitterHandle + ''; + * } + * } + * } ); + * + * + * The function may return the following values: + * + * - `true` (Boolean): Allow Autolinker to replace the match as it normally would. + * - `false` (Boolean): Do not replace the current match at all - leave as-is. + * - Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for + * the match. + * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify an HTML tag before writing out its HTML text. + * * @constructor * @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map). */ var Autolinker = function( cfg ) { - cfg = cfg || {}; - - // Assign the properties of `cfg` onto the Autolinker instance - for( var prop in cfg ) - if( cfg.hasOwnProperty( prop ) ) this[ prop ] = cfg[ prop ]; + Autolinker.Util.assign( this, cfg ); // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs. + + this.matchValidator = new Autolinker.MatchValidator(); }; - - + + Autolinker.prototype = { constructor : Autolinker, // fix constructor property - - /** - * @cfg {Boolean} newWindow - * - * `true` if the links should open in a new window, `false` otherwise. - */ - newWindow : true, - - /** - * @cfg {Boolean} stripPrefix - * - * `true` if 'http://' or 'https://' and/or the 'www.' should be stripped from the beginning of links, `false` otherwise. - */ - stripPrefix : true, - - /** - * @cfg {Number} truncate - * - * A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of - * a link. If the URL/email/twitter is over this number of characters, it will be truncated to this length by - * adding a two period ellipsis ('..') into the middle of the string. - * - * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look - * something like this: 'http://www...th/to/a/file' - */ - - /** - * @cfg {Boolean} twitter - * - * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be. - */ - twitter : true, - - /** - * @cfg {Boolean} email - * - * `true` if email addresses should be automatically linked, `false` if they should not be. - */ - email : true, - + /** * @cfg {Boolean} urls * * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be. */ urls : true, - + + /** + * @cfg {Boolean} email + * + * `true` if email addresses should be automatically linked, `false` if they should not be. + */ + email : true, + + /** + * @cfg {Boolean} twitter + * + * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be. + */ + twitter : true, + + /** + * @cfg {Boolean} newWindow + * + * `true` if the links should open in a new window, `false` otherwise. + */ + newWindow : true, + + /** + * @cfg {Boolean} stripPrefix + * + * `true` if 'http://' or 'https://' and/or the 'www.' should be stripped from the beginning of URL links' text, + * `false` otherwise. + */ + stripPrefix : true, + + /** + * @cfg {Number} truncate + * + * A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of + * a link. If the URL/email/twitter is over this number of characters, it will be truncated to this length by + * adding a two period ellipsis ('..') to the end of the string. + * + * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look + * something like this: 'yahoo.com/some/long/pat..' + */ + /** * @cfg {String} className * @@ -119,13 +179,39 @@ * * For example, if this config is provided as "myLink", then: * - * 1) URL links will have the CSS classes: "myLink myLink-url" - * 2) Email links will have the CSS classes: "myLink myLink-email", and - * 3) Twitter links will have the CSS classes: "myLink myLink-twitter" + * - URL links will have the CSS classes: "myLink myLink-url" + * - Email links will have the CSS classes: "myLink myLink-email", and + * - Twitter links will have the CSS classes: "myLink myLink-twitter" */ className : "", - - + + /** + * @cfg {Function} replaceFn + * + * A function to individually process each URL/Email/Twitter match found in the input string. + * + * See the class's description for usage. + * + * This function is called with the following parameters: + * + * @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may be used to retrieve child objects from (such + * as the instance's {@link #getTagBuilder tag builder}). + * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which can be used to retrieve information about the + * {@link Autolinker.match.Url URL}/{@link Autolinker.match.Email email}/{@link Autolinker.match.Twitter Twitter} + * match that the `replaceFn` is currently processing. + */ + + + /** + * @private + * @property {RegExp} htmlCharacterEntitiesRegex + * + * The regular expression that matches common HTML character entities. + * + * Ignoring & as it could be part of a query string -- handling it separately. + */ + htmlCharacterEntitiesRegex: /( | |<|<|>|>|"|"|')/gi, + /** * @private * @property {RegExp} matcherRegex @@ -134,138 +220,121 @@ * * This regular expression has the following capturing groups: * - * 1. Group that is used to determine if there is a Twitter handle match (i.e. @someTwitterUser). Simply check for its + * 1. Group that is used to determine if there is a Twitter handle match (i.e. \@someTwitterUser). Simply check for its * existence to determine if there is a Twitter handle match. The next couple of capturing groups give information * about the Twitter handle match. - * 2. The whitespace character before the @sign in a Twitter handle. This is needed because there are no lookbehinds in + * 2. The whitespace character before the \@sign in a Twitter handle. This is needed because there are no lookbehinds in * JS regular expressions, and can be used to reconstruct the original string in a replace(). * 3. The Twitter handle itself in a Twitter match. If the match is '@someTwitterUser', the handle is 'someTwitterUser'. * 4. Group that matches an email address. Used to determine if the match is an email address, as well as holding the full * address. Ex: 'me@my.com' * 5. Group that matches a URL in the input text. Ex: 'http://google.com', 'www.google.com', or just 'google.com'. * This also includes a path, url parameters, or hash anchors. Ex: google.com/path/to/file?q1=1&q2=2#myAnchor - * 6. A protocol-relative ('//') match for the case of a 'www.' prefixed URL. Will be an empty string if it is not a + * 6. Group that matches a protocol URL (i.e. 'http://google.com'). This is used to match protocol URLs with just a single + * word, like 'http://localhost', where we won't double check that the domain name has at least one '.' in it. + * 7. A protocol-relative ('//') match for the case of a 'www.' prefixed URL. Will be an empty string if it is not a * protocol-relative match. We need to know the character before the '//' in order to determine if it is a valid match * or the // was in a string we don't want to auto-link. - * 7. A protocol-relative ('//') match for the case of a known TLD prefixed URL. Will be an empty string if it is not a + * 8. A protocol-relative ('//') match for the case of a known TLD prefixed URL. Will be an empty string if it is not a * protocol-relative match. See #6 for more info. */ matcherRegex : (function() { var twitterRegex = /(^|[^\w])@(\w{1,15})/, // For matching a twitter handle. Ex: @gregory_jacobs - + emailRegex = /(?:[\-;:&=\+\$,\w\.]+@)/, // something@ for email addresses (a.k.a. local-part) - - protocolRegex = /(?:[A-Za-z]{3,9}:(?:\/\/)?)/, // match protocol, allow in format http:// or mailto: + + protocolRegex = /(?:[A-Za-z][-.+A-Za-z0-9]+:(?![A-Za-z][-.+A-Za-z0-9]+:\/\/)(?!\d+\/?)(?:\/\/)?)/, // match protocol, allow in format "http://" or "mailto:". However, do not match the first part of something like 'link:http://www.google.com' (i.e. don't match "link:"). Also, make sure we don't interpret 'google.com:8000' as if 'google.com' was a protocol here (i.e. ignore a trailing port number in this regex) wwwRegex = /(?:www\.)/, // starting with 'www.' domainNameRegex = /[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/, // anything looking at all like a domain, non-unicode domains, not ending in a period tldRegex = /\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/, // match our known top level domains (TLDs) - - // Allow optional path, query string, and hash anchor, not ending in the following characters: "!:,.;" + + // Allow optional path, query string, and hash anchor, not ending in the following characters: "?!:,.;" // http://blog.codinghorror.com/the-problem-with-urls/ - urlSuffixRegex = /(?:[\-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|])?/; // note: optional part of the full regex - + urlSuffixRegex = /[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]?!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]]/; + return new RegExp( [ '(', // *** Capturing group $1, which can be used to check for a twitter handle match. Use group $3 for the actual twitter handle though. $2 may be used to reconstruct the original string in a replace() // *** Capturing group $2, which matches the whitespace character before the '@' sign (needed because of no lookbehinds), and // *** Capturing group $3, which matches the actual twitter handle twitterRegex.source, ')', - + '|', - + '(', // *** Capturing group $4, which is used to determine an email match emailRegex.source, domainNameRegex.source, tldRegex.source, ')', - + '|', - + '(', // *** Capturing group $5, which is used to match a URL '(?:', // parens to cover match for protocol (optional), and domain - '(?:', // non-capturing paren for a protocol-prefixed url (ex: http://google.com) + '(', // *** Capturing group $6, for a protocol-prefixed url (ex: http://google.com) protocolRegex.source, domainNameRegex.source, ')', - + '|', - + '(?:', // non-capturing paren for a 'www.' prefixed url (ex: www.google.com) - '(.?//)?', // *** Capturing group $6 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character + '(.?//)?', // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character wwwRegex.source, domainNameRegex.source, ')', - + '|', - + '(?:', // non-capturing paren for known a TLD url (ex: google.com) - '(.?//)?', // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character + '(.?//)?', // *** Capturing group $8 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character domainNameRegex.source, tldRegex.source, ')', ')', - - urlSuffixRegex.source, // match for path, query string, and/or hash anchor + + '(?:' + urlSuffixRegex.source + ')?', // match for path, query string, and/or hash anchor - optional ')' ].join( "" ), 'gi' ); } )(), - - /** - * @private - * @property {RegExp} protocolRelativeRegex - * - * The regular expression used to find protocol-relative URLs. A protocol-relative URL is, for example, "//yahoo.com" - * - * This regular expression needs to match the character before the '//', in order to determine if we should actually - * autolink a protocol-relative URL. For instance, we want to autolink something like "//google.com", but we - * don't want to autolink something like "abc//google.com" - */ - protocolRelativeRegex : /(.)?\/\//, - - /** - * @private - * @property {RegExp} htmlRegex - * - * The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and - * attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html. - * - * Capturing groups: - * - * 1. If it is an end tag, this group will have the '/'. - * 2. The tag name. - */ - htmlRegex : (function() { - var tagNameRegex = /[0-9a-zA-Z:]+/, - attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/, // the unicode range accounts for excluding control chars, and the delete char - attrValueRegex = /(?:".*?"|'.*?'|[^'"=<>`\s]+)/; // double quoted, single quoted, or unquoted attribute values - - return new RegExp( [ - '<(/)?', // Beginning of a tag. Either '<' for a start tag, or '' - '>' - ].join( "" ), 'g' ); - } )(), /** * @private - * @property {RegExp} urlPrefixRegex + * @property {RegExp} charBeforeProtocolRelMatchRegex * - * A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs. + * The regular expression used to retrieve the character before a protocol-relative URL match. + * + * This is used in conjunction with the {@link #matcherRegex}, which needs to grab the character before a protocol-relative + * '//' due to the lack of a negative look-behind in JavaScript regular expressions. The character before the match is stripped + * from the URL. */ - urlPrefixRegex: /^(https?:\/\/)?(www\.)?/i, - - + charBeforeProtocolRelMatchRegex : /^(.)?\/\//, + + /** + * @private + * @property {Autolinker.MatchValidator} matchValidator + * + * The MatchValidator object, used to filter out any false positives from the {@link #matcherRegex}. See + * {@link Autolinker.MatchValidator} for details. + */ + + /** + * @private + * @property {Autolinker.HtmlParser} htmlParser + * + * The HtmlParser instance used to skip over HTML tags, while finding text nodes to process. This is lazily instantiated + * in the {@link #getHtmlParser} method. + */ + + /** + * @private + * @property {Autolinker.AnchorTagBuilder} tagBuilder + * + * The AnchorTagBuilder instance used to build the URL/email/Twitter replacement anchor tags. This is lazily instantiated + * in the {@link #getTagBuilder} method. + */ + + /** * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML. * Does not link URLs found within HTML tags. @@ -273,79 +342,111 @@ * For instance, if given the text: `You should go to http://www.yahoo.com`, then the result * will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` * - * @method link - * @param {String} textOrHtml The HTML or text to link URLs, email addresses, and Twitter handles within. - * @return {String} The HTML, with URLs/emails/twitter handles automatically linked. + * This method finds the text around any HTML elements in the input `textOrHtml`, which will be the text that is processed. + * Any original HTML elements will be left as-is, as well as the text that is already wrapped in anchor (<a>) tags. + * + * @param {String} textOrHtml The HTML or text to link URLs, email addresses, and Twitter handles within (depending on if + * the {@link #urls}, {@link #email}, and {@link #twitter} options are enabled). + * @return {String} The HTML, with URLs/emails/Twitter handles automatically linked. */ link : function( textOrHtml ) { - return this.processHtml( textOrHtml ); - }, - - - /** - * Processes the given HTML to auto-link URLs/emails/Twitter handles. - * - * Finds the text around any HTML elements in the input `html`, which will be the text that is processed. - * Any original HTML elements will be left as-is, as well as the text that is already wrapped in anchor tags. - * - * @private - * @method processHtml - * @param {String} html The input text or HTML to process in order to auto-link. - * @return {String} - */ - processHtml : function( html ) { - // Loop over the HTML string, ignoring HTML tags, and processing the text that lies between them, - // wrapping the URLs in anchor tags - var htmlRegex = this.htmlRegex, - currentResult, - inBetweenTagsText, - lastIndex = 0, - anchorTagStackCount = 0, + var me = this, // for closure + htmlParser = this.getHtmlParser(), + htmlCharacterEntitiesRegex = this.htmlCharacterEntitiesRegex, + anchorTagStackCount = 0, // used to only process text around anchor tags, and any inner text/html they may have resultHtml = []; - - while( ( currentResult = htmlRegex.exec( html ) ) !== null ) { - var tagText = currentResult[ 0 ], - tagName = currentResult[ 2 ], - isClosingTag = !!currentResult[ 1 ]; - - inBetweenTagsText = html.substring( lastIndex, currentResult.index ); - lastIndex = currentResult.index + tagText.length; - - // Process around anchor tags, and any inner text / html they may have - if( tagName === 'a' ) { - if( !isClosingTag ) { // it's the start tag - anchorTagStackCount++; - resultHtml.push( this.processTextNode( inBetweenTagsText ) ); - - } else { // it's the end tag - anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous tags by making sure the stack count never goes below 0 - if( anchorTagStackCount === 0 ) { - resultHtml.push( inBetweenTagsText ); // We hit the matching tag, simply add all of the text from the start tag to the end tag without linking it + + htmlParser.parse( textOrHtml, { + // Process HTML nodes in the input `textOrHtml` + processHtmlNode : function( tagText, tagName, isClosingTag ) { + if( tagName === 'a' ) { + if( !isClosingTag ) { // it's the start tag + anchorTagStackCount++; + } else { // it's the end tag + anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous tags by making sure the stack count never goes below 0 } } - - } else if( anchorTagStackCount === 0 ) { // not within an anchor tag, link the "in between" text - resultHtml.push( this.processTextNode( inBetweenTagsText ) ); - - } else { - // if we have a tag that is in between anchor tags (ex: google.com), - // just append the inner text - resultHtml.push( inBetweenTagsText ); + resultHtml.push( tagText ); // now add the text of the tag itself verbatim + }, + + // Process text nodes in the input `textOrHtml` + processTextNode : function( text ) { + if( anchorTagStackCount === 0 ) { + // If we're not within an tag, process the text node + var unescapedText = Autolinker.Util.splitAndCapture( text, htmlCharacterEntitiesRegex ); // split at HTML entities, but include the HTML entities in the results array + + for ( var i = 0, len = unescapedText.length; i < len; i++ ) { + var textToProcess = unescapedText[ i ], + processedTextNode = me.processTextNode( textToProcess ); + + resultHtml.push( processedTextNode ); + } + + } else { + // `text` is within an tag, simply append the text - we do not want to autolink anything + // already within an ... tag + resultHtml.push( text ); + } } - - resultHtml.push( tagText ); // now add the text of the tag itself verbatim - } - - // Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements. - if( lastIndex < html.length ) { - var processedTextNode = this.processTextNode( html.substring( lastIndex ) ); - resultHtml.push( processedTextNode ); - } - + } ); + return resultHtml.join( "" ); }, - - + + + /** + * Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance. + * + * @protected + * @return {Autolinker.HtmlParser} + */ + getHtmlParser : function() { + var htmlParser = this.htmlParser; + + if( !htmlParser ) { + htmlParser = this.htmlParser = new Autolinker.HtmlParser(); + } + + return htmlParser; + }, + + + /** + * Returns the {@link #tagBuilder} instance for this Autolinker instance, lazily instantiating it + * if it does not yet exist. + * + * This method may be used in a {@link #replaceFn} to generate the {@link Autolinker.HtmlTag HtmlTag} instance that + * Autolinker would normally generate, and then allow for modifications before returning it. For example: + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + * + * @return {Autolinker.AnchorTagBuilder} + */ + getTagBuilder : function() { + var tagBuilder = this.tagBuilder; + + if( !tagBuilder ) { + tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( { + newWindow : this.newWindow, + truncate : this.truncate, + className : this.className + } ); + } + + return tagBuilder; + }, + + /** * Process the text that lies inbetween HTML tags. This method does the actual wrapping of URLs with * anchor tags. @@ -355,228 +456,201 @@ * @return {String} The text with anchor tags auto-filled. */ processTextNode : function( text ) { - var me = this, // for closures - matcherRegex = this.matcherRegex, - enableTwitter = this.twitter, - enableEmailAddresses = this.email, - enableUrls = this.urls; - - return text.replace( matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7 ) { - var twitterMatch = $1, - twitterHandlePrefixWhitespaceChar = $2, // The whitespace char before the @ sign in a Twitter handle match. This is needed because of no lookbehinds in JS regexes - twitterHandle = $3, // The actual twitterUser (i.e the word after the @ sign in a Twitter handle match) - emailAddress = $4, // For both determining if it is an email address, and stores the actual email address - urlMatch = $5, // The matched URL string - protocolRelativeMatch = $6 || $7, // The '//' for a protocol-relative match, with the character that comes before the '//' - - prefixStr = "", // A string to use to prefix the anchor tag that is created. This is needed for the Twitter handle match - suffixStr = ""; // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked. - - // Early exits with no replacements for: - // 1) Disabled link types - // 2) URL matches which do not have at least have one period ('.') in the domain name (effectively skipping over - // matches like "abc:def") - // 3) A protocol-relative url match (a URL beginning with '//') whose previous character is a word character - // (effectively skipping over strings like "abc//google.com") - if( - ( twitterMatch && !enableTwitter ) || ( emailAddress && !enableEmailAddresses ) || ( urlMatch && !enableUrls ) || - ( urlMatch && urlMatch.indexOf( '.' ) === -1 ) || // At least one period ('.') must exist in the URL match for us to consider it an actual URL - ( urlMatch && /^[A-Za-z]{3,9}:/.test( urlMatch ) && !/:.*?[A-Za-z]/.test( urlMatch ) ) || // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0" - ( protocolRelativeMatch && /^[\w]\/\//.test( protocolRelativeMatch ) ) // a protocol-relative match which has a word character in front of it (so we can skip something like "abc//google.com") - ) { + var me = this; // for closure + + return text.replace( this.matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7, $8 ) { + var matchDescObj = me.processCandidateMatch( matchStr, $1, $2, $3, $4, $5, $6, $7, $8 ); // match description object + + // Return out with no changes for match types that are disabled (url, email, twitter), or for matches that are + // invalid (false positives from the matcherRegex, which can't use look-behinds since they are unavailable in JS). + if( !matchDescObj ) { return matchStr; - } - - - // Handle a closing parenthesis at the end of the match, and exclude it if there is not a matching open parenthesis - // in the match. This handles cases like the string "wikipedia.com/something_(disambiguation)" (which should be auto- - // linked, and when it is enclosed in parenthesis itself, such as: "(wikipedia.com/something_(disambiguation))" (in - // which the outer parens should *not* be auto-linked. - var lastChar = matchStr.charAt( matchStr.length - 1 ); - if( lastChar === ')' ) { - var openParensMatch = matchStr.match( /\(/g ), - closeParensMatch = matchStr.match( /\)/g ), - numOpenParens = ( openParensMatch && openParensMatch.length ) || 0, - numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0; - - if( numOpenParens < numCloseParens ) { - matchStr = matchStr.substr( 0, matchStr.length - 1 ); // remove the trailing ")" - suffixStr = ")"; // this will be added after the tag - } - } - - - var anchorHref = matchStr, // initialize both of these - anchorText = matchStr, // values as the full match - linkType; - // Process the urls that are found. We need to change URLs like "www.yahoo.com" to "http://www.yahoo.com" (or the browser - // will try to direct the user to "http://current-domain.com/www.yahoo.com"), and we need to prefix 'mailto:' to email addresses. - if( twitterMatch ) { - linkType = 'twitter'; - prefixStr = twitterHandlePrefixWhitespaceChar; - anchorHref = 'https://twitter.com/' + twitterHandle; - anchorText = '@' + twitterHandle; - - } else if( emailAddress ) { - linkType = 'email'; - anchorHref = 'mailto:' + emailAddress; - anchorText = emailAddress; - - } else { // url match - linkType = 'url'; - - if( protocolRelativeMatch ) { - // Strip off any protocol-relative '//' from the anchor text (leaving the previous non-word character - // intact, if there is one) - var protocolRelRegex = new RegExp( "^" + me.protocolRelativeRegex.source ), // for this one, we want to only match at the beginning of the string - charBeforeMatch = protocolRelativeMatch.match( protocolRelRegex )[ 1 ] || ""; - - prefixStr = charBeforeMatch + prefixStr; // re-add the character before the '//' to what will be placed before the tag - anchorHref = anchorHref.replace( protocolRelRegex, "//" ); // remove the char before the match for the href - anchorText = anchorText.replace( protocolRelRegex, "" ); // remove both the char before the match and the '//' for the anchor text - - } else if( !/^[A-Za-z]{3,9}:/i.test( anchorHref ) ) { - // url string doesn't begin with a protocol, assume http:// - anchorHref = 'http://' + anchorHref; - } + } else { + // Generate the replacement text for the match + var matchReturnVal = me.createMatchReturnVal( matchDescObj.match, matchDescObj.matchStr ); + return matchDescObj.prefixStr + matchReturnVal + matchDescObj.suffixStr; } - - // wrap the match in an anchor tag - var anchorTag = me.createAnchorTag( linkType, anchorHref, anchorText ); - return prefixStr + anchorTag + suffixStr; } ); }, - - + + /** - * Generates the actual anchor (<a>) tag to use in place of a source url/email/twitter link. + * Processes a candidate match from the {@link #matcherRegex}. + * + * Not all matches found by the regex are actual URL/email/Twitter matches, as determined by the {@link #matchValidator}. In + * this case, the method returns `null`. Otherwise, a valid Object with `prefixStr`, `match`, and `suffixStr` is returned. * * @private - * @param {"url"/"email"/"twitter"} linkType The type of link that an anchor tag is being generated for. - * @param {String} anchorHref The href for the anchor tag. - * @param {String} anchorText The anchor tag's text (i.e. what will be displayed). - * @return {String} The full HTML for the anchor tag. + * @param {String} matchStr The full match that was found by the {@link #matcherRegex}. + * @param {String} twitterMatch The matched text of a Twitter handle, if the match is a Twitter match. + * @param {String} twitterHandlePrefixWhitespaceChar The whitespace char before the @ sign in a Twitter handle match. This + * is needed because of no lookbehinds in JS regexes, and is need to re-include the character for the anchor tag replacement. + * @param {String} twitterHandle The actual Twitter user (i.e the word after the @ sign in a Twitter match). + * @param {String} emailAddressMatch The matched email address for an email address match. + * @param {String} urlMatch The matched URL string for a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to match + * something like 'http://localhost', where we won't double check that the domain name has at least one '.' in it. + * @param {String} wwwProtocolRelativeMatch The '//' for a protocol-relative match from a 'www' url, with the character that + * comes before the '//'. + * @param {String} tldProtocolRelativeMatch The '//' for a protocol-relative match from a TLD (top level domain) match, with + * the character that comes before the '//'. + * + * @return {Object} A "match description object". This will be `null` if the match was invalid, or if a match type is disabled. + * Otherwise, this will be an Object (map) with the following properties: + * @return {String} return.prefixStr The char(s) that should be prepended to the replacement string. These are char(s) that + * were needed to be included from the regex match that were ignored by processing code, and should be re-inserted into + * the replacement stream. + * @return {String} return.suffixStr The char(s) that should be appended to the replacement string. These are char(s) that + * were needed to be included from the regex match that were ignored by processing code, and should be re-inserted into + * the replacement stream. + * @return {String} return.matchStr The `matchStr`, fixed up to remove characters that are no longer needed (which have been + * added to `prefixStr` and `suffixStr`). + * @return {Autolinker.match.Match} return.match The Match object that represents the match that was found. */ - createAnchorTag : function( linkType, anchorHref, anchorText ) { - var attributesStr = this.createAnchorAttrsStr( linkType, anchorHref ); - anchorText = this.processAnchorText( anchorText ); - - return '' + anchorText + ''; - }, - - - /** - * Creates the string which will be the HTML attributes for the anchor (<a>) tag being generated. - * - * @private - * @param {"url"/"email"/"twitter"} linkType The type of link that an anchor tag is being generated for. - * @param {String} href The href for the anchor tag. - * @return {String} The anchor tag's attribute. Ex: `href="http://google.com" class="myLink myLink-url" target="_blank"` - */ - createAnchorAttrsStr : function( linkType, anchorHref ) { - var attrs = [ 'href="' + anchorHref + '"' ]; // we'll always have the `href` attribute - - var cssClass = this.createCssClass( linkType ); - if( cssClass ) { - attrs.push( 'class="' + cssClass + '"' ); + processCandidateMatch : function( + matchStr, twitterMatch, twitterHandlePrefixWhitespaceChar, twitterHandle, + emailAddressMatch, urlMatch, protocolUrlMatch, wwwProtocolRelativeMatch, tldProtocolRelativeMatch + ) { + var protocolRelativeMatch = wwwProtocolRelativeMatch || tldProtocolRelativeMatch, + match, // Will be an Autolinker.match.Match object + + prefixStr = "", // A string to use to prefix the anchor tag that is created. This is needed for the Twitter handle match + suffixStr = ""; // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked. + + + // Return out with `null` for match types that are disabled (url, email, twitter), or for matches that are + // invalid (false positives from the matcherRegex, which can't use look-behinds since they are unavailable in JS). + if( + ( twitterMatch && !this.twitter ) || ( emailAddressMatch && !this.email ) || ( urlMatch && !this.urls ) || + !this.matchValidator.isValidMatch( urlMatch, protocolUrlMatch, protocolRelativeMatch ) + ) { + return null; } - if( this.newWindow ) { - attrs.push( 'target="_blank"' ); + + // Handle a closing parenthesis at the end of the match, and exclude it if there is not a matching open parenthesis + // in the match itself. + if( this.matchHasUnbalancedClosingParen( matchStr ) ) { + matchStr = matchStr.substr( 0, matchStr.length - 1 ); // remove the trailing ")" + suffixStr = ")"; // this will be added after the generated tag } - - return attrs.join( " " ); - }, - - - /** - * Creates the CSS class that will be used for a given anchor tag, based on the `linkType` and the {@link #className} - * config. - * - * @private - * @param {"url"/"email"/"twitter"} linkType The type of link that an anchor tag is being generated for. - * @return {String} The CSS class string for the link. Example return: "myLink myLink-url". If no {@link #className} - * was configured, returns an empty string. - */ - createCssClass : function( linkType ) { - var className = this.className; - - if( !className ) - return ""; - else - return className + " " + className + "-" + linkType; // ex: "myLink myLink-url", "myLink myLink-email", or "myLink myLink-twitter" - }, - - - /** - * Processes the `anchorText` by stripping the URL prefix (if {@link #stripPrefix} is `true`), removing - * any trailing slash, and truncating the text according to the {@link #truncate} config. - * - * @private - * @param {String} anchorText The anchor tag's text (i.e. what will be displayed). - * @return {String} The processed `anchorText`. - */ - processAnchorText : function( anchorText ) { - if( this.stripPrefix ) { - anchorText = this.stripUrlPrefix( anchorText ); + + + if( emailAddressMatch ) { + match = new Autolinker.match.Email( { matchedText: matchStr, email: emailAddressMatch } ); + + } else if( twitterMatch ) { + // fix up the `matchStr` if there was a preceding whitespace char, which was needed to determine the match + // itself (since there are no look-behinds in JS regexes) + if( twitterHandlePrefixWhitespaceChar ) { + prefixStr = twitterHandlePrefixWhitespaceChar; + matchStr = matchStr.slice( 1 ); // remove the prefixed whitespace char from the match + } + match = new Autolinker.match.Twitter( { matchedText: matchStr, twitterHandle: twitterHandle } ); + + } else { // url match + // If it's a protocol-relative '//' match, remove the character before the '//' (which the matcherRegex needed + // to match due to the lack of a negative look-behind in JavaScript regular expressions) + if( protocolRelativeMatch ) { + var charBeforeMatch = protocolRelativeMatch.match( this.charBeforeProtocolRelMatchRegex )[ 1 ] || ""; + + if( charBeforeMatch ) { // fix up the `matchStr` if there was a preceding char before a protocol-relative match, which was needed to determine the match itself (since there are no look-behinds in JS regexes) + prefixStr = charBeforeMatch; + matchStr = matchStr.slice( 1 ); // remove the prefixed char from the match + } + } + + match = new Autolinker.match.Url( { + matchedText : matchStr, + url : matchStr, + protocolUrlMatch : !!protocolUrlMatch, + protocolRelativeMatch : !!protocolRelativeMatch, + stripPrefix : this.stripPrefix + } ); } - anchorText = this.removeTrailingSlash( anchorText ); // remove trailing slash, if there is one - anchorText = this.doTruncate( anchorText ); - - return anchorText; + + return { + prefixStr : prefixStr, + suffixStr : suffixStr, + matchStr : matchStr, + match : match + }; }, - - + + /** - * Strips the URL prefix (such as "http://" or "https://") from the given text. + * Determines if a match found has an unmatched closing parenthesis. If so, this parenthesis will be removed + * from the match itself, and appended after the generated anchor tag in {@link #processTextNode}. + * + * A match may have an extra closing parenthesis at the end of the match because the regular expression must include parenthesis + * for URLs such as "wikipedia.com/something_(disambiguation)", which should be auto-linked. + * + * However, an extra parenthesis *will* be included when the URL itself is wrapped in parenthesis, such as in the case of + * "(wikipedia.com/something_(disambiguation))". In this case, the last closing parenthesis should *not* be part of the URL + * itself, and this method will return `true`. * * @private - * @param {String} text The text of the anchor that is being generated, for which to strip off the - * url prefix (such as stripping off "http://") - * @return {String} The `anchorText`, with the prefix stripped. + * @param {String} matchStr The full match string from the {@link #matcherRegex}. + * @return {Boolean} `true` if there is an unbalanced closing parenthesis at the end of the `matchStr`, `false` otherwise. */ - stripUrlPrefix : function( text ) { - return text.replace( this.urlPrefixRegex, '' ); - }, - - - /** - * Removes any trailing slash from the given `anchorText`, in prepration for the text to be displayed. - * - * @private - * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing - * slash ('/') that may exist. - * @return {String} The `anchorText`, with the trailing slash removed. - */ - removeTrailingSlash : function( anchorText ) { - if( anchorText.charAt( anchorText.length - 1 ) === '/' ) { - anchorText = anchorText.slice( 0, -1 ); + matchHasUnbalancedClosingParen : function( matchStr ) { + var lastChar = matchStr.charAt( matchStr.length - 1 ); + + if( lastChar === ')' ) { + var openParensMatch = matchStr.match( /\(/g ), + closeParensMatch = matchStr.match( /\)/g ), + numOpenParens = ( openParensMatch && openParensMatch.length ) || 0, + numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0; + + if( numOpenParens < numCloseParens ) { + return true; + } } - return anchorText; + + return false; }, - - + + /** - * Performs the truncation of the `anchorText`, if the `anchorText` is longer than the {@link #truncate} option. - * Truncates the text to 2 characters fewer than the {@link #truncate} option, and adds ".." to the end. + * Creates the return string value for a given match in the input string, for the {@link #processTextNode} method. + * + * This method handles the {@link #replaceFn}, if one was provided. * * @private - * @param {String} text The anchor tag's text (i.e. what will be displayed). - * @return {String} The truncated anchor text. + * @param {Autolinker.match.Match} match The Match object that represents the match. + * @param {String} matchStr The original match string, after having been preprocessed to fix match edge cases (see + * the `prefixStr` and `suffixStr` vars in {@link #processTextNode}. + * @return {String} The string that the `match` should be replaced with. This is usually the anchor tag string, but + * may be the `matchStr` itself if the match is not to be replaced. */ - doTruncate : function( anchorText ) { - var truncateLen = this.truncate; - - // Truncate the anchor text if it is longer than the provided 'truncate' option - if( truncateLen && anchorText.length > truncateLen ) { - anchorText = anchorText.substring( 0, truncateLen - 2 ) + '..'; + createMatchReturnVal : function( match, matchStr ) { + // Handle a custom `replaceFn` being provided + var replaceFnResult; + if( this.replaceFn ) { + replaceFnResult = this.replaceFn.call( this, this, match ); // Autolinker instance is the context, and the first arg + } + + if( typeof replaceFnResult === 'string' ) { + return replaceFnResult; // `replaceFn` returned a string, use that + + } else if( replaceFnResult === false ) { + return matchStr; // no replacement for the match + + } else if( replaceFnResult instanceof Autolinker.HtmlTag ) { + return replaceFnResult.toString(); + + } else { // replaceFnResult === true, or no/unknown return value from function + // Perform Autolinker's default anchor tag generation + var tagBuilder = this.getTagBuilder(), + anchorTag = tagBuilder.build( match ); // returns an Autolinker.HtmlTag instance + + return anchorTag.toString(); } - return anchorText; } - + }; - - + + /** * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML. * Does not link URLs found within HTML tags. @@ -590,18 +664,1332 @@ * // Produces: "Go to google.com" * * @static - * @method link - * @param {String} html The HTML text to link URLs within. + * @param {String} textOrHtml The HTML or text to find URLs, email addresses, and Twitter handles within (depending on if + * the {@link #urls}, {@link #email}, and {@link #twitter} options are enabled). * @param {Object} [options] Any of the configuration options for the Autolinker class, specified in an Object (map). * See the class description for an example call. * @return {String} The HTML text, with URLs automatically linked */ - Autolinker.link = function( text, options ) { + Autolinker.link = function( textOrHtml, options ) { var autolinker = new Autolinker( options ); - return autolinker.link( text ); + return autolinker.link( textOrHtml ); }; - + + // Namespace for `match` classes + Autolinker.match = {}; + /*global Autolinker */ + /*jshint eqnull:true, boss:true */ + /** + * @class Autolinker.Util + * @singleton + * + * A few utility methods for Autolinker. + */ + Autolinker.Util = { + + /** + * @property {Function} abstractMethod + * + * A function object which represents an abstract method. + */ + abstractMethod : function() { throw "abstract"; }, + + + /** + * Assigns (shallow copies) the properties of `src` onto `dest`. + * + * @param {Object} dest The destination object. + * @param {Object} src The source object. + * @return {Object} The destination object (`dest`) + */ + assign : function( dest, src ) { + for( var prop in src ) { + if( src.hasOwnProperty( prop ) ) { + dest[ prop ] = src[ prop ]; + } + } + + return dest; + }, + + + /** + * Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype. + * + * @param {Function} superclass The constructor function for the superclass. + * @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the + * special property `constructor`, which will be used as the new subclass's constructor function. + * @return {Function} The new subclass function. + */ + extend : function( superclass, protoProps ) { + var superclassProto = superclass.prototype; + + var F = function() {}; + F.prototype = superclassProto; + + var subclass; + if( protoProps.hasOwnProperty( 'constructor' ) ) { + subclass = protoProps.constructor; + } else { + subclass = function() { superclassProto.constructor.apply( this, arguments ); }; + } + + var subclassProto = subclass.prototype = new F(); // set up prototype chain + subclassProto.constructor = subclass; // fix constructor property + subclassProto.superclass = superclassProto; + + delete protoProps.constructor; // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there + Autolinker.Util.assign( subclassProto, protoProps ); + + return subclass; + }, + + + /** + * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the + * end of the string (by default, two periods: '..'). If the `str` length does not exceed + * `len`, the string will be returned unchanged. + * + * @param {String} str The string to truncate and add an ellipsis to. + * @param {Number} truncateLen The length to truncate the string at. + * @param {String} [ellipsisChars=..] The ellipsis character(s) to add to the end of `str` + * when truncated. Defaults to '..' + */ + ellipsis : function( str, truncateLen, ellipsisChars ) { + if( str.length > truncateLen ) { + ellipsisChars = ( ellipsisChars == null ) ? '..' : ellipsisChars; + str = str.substring( 0, truncateLen - ellipsisChars.length ) + ellipsisChars; + } + return str; + }, + + + /** + * Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below). + * + * @param {Array} arr The array to find an element of. + * @param {*} element The element to find in the array, and return the index of. + * @return {Number} The index of the `element`, or -1 if it was not found. + */ + indexOf : function( arr, element ) { + if( Array.prototype.indexOf ) { + return arr.indexOf( element ); + + } else { + for( var i = 0, len = arr.length; i < len; i++ ) { + if( arr[ i ] === element ) return i; + } + return -1; + } + }, + + + + /** + * Performs the functionality of what modern browsers do when `String.prototype.split()` is called + * with a regular expression that contains capturing parenthesis. + * + * For example: + * + * // Modern browsers: + * "a,b,c".split( /(,)/ ); // --> [ 'a', ',', 'b', ',', 'c' ] + * + * // Old IE (including IE8): + * "a,b,c".split( /(,)/ ); // --> [ 'a', 'b', 'c' ] + * + * This method emulates the functionality of modern browsers for the old IE case. + * + * @param {String} str The string to split. + * @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting + * character(s) will be spliced into the array, as in the "modern browsers" example in the + * description of this method. + * Note #1: the supplied regular expression **must** have the 'g' flag specified. + * Note #2: for simplicity's sake, the regular expression does not need + * to contain capturing parenthesis - it will be assumed that any match has them. + * @return {String[]} The split array of strings, with the splitting character(s) included. + */ + splitAndCapture : function( str, splitRegex ) { + if( !splitRegex.global ) throw new Error( "`splitRegex` must have the 'g' flag set" ); + + var result = [], + lastIdx = 0, + match; + + while( match = splitRegex.exec( str ) ) { + result.push( str.substring( lastIdx, match.index ) ); + result.push( match[ 0 ] ); // push the splitting char(s) + + lastIdx = match.index + match[ 0 ].length; + } + result.push( str.substring( lastIdx ) ); + + return result; + } + + }; + /*global Autolinker */ + /** + * @private + * @class Autolinker.HtmlParser + * @extends Object + * + * An HTML parser implementation which simply walks an HTML string and calls the provided visitor functions to process + * HTML and text nodes. + * + * Autolinker uses this to only link URLs/emails/Twitter handles within text nodes, basically ignoring HTML tags. + */ + Autolinker.HtmlParser = Autolinker.Util.extend( Object, { + + /** + * @private + * @property {RegExp} htmlRegex + * + * The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and + * attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html. + * + * Capturing groups: + * + * 1. The "!DOCTYPE" tag name, if a tag is a <!DOCTYPE> tag. + * 2. If it is an end tag, this group will have the '/'. + * 3. The tag name for all tags (other than the <!DOCTYPE> tag) + */ + htmlRegex : (function() { + var tagNameRegex = /[0-9a-zA-Z][0-9a-zA-Z:]*/, + attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/, // the unicode range accounts for excluding control chars, and the delete char + attrValueRegex = /(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/, // double quoted, single quoted, or unquoted attribute values + nameEqualsValueRegex = attrNameRegex.source + '(?:\\s*=\\s*' + attrValueRegex.source + ')?'; // optional '=[value]' + + return new RegExp( [ + // for tag. Ex: ) + '(?:', + '<(!DOCTYPE)', // *** Capturing Group 1 - If it's a doctype tag + + // Zero or more attributes following the tag name + '(?:', + '\\s+', // one or more whitespace chars before an attribute + + // Either: + // A. attr="value", or + // B. "value" alone (To cover example doctype tag: ) + '(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')', + ')*', + '>', + ')', + + '|', + + // All other HTML tags (i.e. tags that are not ) + '(?:', + '<(/)?', // Beginning of a tag. Either '<' for a start tag, or '' + '>', + ')' + ].join( "" ), 'gi' ); + } )(), + + + /** + * Walks an HTML string, calling the `options.processHtmlNode` function for each HTML tag that is encountered, and calling + * the `options.processTextNode` function when each text around HTML tags is encountered. + * + * @param {String} html The HTML to parse. + * @param {Object} [options] An Object (map) which may contain the following properties: + * + * @param {Function} [options.processHtmlNode] A visitor function which allows processing of an encountered HTML node. + * This function is called with the following arguments: + * @param {String} [options.processHtmlNode.tagText] The HTML tag text that was found. + * @param {String} [options.processHtmlNode.tagName] The tag name for the HTML tag that was found. Ex: 'a' for an anchor tag. + * @param {String} [options.processHtmlNode.isClosingTag] `true` if the tag is a closing tag (ex: </a>), `false` otherwise. + * + * @param {Function} [options.processTextNode] A visitor function which allows processing of an encountered text node. + * This function is called with the following arguments: + * @param {String} [options.processTextNode.text] The text node that was matched. + */ + parse : function( html, options ) { + options = options || {}; + + var processHtmlNodeVisitor = options.processHtmlNode || function() {}, + processTextNodeVisitor = options.processTextNode || function() {}, + htmlRegex = this.htmlRegex, + currentResult, + lastIndex = 0; + + // Loop over the HTML string, ignoring HTML tags, and processing the text that lies between them, + // wrapping the URLs in anchor tags + while( ( currentResult = htmlRegex.exec( html ) ) !== null ) { + var tagText = currentResult[ 0 ], + tagName = currentResult[ 1 ] || currentResult[ 3 ], // The tag (ex: "!DOCTYPE"), or another tag (ex: "a") + isClosingTag = !!currentResult[ 2 ], + inBetweenTagsText = html.substring( lastIndex, currentResult.index ); + + if( inBetweenTagsText ) { + processTextNodeVisitor( inBetweenTagsText ); + } + + processHtmlNodeVisitor( tagText, tagName.toLowerCase(), isClosingTag ); + + lastIndex = currentResult.index + tagText.length; + } + + // Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements. + if( lastIndex < html.length ) { + var text = html.substring( lastIndex ); + + if( text ) { + processTextNodeVisitor( text ); + } + } + } + + } ); + /*global Autolinker */ + /*jshint boss:true */ + /** + * @class Autolinker.HtmlTag + * @extends Object + * + * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically. + * + * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use + * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}. + * + * ## Examples + * + * Example instantiation: + * + * var tag = new Autolinker.HtmlTag( { + * tagName : 'a', + * attrs : { 'href': 'http://google.com', 'class': 'external-link' }, + * innerHtml : 'Google' + * } ); + * + * tag.toString(); // Google + * + * // Individual accessor methods + * tag.getTagName(); // 'a' + * tag.getAttr( 'href' ); // 'http://google.com' + * tag.hasClass( 'external-link' ); // true + * + * + * Using mutator methods (which may be used in combination with instantiation config properties): + * + * var tag = new Autolinker.HtmlTag(); + * tag.setTagName( 'a' ); + * tag.setAttr( 'href', 'http://google.com' ); + * tag.addClass( 'external-link' ); + * tag.setInnerHtml( 'Google' ); + * + * tag.getTagName(); // 'a' + * tag.getAttr( 'href' ); // 'http://google.com' + * tag.hasClass( 'external-link' ); // true + * + * tag.toString(); // Google + * + * + * ## Example use within a {@link Autolinker#replaceFn replaceFn} + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + * + * + * ## Example use with a new tag for the replacement + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = new Autolinker.HtmlTag( { + * tagName : 'button', + * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() }, + * innerHtml : 'Load URL: ' + match.getAnchorText() + * } ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test + */ + Autolinker.HtmlTag = Autolinker.Util.extend( Object, { + + /** + * @cfg {String} tagName + * + * The tag name. Ex: 'a', 'button', etc. + * + * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toString} + * is executed. + */ + + /** + * @cfg {Object.} attrs + * + * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the + * values are the attribute values. + */ + + /** + * @cfg {String} innerHtml + * + * The inner HTML for the tag. + * + * Note the camel case name on `innerHtml`. Acronyms are camelCased in this utility (such as not to run into the acronym + * naming inconsistency that the DOM developers created with `XMLHttpRequest`). You may alternatively use {@link #innerHTML} + * if you prefer, but this one is recommended. + */ + + /** + * @cfg {String} innerHTML + * + * Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version + * for acronym names. + */ + + + /** + * @protected + * @property {RegExp} whitespaceRegex + * + * Regular expression used to match whitespace in a string of CSS classes. + */ + whitespaceRegex : /\s+/, + + + /** + * @constructor + * @param {Object} [cfg] The configuration properties for this class, in an Object (map) + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + + this.innerHtml = this.innerHtml || this.innerHTML; // accept either the camelCased form or the fully capitalized acronym + }, + + + /** + * Sets the tag name that will be used to generate the tag with. + * + * @param {String} tagName + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setTagName : function( tagName ) { + this.tagName = tagName; + return this; + }, + + + /** + * Retrieves the tag name. + * + * @return {String} + */ + getTagName : function() { + return this.tagName || ""; + }, + + + /** + * Sets an attribute on the HtmlTag. + * + * @param {String} attrName The attribute name to set. + * @param {String} attrValue The attribute value to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setAttr : function( attrName, attrValue ) { + var tagAttrs = this.getAttrs(); + tagAttrs[ attrName ] = attrValue; + + return this; + }, + + + /** + * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`. + * + * @param {String} name The attribute name to retrieve. + * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag. + */ + getAttr : function( attrName ) { + return this.getAttrs()[ attrName ]; + }, + + + /** + * Sets one or more attributes on the HtmlTag. + * + * @param {Object.} attrs A key/value Object (map) of the attributes to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setAttrs : function( attrs ) { + var tagAttrs = this.getAttrs(); + Autolinker.Util.assign( tagAttrs, attrs ); + + return this; + }, + + + /** + * Retrieves the attributes Object (map) for the HtmlTag. + * + * @return {Object.} A key/value object of the attributes for the HtmlTag. + */ + getAttrs : function() { + return this.attrs || ( this.attrs = {} ); + }, + + + /** + * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag. + * + * @param {String} cssClass One or more space-separated CSS classes to set (overwrite). + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setClass : function( cssClass ) { + return this.setAttr( 'class', cssClass ); + }, + + + /** + * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes. + * + * @param {String} cssClass One or more space-separated CSS classes to add. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + addClass : function( cssClass ) { + var classAttr = this.getClass(), + whitespaceRegex = this.whitespaceRegex, + indexOf = Autolinker.Util.indexOf, // to support IE8 and below + classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), + newClasses = cssClass.split( whitespaceRegex ), + newClass; + + while( newClass = newClasses.shift() ) { + if( indexOf( classes, newClass ) === -1 ) { + classes.push( newClass ); + } + } + + this.getAttrs()[ 'class' ] = classes.join( " " ); + return this; + }, + + + /** + * Convenience method to remove one or more CSS classes from the HtmlTag. + * + * @param {String} cssClass One or more space-separated CSS classes to remove. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + removeClass : function( cssClass ) { + var classAttr = this.getClass(), + whitespaceRegex = this.whitespaceRegex, + indexOf = Autolinker.Util.indexOf, // to support IE8 and below + classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), + removeClasses = cssClass.split( whitespaceRegex ), + removeClass; + + while( classes.length && ( removeClass = removeClasses.shift() ) ) { + var idx = indexOf( classes, removeClass ); + if( idx !== -1 ) { + classes.splice( idx, 1 ); + } + } + + this.getAttrs()[ 'class' ] = classes.join( " " ); + return this; + }, + + + /** + * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when + * there are multiple. + * + * @return {String} + */ + getClass : function() { + return this.getAttrs()[ 'class' ] || ""; + }, + + + /** + * Convenience method to check if the tag has a CSS class or not. + * + * @param {String} cssClass The CSS class to check for. + * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise. + */ + hasClass : function( cssClass ) { + return ( ' ' + this.getClass() + ' ' ).indexOf( ' ' + cssClass + ' ' ) !== -1; + }, + + + /** + * Sets the inner HTML for the tag. + * + * @param {String} html The inner HTML to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setInnerHtml : function( html ) { + this.innerHtml = html; + + return this; + }, + + + /** + * Retrieves the inner HTML for the tag. + * + * @return {String} + */ + getInnerHtml : function() { + return this.innerHtml || ""; + }, + + + /** + * Override of superclass method used to generate the HTML string for the tag. + * + * @return {String} + */ + toString : function() { + var tagName = this.getTagName(), + attrsStr = this.buildAttrsStr(); + + attrsStr = ( attrsStr ) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes + + return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '' ].join( "" ); + }, + + + /** + * Support method for {@link #toString}, returns the string space-separated key="value" pairs, used to populate + * the stringified HtmlTag. + * + * @protected + * @return {String} Example return: `attr1="value1" attr2="value2"` + */ + buildAttrsStr : function() { + if( !this.attrs ) return ""; // no `attrs` Object (map) has been set, return empty string + + var attrs = this.getAttrs(), + attrsArr = []; + + for( var prop in attrs ) { + if( attrs.hasOwnProperty( prop ) ) { + attrsArr.push( prop + '="' + attrs[ prop ] + '"' ); + } + } + return attrsArr.join( " " ); + } + + } ); + /*global Autolinker */ + /*jshint scripturl:true */ + /** + * @private + * @class Autolinker.MatchValidator + * @extends Object + * + * Used by Autolinker to filter out false positives from the {@link Autolinker#matcherRegex}. + * + * Due to the limitations of regular expressions (including the missing feature of look-behinds in JS regular expressions), + * we cannot always determine the validity of a given match. This class applies a bit of additional logic to filter out any + * false positives that have been matched by the {@link Autolinker#matcherRegex}. + */ + Autolinker.MatchValidator = Autolinker.Util.extend( Object, { + + /** + * @private + * @property {RegExp} invalidProtocolRelMatchRegex + * + * The regular expression used to check a potential protocol-relative URL match, coming from the + * {@link Autolinker#matcherRegex}. A protocol-relative URL is, for example, "//yahoo.com" + * + * This regular expression checks to see if there is a word character before the '//' match in order to determine if + * we should actually autolink a protocol-relative URL. This is needed because there is no negative look-behind in + * JavaScript regular expressions. + * + * For instance, we want to autolink something like "Go to: //google.com", but we don't want to autolink something + * like "abc//google.com" + */ + invalidProtocolRelMatchRegex : /^[\w]\/\//, + + /** + * Regex to test for a full protocol, with the two trailing slashes. Ex: 'http://' + * + * @private + * @property {RegExp} hasFullProtocolRegex + */ + hasFullProtocolRegex : /^[A-Za-z][-.+A-Za-z0-9]+:\/\//, + + /** + * Regex to find the URI scheme, such as 'mailto:'. + * + * This is used to filter out 'javascript:' and 'vbscript:' schemes. + * + * @private + * @property {RegExp} uriSchemeRegex + */ + uriSchemeRegex : /^[A-Za-z][-.+A-Za-z0-9]+:/, + + /** + * Regex to determine if at least one word char exists after the protocol (i.e. after the ':') + * + * @private + * @property {RegExp} hasWordCharAfterProtocolRegex + */ + hasWordCharAfterProtocolRegex : /:[^\s]*?[A-Za-z]/, + + + /** + * Determines if a given match found by {@link Autolinker#processTextNode} is valid. Will return `false` for: + * + * 1) URL matches which do not have at least have one period ('.') in the domain name (effectively skipping over + * matches like "abc:def"). However, URL matches with a protocol will be allowed (ex: 'http://localhost') + * 2) URL matches which do not have at least one word character in the domain name (effectively skipping over + * matches like "git:1.0"). + * 3) A protocol-relative url match (a URL beginning with '//') whose previous character is a word character + * (effectively skipping over strings like "abc//google.com") + * + * Otherwise, returns `true`. + * + * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to match + * something like 'http://localhost', where we won't double check that the domain name has at least one '.' in it. + * @param {String} protocolRelativeMatch The protocol-relative string for a URL match (i.e. '//'), possibly with a preceding + * character (ex, a space, such as: ' //', or a letter, such as: 'a//'). The match is invalid if there is a word character + * preceding the '//'. + * @return {Boolean} `true` if the match given is valid and should be processed, or `false` if the match is invalid and/or + * should just not be processed. + */ + isValidMatch : function( urlMatch, protocolUrlMatch, protocolRelativeMatch ) { + if( + ( protocolUrlMatch && !this.isValidUriScheme( protocolUrlMatch ) ) || + this.urlMatchDoesNotHaveProtocolOrDot( urlMatch, protocolUrlMatch ) || // At least one period ('.') must exist in the URL match for us to consider it an actual URL, *unless* it was a full protocol match (like 'http://localhost') + this.urlMatchDoesNotHaveAtLeastOneWordChar( urlMatch, protocolUrlMatch ) || // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0" + this.isInvalidProtocolRelativeMatch( protocolRelativeMatch ) // A protocol-relative match which has a word character in front of it (so we can skip something like "abc//google.com") + ) { + return false; + } + + return true; + }, + + + /** + * Determines if the URI scheme is a valid scheme to be autolinked. Returns `false` if the scheme is + * 'javascript:' or 'vbscript:' + * + * @private + * @param {String} uriSchemeMatch The match URL string for a full URI scheme match. Ex: 'http://yahoo.com' + * or 'mailto:a@a.com'. + * @return {Boolean} `true` if the scheme is a valid one, `false` otherwise. + */ + isValidUriScheme : function( uriSchemeMatch ) { + var uriScheme = uriSchemeMatch.match( this.uriSchemeRegex )[ 0 ]; + + return ( uriScheme !== 'javascript:' && uriScheme !== 'vbscript:' ); + }, + + + /** + * Determines if a URL match does not have either: + * + * a) a full protocol (i.e. 'http://'), or + * b) at least one dot ('.') in the domain name (for a non-full-protocol match). + * + * Either situation is considered an invalid URL (ex: 'git:d' does not have either the '://' part, or at least one dot + * in the domain name. If the match was 'git:abc.com', we would consider this valid.) + * + * @private + * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to match + * something like 'http://localhost', where we won't double check that the domain name has at least one '.' in it. + * @return {Boolean} `true` if the URL match does not have a full protocol, or at least one dot ('.') in a non-full-protocol + * match. + */ + urlMatchDoesNotHaveProtocolOrDot : function( urlMatch, protocolUrlMatch ) { + return ( !!urlMatch && ( !protocolUrlMatch || !this.hasFullProtocolRegex.test( protocolUrlMatch ) ) && urlMatch.indexOf( '.' ) === -1 ); + }, + + + /** + * Determines if a URL match does not have at least one word character after the protocol (i.e. in the domain name). + * + * At least one letter character must exist in the domain name after a protocol match. Ex: skip over something + * like "git:1.0" + * + * @private + * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match. + * @param {String} protocolUrlMatch The match URL string for a protocol match. Ex: 'http://yahoo.com'. This is used to + * know whether or not we have a protocol in the URL string, in order to check for a word character after the protocol + * separator (':'). + * @return {Boolean} `true` if the URL match does not have at least one word character in it after the protocol, `false` + * otherwise. + */ + urlMatchDoesNotHaveAtLeastOneWordChar : function( urlMatch, protocolUrlMatch ) { + if( urlMatch && protocolUrlMatch ) { + return !this.hasWordCharAfterProtocolRegex.test( urlMatch ); + } else { + return false; + } + }, + + + /** + * Determines if a protocol-relative match is an invalid one. This method returns `true` if there is a `protocolRelativeMatch`, + * and that match contains a word character before the '//' (i.e. it must contain whitespace or nothing before the '//' in + * order to be considered valid). + * + * @private + * @param {String} protocolRelativeMatch The protocol-relative string for a URL match (i.e. '//'), possibly with a preceding + * character (ex, a space, such as: ' //', or a letter, such as: 'a//'). The match is invalid if there is a word character + * preceding the '//'. + * @return {Boolean} `true` if it is an invalid protocol-relative match, `false` otherwise. + */ + isInvalidProtocolRelativeMatch : function( protocolRelativeMatch ) { + return ( !!protocolRelativeMatch && this.invalidProtocolRelMatchRegex.test( protocolRelativeMatch ) ); + } + + } ); + /*global Autolinker */ + /*jshint sub:true */ + /** + * @protected + * @class Autolinker.AnchorTagBuilder + * @extends Object + * + * Builds anchor (<a>) tags for the Autolinker utility when a match is found. + * + * Normally this class is instantiated, configured, and used internally by an {@link Autolinker} instance, but may + * actually be retrieved in a {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} instances + * which may be modified before returning from the {@link Autolinker#replaceFn replaceFn}. For example: + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + */ + Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { + + /** + * @cfg {Boolean} newWindow + * @inheritdoc Autolinker#newWindow + */ + + /** + * @cfg {Number} truncate + * @inheritdoc Autolinker#truncate + */ + + /** + * @cfg {String} className + * @inheritdoc Autolinker#className + */ + + + /** + * @constructor + * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + }, + + + /** + * Generates the actual anchor (<a>) tag to use in place of the matched URL/email/Twitter text, + * via its `match` object. + * + * @param {Autolinker.match.Match} match The Match instance to generate an anchor tag from. + * @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag. + */ + build : function( match ) { + var tag = new Autolinker.HtmlTag( { + tagName : 'a', + attrs : this.createAttrs( match.getType(), match.getAnchorHref() ), + innerHtml : this.processAnchorText( match.getAnchorText() ) + } ); + + return tag; + }, + + + /** + * Creates the Object (map) of the HTML attributes for the anchor (<a>) tag being generated. + * + * @protected + * @param {"url"/"email"/"twitter"} matchType The type of match that an anchor tag is being generated for. + * @param {String} href The href for the anchor tag. + * @return {Object} A key/value Object (map) of the anchor tag's attributes. + */ + createAttrs : function( matchType, anchorHref ) { + var attrs = { + 'href' : anchorHref // we'll always have the `href` attribute + }; + + var cssClass = this.createCssClass( matchType ); + if( cssClass ) { + attrs[ 'class' ] = cssClass; + } + if( this.newWindow ) { + attrs[ 'target' ] = "_blank"; + } + + return attrs; + }, + + + /** + * Creates the CSS class that will be used for a given anchor tag, based on the `matchType` and the {@link #className} + * config. + * + * @private + * @param {"url"/"email"/"twitter"} matchType The type of match that an anchor tag is being generated for. + * @return {String} The CSS class string for the link. Example return: "myLink myLink-url". If no {@link #className} + * was configured, returns an empty string. + */ + createCssClass : function( matchType ) { + var className = this.className; + + if( !className ) + return ""; + else + return className + " " + className + "-" + matchType; // ex: "myLink myLink-url", "myLink myLink-email", or "myLink myLink-twitter" + }, + + + /** + * Processes the `anchorText` by truncating the text according to the {@link #truncate} config. + * + * @private + * @param {String} anchorText The anchor tag's text (i.e. what will be displayed). + * @return {String} The processed `anchorText`. + */ + processAnchorText : function( anchorText ) { + anchorText = this.doTruncate( anchorText ); + + return anchorText; + }, + + + /** + * Performs the truncation of the `anchorText`, if the `anchorText` is longer than the {@link #truncate} option. + * Truncates the text to 2 characters fewer than the {@link #truncate} option, and adds ".." to the end. + * + * @private + * @param {String} text The anchor tag's text (i.e. what will be displayed). + * @return {String} The truncated anchor text. + */ + doTruncate : function( anchorText ) { + return Autolinker.Util.ellipsis( anchorText, this.truncate || Number.POSITIVE_INFINITY ); + } + + } ); + /*global Autolinker */ + /** + * @abstract + * @class Autolinker.match.Match + * + * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a + * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match. + * + * For example: + * + * var input = "..."; // string with URLs, Email Addresses, and Twitter Handles + * + * var linkedText = Autolinker.link( input, { + * replaceFn : function( autolinker, match ) { + * console.log( "href = ", match.getAnchorHref() ); + * console.log( "text = ", match.getAnchorText() ); + * + * switch( match.getType() ) { + * case 'url' : + * console.log( "url: ", match.getUrl() ); + * + * case 'email' : + * console.log( "email: ", match.getEmail() ); + * + * case 'twitter' : + * console.log( "twitter: ", match.getTwitterHandle() ); + * } + * } + * } ); + * + * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}. + */ + Autolinker.match.Match = Autolinker.Util.extend( Object, { + + /** + * @cfg {String} matchedText (required) + * + * The original text that was matched. + */ + + + /** + * @constructor + * @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + }, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @abstract + * @return {String} + */ + getType : Autolinker.Util.abstractMethod, + + + /** + * Returns the original text that was matched. + * + * @return {String} + */ + getMatchedText : function() { + return this.matchedText; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @abstract + * @return {String} + */ + getAnchorHref : Autolinker.Util.abstractMethod, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @abstract + * @return {String} + */ + getAnchorText : Autolinker.Util.abstractMethod + + } ); + /*global Autolinker */ + /** + * @class Autolinker.match.Email + * @extends Autolinker.match.Match + * + * Represents a Email match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + Autolinker.match.Email = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} email (required) + * + * The email address that was matched. + */ + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'email'; + }, + + + /** + * Returns the email address that was matched. + * + * @return {String} + */ + getEmail : function() { + return this.email; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return 'mailto:' + this.email; + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return this.email; + } + + } ); + /*global Autolinker */ + /** + * @class Autolinker.match.Twitter + * @extends Autolinker.match.Match + * + * Represents a Twitter match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} twitterHandle (required) + * + * The Twitter handle that was matched. + */ + + + /** + * Returns the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'twitter'; + }, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getTwitterHandle : function() { + return this.twitterHandle; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return 'https://twitter.com/' + this.twitterHandle; + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return '@' + this.twitterHandle; + } + + } ); + /*global Autolinker */ + /** + * @class Autolinker.match.Url + * @extends Autolinker.match.Match + * + * Represents a Url match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} url (required) + * + * The url that was matched. + */ + + /** + * @cfg {Boolean} protocolUrlMatch (required) + * + * `true` if the URL is a match which already has a protocol (i.e. 'http://'), `false` if the match was from a 'www' or + * known TLD match. + */ + + /** + * @cfg {Boolean} protocolRelativeMatch (required) + * + * `true` if the URL is a protocol-relative match. A protocol-relative match is a URL that starts with '//', + * and will be either http:// or https:// based on the protocol that the site is loaded under. + */ + + /** + * @cfg {Boolean} stripPrefix (required) + * @inheritdoc Autolinker#stripPrefix + */ + + + /** + * @private + * @property {RegExp} urlPrefixRegex + * + * A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs. + */ + urlPrefixRegex: /^(https?:\/\/)?(www\.)?/i, + + /** + * @private + * @property {RegExp} protocolRelativeRegex + * + * The regular expression used to remove the protocol-relative '//' from the {@link #url} string, for purposes + * of {@link #getAnchorText}. A protocol-relative URL is, for example, "//yahoo.com" + */ + protocolRelativeRegex : /^\/\//, + + /** + * @private + * @property {Boolean} protocolPrepended + * + * Will be set to `true` if the 'http://' protocol has been prepended to the {@link #url} (because the + * {@link #url} did not have a protocol) + */ + protocolPrepended : false, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'url'; + }, + + + /** + * Returns the url that was matched, assuming the protocol to be 'http://' if the original + * match was missing a protocol. + * + * @return {String} + */ + getUrl : function() { + var url = this.url; + + // if the url string doesn't begin with a protocol, assume 'http://' + if( !this.protocolRelativeMatch && !this.protocolUrlMatch && !this.protocolPrepended ) { + url = this.url = 'http://' + url; + + this.protocolPrepended = true; + } + + return url; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + var url = this.getUrl(); + + return url.replace( /&/g, '&' ); // any &'s in the URL should be converted back to '&' if they were displayed as & in the source html + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + var anchorText = this.getUrl(); + + if( this.protocolRelativeMatch ) { + // Strip off any protocol-relative '//' from the anchor text + anchorText = this.stripProtocolRelativePrefix( anchorText ); + } + if( this.stripPrefix ) { + anchorText = this.stripUrlPrefix( anchorText ); + } + anchorText = this.removeTrailingSlash( anchorText ); // remove trailing slash, if there is one + + return anchorText; + }, + + + // --------------------------------------- + + // Utility Functionality + + /** + * Strips the URL prefix (such as "http://" or "https://") from the given text. + * + * @private + * @param {String} text The text of the anchor that is being generated, for which to strip off the + * url prefix (such as stripping off "http://") + * @return {String} The `anchorText`, with the prefix stripped. + */ + stripUrlPrefix : function( text ) { + return text.replace( this.urlPrefixRegex, '' ); + }, + + + /** + * Strips any protocol-relative '//' from the anchor text. + * + * @private + * @param {String} text The text of the anchor that is being generated, for which to strip off the + * protocol-relative prefix (such as stripping off "//") + * @return {String} The `anchorText`, with the protocol-relative prefix stripped. + */ + stripProtocolRelativePrefix : function( text ) { + return text.replace( this.protocolRelativeRegex, '' ); + }, + + + /** + * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed. + * + * @private + * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing + * slash ('/') that may exist. + * @return {String} The `anchorText`, with the trailing slash removed. + */ + removeTrailingSlash : function( anchorText ) { + if( anchorText.charAt( anchorText.length - 1 ) === '/' ) { + anchorText = anchorText.slice( 0, -1 ); + } + return anchorText; + } + + } ); + return Autolinker; - -} ) ); + + +})); diff --git a/vendors/Autolinker/Autolinker.min.js b/vendors/Autolinker/Autolinker.min.js index ea375d19b..9bb4c7112 100644 --- a/vendors/Autolinker/Autolinker.min.js +++ b/vendors/Autolinker/Autolinker.min.js @@ -1,10 +1,10 @@ /*! * Autolinker.js - * 0.11.0 + * 0.15.2 * - * Copyright(c) 2014 Gregory Jacobs + * Copyright(c) 2015 Gregory Jacobs * MIT Licensed. http://www.opensource.org/licenses/mit-license.php * * https://github.com/gregjacobs/Autolinker.js */ -!function(a,b){"function"==typeof define&&define.amd?define(b):"undefined"!=typeof exports?module.exports=b():a.Autolinker=b()}(this,function(){var a=function(a){a=a||{};for(var b in a)a.hasOwnProperty(b)&&(this[b]=a[b])};return a.prototype={constructor:a,newWindow:!0,stripPrefix:!0,twitter:!0,email:!0,urls:!0,className:"",matcherRegex:function(){var a=/(^|[^\w])@(\w{1,15})/,b=/(?:[\-;:&=\+\$,\w\.]+@)/,c=/(?:[A-Za-z]{3,9}:(?:\/\/)?)/,d=/(?:www\.)/,e=/[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/,f=/\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/,g=/(?:[\-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|])?/;return new RegExp(["(",a.source,")","|","(",b.source,e.source,f.source,")","|","(","(?:","(?:",c.source,e.source,")","|","(?:","(.?//)?",d.source,e.source,")","|","(?:","(.?//)?",e.source,f.source,")",")",g.source,")"].join(""),"gi")}(),protocolRelativeRegex:/(.)?\/\//,htmlRegex:function(){var a=/[0-9a-zA-Z:]+/,b=/[^\s\0"'>\/=\x01-\x1F\x7F]+/,c=/(?:".*?"|'.*?'|[^'"=<>`\s]+)/;return new RegExp(["<(/)?","("+a.source+")","(?:","\\s+",b.source,"(?:\\s*=\\s*"+c.source+")?",")*","\\s*",">"].join(""),"g")}(),urlPrefixRegex:/^(https?:\/\/)?(www\.)?/i,link:function(a){return this.processHtml(a)},processHtml:function(a){for(var b,c,d=this.htmlRegex,e=0,f=0,g=[];null!==(b=d.exec(a));){var h=b[0],i=b[2],j=!!b[1];c=a.substring(e,b.index),e=b.index+h.length,"a"===i?j?(f=Math.max(f-1,0),0===f&&g.push(c)):(f++,g.push(this.processTextNode(c))):g.push(0===f?this.processTextNode(c):c),g.push(h)}if(ex&&(a=a.substr(0,a.length-1),t=")")}var z,A=a,B=a;if(m)z="twitter",s=n,A="https://twitter.com/"+o,B="@"+o;else if(p)z="email",A="mailto:"+p,B=p;else if(z="url",r){var C=new RegExp("^"+b.protocolRelativeRegex.source),D=r.match(C)[1]||"";s=D+s,A=A.replace(C,"//"),B=B.replace(C,"")}else/^[A-Za-z]{3,9}:/i.test(A)||(A="http://"+A);var E=b.createAnchorTag(z,A,B);return s+E+t})},createAnchorTag:function(a,b,c){var d=this.createAnchorAttrsStr(a,b);return c=this.processAnchorText(c),""+c+""},createAnchorAttrsStr:function(a,b){var c=['href="'+b+'"'],d=this.createCssClass(a);return d&&c.push('class="'+d+'"'),this.newWindow&&c.push('target="_blank"'),c.join(" ")},createCssClass:function(a){var b=this.className;return b?b+" "+b+"-"+a:""},processAnchorText:function(a){return this.stripPrefix&&(a=this.stripUrlPrefix(a)),a=this.removeTrailingSlash(a),a=this.doTruncate(a)},stripUrlPrefix:function(a){return a.replace(this.urlPrefixRegex,"")},removeTrailingSlash:function(a){return"/"===a.charAt(a.length-1)&&(a=a.slice(0,-1)),a},doTruncate:function(a){var b=this.truncate;return b&&a.length>b&&(a=a.substring(0,b-2)+".."),a}},a.link=function(b,c){var d=new a(c);return d.link(b)},a}); \ No newline at end of file +!function(a,b){"function"==typeof define&&define.amd?define([],function(){return a.returnExportsGlobal=b()}):"object"==typeof exports?module.exports=b():a.Autolinker=b()}(this,function(){var a=function(b){a.Util.assign(this,b),this.matchValidator=new a.MatchValidator};return a.prototype={constructor:a,urls:!0,email:!0,twitter:!0,newWindow:!0,stripPrefix:!0,className:"",htmlCharacterEntitiesRegex:/( | |<|<|>|>|"|"|')/gi,matcherRegex:function(){var a=/(^|[^\w])@(\w{1,15})/,b=/(?:[\-;:&=\+\$,\w\.]+@)/,c=/(?:[A-Za-z][-.+A-Za-z0-9]+:(?![A-Za-z][-.+A-Za-z0-9]+:\/\/)(?!\d+\/?)(?:\/\/)?)/,d=/(?:www\.)/,e=/[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/,f=/\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/,g=/[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]?!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|'$*\[\]]/;return new RegExp(["(",a.source,")","|","(",b.source,e.source,f.source,")","|","(","(?:","(",c.source,e.source,")","|","(?:","(.?//)?",d.source,e.source,")","|","(?:","(.?//)?",e.source,f.source,")",")","(?:"+g.source+")?",")"].join(""),"gi")}(),charBeforeProtocolRelMatchRegex:/^(.)?\/\//,link:function(b){var c=this,d=this.getHtmlParser(),e=this.htmlCharacterEntitiesRegex,f=0,g=[];return d.parse(b,{processHtmlNode:function(a,b,c){"a"===b&&(c?f=Math.max(f-1,0):f++),g.push(a)},processTextNode:function(b){if(0===f)for(var d=a.Util.splitAndCapture(b,e),h=0,i=d.length;i>h;h++){var j=d[h],k=c.processTextNode(j);g.push(k)}else g.push(b)}}),g.join("")},getHtmlParser:function(){var b=this.htmlParser;return b||(b=this.htmlParser=new a.HtmlParser),b},getTagBuilder:function(){var b=this.tagBuilder;return b||(b=this.tagBuilder=new a.AnchorTagBuilder({newWindow:this.newWindow,truncate:this.truncate,className:this.className})),b},processTextNode:function(a){var b=this;return a.replace(this.matcherRegex,function(a,c,d,e,f,g,h,i,j){var k=b.processCandidateMatch(a,c,d,e,f,g,h,i,j);if(k){var l=b.createMatchReturnVal(k.match,k.matchStr);return k.prefixStr+l+k.suffixStr}return a})},processCandidateMatch:function(b,c,d,e,f,g,h,i,j){var k,l=i||j,m="",n="";if(c&&!this.twitter||f&&!this.email||g&&!this.urls||!this.matchValidator.isValidMatch(g,h,l))return null;if(this.matchHasUnbalancedClosingParen(b)&&(b=b.substr(0,b.length-1),n=")"),f)k=new a.match.Email({matchedText:b,email:f});else if(c)d&&(m=d,b=b.slice(1)),k=new a.match.Twitter({matchedText:b,twitterHandle:e});else{if(l){var o=l.match(this.charBeforeProtocolRelMatchRegex)[1]||"";o&&(m=o,b=b.slice(1))}k=new a.match.Url({matchedText:b,url:b,protocolUrlMatch:!!h,protocolRelativeMatch:!!l,stripPrefix:this.stripPrefix})}return{prefixStr:m,suffixStr:n,matchStr:b,match:k}},matchHasUnbalancedClosingParen:function(a){var b=a.charAt(a.length-1);if(")"===b){var c=a.match(/\(/g),d=a.match(/\)/g),e=c&&c.length||0,f=d&&d.length||0;if(f>e)return!0}return!1},createMatchReturnVal:function(b,c){var d;if(this.replaceFn&&(d=this.replaceFn.call(this,this,b)),"string"==typeof d)return d;if(d===!1)return c;if(d instanceof a.HtmlTag)return d.toString();var e=this.getTagBuilder(),f=e.build(b);return f.toString()}},a.link=function(b,c){var d=new a(c);return d.link(b)},a.match={},a.Util={abstractMethod:function(){throw"abstract"},assign:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},extend:function(b,c){var d=b.prototype,e=function(){};e.prototype=d;var f;f=c.hasOwnProperty("constructor")?c.constructor:function(){d.constructor.apply(this,arguments)};var g=f.prototype=new e;return g.constructor=f,g.superclass=d,delete c.constructor,a.Util.assign(g,c),f},ellipsis:function(a,b,c){return a.length>b&&(c=null==c?"..":c,a=a.substring(0,b-c.length)+c),a},indexOf:function(a,b){if(Array.prototype.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},splitAndCapture:function(a,b){if(!b.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var c,d=[],e=0;c=b.exec(a);)d.push(a.substring(e,c.index)),d.push(c[0]),e=c.index+c[0].length;return d.push(a.substring(e)),d}},a.HtmlParser=a.Util.extend(Object,{htmlRegex:function(){var a=/[0-9a-zA-Z][0-9a-zA-Z:]*/,b=/[^\s\0"'>\/=\x01-\x1F\x7F]+/,c=/(?:"[^"]*?"|'[^']*?'|[^'"=<>`\s]+)/,d=b.source+"(?:\\s*=\\s*"+c.source+")?";return new RegExp(["(?:","<(!DOCTYPE)","(?:","\\s+","(?:",d,"|",c.source+")",")*",">",")","|","(?:","<(/)?","("+a.source+")","(?:","\\s+",d,")*","\\s*/?",">",")"].join(""),"gi")}(),parse:function(a,b){b=b||{};for(var c,d=b.processHtmlNode||function(){},e=b.processTextNode||function(){},f=this.htmlRegex,g=0;null!==(c=f.exec(a));){var h=c[0],i=c[1]||c[3],j=!!c[2],k=a.substring(g,c.index);k&&e(k),d(h,i.toLowerCase(),j),g=c.index+h.length}if(g",this.getInnerHtml(),""].join("")},buildAttrsStr:function(){if(!this.attrs)return"";var a=this.getAttrs(),b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c+'="'+a[c]+'"');return b.join(" ")}}),a.MatchValidator=a.Util.extend(Object,{invalidProtocolRelMatchRegex:/^[\w]\/\//,hasFullProtocolRegex:/^[A-Za-z][-.+A-Za-z0-9]+:\/\//,uriSchemeRegex:/^[A-Za-z][-.+A-Za-z0-9]+:/,hasWordCharAfterProtocolRegex:/:[^\s]*?[A-Za-z]/,isValidMatch:function(a,b,c){return b&&!this.isValidUriScheme(b)||this.urlMatchDoesNotHaveProtocolOrDot(a,b)||this.urlMatchDoesNotHaveAtLeastOneWordChar(a,b)||this.isInvalidProtocolRelativeMatch(c)?!1:!0},isValidUriScheme:function(a){var b=a.match(this.uriSchemeRegex)[0];return"javascript:"!==b&&"vbscript:"!==b},urlMatchDoesNotHaveProtocolOrDot:function(a,b){return!(!a||b&&this.hasFullProtocolRegex.test(b)||-1!==a.indexOf("."))},urlMatchDoesNotHaveAtLeastOneWordChar:function(a,b){return a&&b?!this.hasWordCharAfterProtocolRegex.test(a):!1},isInvalidProtocolRelativeMatch:function(a){return!!a&&this.invalidProtocolRelMatchRegex.test(a)}}),a.AnchorTagBuilder=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},build:function(b){var c=new a.HtmlTag({tagName:"a",attrs:this.createAttrs(b.getType(),b.getAnchorHref()),innerHtml:this.processAnchorText(b.getAnchorText())});return c},createAttrs:function(a,b){var c={href:b},d=this.createCssClass(a);return d&&(c["class"]=d),this.newWindow&&(c.target="_blank"),c},createCssClass:function(a){var b=this.className;return b?b+" "+b+"-"+a:""},processAnchorText:function(a){return a=this.doTruncate(a)},doTruncate:function(b){return a.Util.ellipsis(b,this.truncate||Number.POSITIVE_INFINITY)}}),a.match.Match=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},getType:a.Util.abstractMethod,getMatchedText:function(){return this.matchedText},getAnchorHref:a.Util.abstractMethod,getAnchorText:a.Util.abstractMethod}),a.match.Email=a.Util.extend(a.match.Match,{getType:function(){return"email"},getEmail:function(){return this.email},getAnchorHref:function(){return"mailto:"+this.email},getAnchorText:function(){return this.email}}),a.match.Twitter=a.Util.extend(a.match.Match,{getType:function(){return"twitter"},getTwitterHandle:function(){return this.twitterHandle},getAnchorHref:function(){return"https://twitter.com/"+this.twitterHandle},getAnchorText:function(){return"@"+this.twitterHandle}}),a.match.Url=a.Util.extend(a.match.Match,{urlPrefixRegex:/^(https?:\/\/)?(www\.)?/i,protocolRelativeRegex:/^\/\//,protocolPrepended:!1,getType:function(){return"url"},getUrl:function(){var a=this.url;return this.protocolRelativeMatch||this.protocolUrlMatch||this.protocolPrepended||(a=this.url="http://"+a,this.protocolPrepended=!0),a},getAnchorHref:function(){var a=this.getUrl();return a.replace(/&/g,"&")},getAnchorText:function(){var a=this.getUrl();return this.protocolRelativeMatch&&(a=this.stripProtocolRelativePrefix(a)),this.stripPrefix&&(a=this.stripUrlPrefix(a)),a=this.removeTrailingSlash(a)},stripUrlPrefix:function(a){return a.replace(this.urlPrefixRegex,"")},stripProtocolRelativePrefix:function(a){return a.replace(this.protocolRelativeRegex,"")},removeTrailingSlash:function(a){return"/"===a.charAt(a.length-1)&&(a=a.slice(0,-1)),a}}),a}); \ No newline at end of file diff --git a/vendors/Autolinker/README.md b/vendors/Autolinker/README.md index 76f327c38..fc1660603 100644 --- a/vendors/Autolinker/README.md +++ b/vendors/Autolinker/README.md @@ -50,13 +50,13 @@ JavaScript: ```javascript var Autolinker = require( 'autolinker' ); // note: npm wants an all-lowercase package name, but the utility is a class and should be -// aliased with a captial letter +// aliased with a capital letter ``` ## Usage -Using the static `link()` method: +Using the static [link()](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-static-method-link) method: ```javascript var linkedText = Autolinker.link( textToAutolink[, options] ); @@ -70,6 +70,9 @@ var autolinker = new Autolinker( [ options ] ); var linkedText = autolinker.link( textToAutoLink ); ``` +Note: if using the same options to autolink multiple pieces of html/text, it is slightly more efficient to create a single +Autolinker instance, and run the [link()](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-method-link) method repeatedly (i.e. use the "class" form above). + #### Example: @@ -80,16 +83,16 @@ var linkedText = Autolinker.link( "Check out google.com", { className: "myLink" ## Options -These are the options which may be specified for linking. These are specified by providing an Object as the second parameter to `Autolinker.link()`. These include: +These are the options which may be specified for linking. These are specified by providing an Object as the second parameter to [Autolinker.link()](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-static-method-link). These include: -- **newWindow** : Boolean
+- [newWindow](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-newWindow) : Boolean
`true` to have the links should open in a new window when clicked, `false` otherwise. Defaults to `true`.

-- **stripPrefix** : Boolean
+- [stripPrefix](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-stripPrefix) : Boolean
`true` to have the 'http://' or 'https://' and/or the 'www.' stripped from the beginning of links, `false` otherwise. Defaults to `true`.

-- **truncate** : Number
+- [truncate](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-truncate) : Number
A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of a link. If the URL/email/twitter is over the number of characters, it will be truncated to this length by replacing the end of the string with a two period ellipsis ('..').

- Example: a url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters may look like this: 'yahoo.com/some/long/pat..'
-- **className** : String
+ Example: a url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters may look like this: 'yahoo.com/some/long/pat..'

+- [className](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-className) : String
A CSS class name to add to the generated anchor tags. This class will be added to all links, as well as this class plus "url"/"email"/"twitter" suffixes for styling url/email/twitter links differently. @@ -99,14 +102,18 @@ These are the options which may be specified for linking. These are specified by 2) Email links will have the CSS classes: "myLink myLink-email", and
3) Twitter links will have the CSS classes: "myLink myLink-twitter"
-- **urls** : Boolean
+- [urls](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-urls) : Boolean
`true` to have URLs auto-linked, `false` to skip auto-linking of URLs. Defaults to `true`.
-- **email** : Boolean
+- [email](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-email) : Boolean
`true` to have email addresses auto-linked, `false` to skip auto-linking of email addresses. Defaults to `true`.

-- **twitter** : Boolean
- `true` to have Twitter handles auto-linked, `false` to skip auto-linking of Twitter handles. Defaults to `true`. +- [twitter](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-twitter) : Boolean
+ `true` to have Twitter handles auto-linked, `false` to skip auto-linking of Twitter handles. Defaults to `true`.

+- [replaceFn](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-replaceFn) : Function
+ A function to use to programmatically make replacements of matches in the input string, one at a time. See the section + Custom Replacement Function for more details. -For example, if you wanted to disable links from opening in new windows, you could do: + +For example, if you wanted to disable links from opening in [new windows](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-newWindow), you could do: ```javascript var linkedText = Autolinker.link( "Check out google.com", { newWindow: false } ); @@ -128,7 +135,7 @@ var myTextEl = document.getElementById( 'text' ); myTextEl.innerHTML = Autolinker.link( myTextEl.innerHTML ); ``` -Using the same pre-configured Autolinker instance in multiple locations of a codebase (usually by dependency injection): +Using the same pre-configured [Autolinker](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker) instance in multiple locations of a codebase (usually by dependency injection): ```javascript var autolinker = new Autolinker( { newWindow: false, truncate: 25 } ); @@ -146,110 +153,78 @@ autolinker.link( "Go to www.google.com" ); ``` -## Changelog: +## Custom Replacement Function -### 0.11.0 +A custom replacement function ([replaceFn](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-cfg-replaceFn)) may be provided to replace url/email/twitter matches on an individual basis, based +on the return from this function. -- Allow Autolinker to link fully-capitalized URLs/Emails/Twitter handles. - -### 0.10.1 - -- Added fix to not autolink strings like "version:1.0", which were accidentally being interpreted as a protocol:domain string. - -### 0.10.0 - -- Added support for protocol-relative URLs (ex: `//google.com`, which will effectively either have the `http://` or `https://` - protocol depending on the protocol that is hosting the website) - -### 0.9.4 - -- Fixed an issue where a string in the form of `abc:def` would be autolinked as a protocol and domain name URL. Autolinker now - requires the domain name to have at least one period in it to be considered. - -### 0.9.3 - -- Fixed an issue where Twitter handles wouldn't be autolinked if they existed as the sole entity within parenthesis or brackets - (thanks [@busticated](https://github.com/busticated) for pointing this out and providing unit tests) - -### 0.9.2 - -- Fixed an issue with nested tags within an existing <a> tag, where the nested tags' inner text would be accidentally - removed from the output (thanks [@mjsabin01](https://github.com/mjsabin01)) - -### 0.9.1 - -- Added a patch to attempt to better handle extraneous </a> tags in the input string if any exist. This is for when the - input may have some invalid markup (for instance, on sites which allow user comments, blog posts, etc.). - -### 0.9.0 - -- Added better support for the processing of existing HTML in the input string. Now handles namespaced tags, and attribute names - with dashes or any other Unicode character (thanks [@aziraphale](https://github.com/aziraphale)) - -### 0.8.0 - -- Added `className` option for easily styling produced links (thanks [@busticated](https://github.com/busticated)) -- Refactored into a JS class. Autolinker can now be instantiated using: +Full example, for purposes of documenting the API: ```javascript -var autolinker = new Autolinker( { newWindow: false, truncate: 25 } ); +var input = "..."; // string with URLs, Email Addresses, and Twitter Handles -autolinker.link( "Check out http://www.yahoo.com/some/long/path/to/a/file" ); -// Produces: "Check out yahoo.com/some/long/pat.." +var linkedText = Autolinker.link( input, { + replaceFn : function( autolinker, match ) { + console.log( "href = ", match.getAnchorHref() ); + console.log( "text = ", match.getAnchorText() ); + + switch( match.getType() ) { + case 'url' : + console.log( "url: ", match.getUrl() ); + + if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { + var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes + tag.setAttr( 'rel', 'nofollow' ); + tag.addClass( 'external-link' ); + + return tag; + + } else { + return true; // let Autolinker perform its normal anchor tag replacement + } + + case 'email' : + var email = match.getEmail(); + console.log( "email: ", email ); + + if( email === "my@own.address" ) { + return false; // don't auto-link this particular email address; leave as-is + } else { + return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`) + } + + case 'twitter' : + var twitterHandle = match.getTwitterHandle(); + console.log( twitterHandle ); + + return '' + twitterHandle + ''; + } + } +} ); ``` -This allows options to be set on a single instance, and used throughout a codebase by injecting the `autolinker` instance as a dependency to the modules/classes that use it. (Note: Autolinker may still be used with the static `Autolinker.link()` method as was previously available as well.) -### 0.7.0 +The function is provided two arguments: -- Changed build system to Grunt. -- Added AMD and CommonJS module loading support (ex: RequireJS, and Node.js's module loader). -- Added command line Jasmine test runner (`grunt test`) -- Upgraded Jasmine from 1.3.1 to 2.0 -- Added license header to dist files. +1. The Autolinker instance that is performing replacements. This can be used to query the options that the Autolinker + instance is configured with, or to retrieve its TagBuilder instance (via [autolinker.getTagBuilder()](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker-method-getTagBuilder)). +2. An [Autolinker.match.Match](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.match.Match) object which details the match that is to be replaced. -(Thanks to [@busticated](https://github.com/busticated)!) -### 0.6.1 +A replacement of the match is made based on the return value of the function. The following return values may be provided: + +- No return value (`undefined`), or `true` (Boolean): Delegate back to Autolinker to replace the match as it normally would. +- `false` (Boolean): Do not replace the current match at all - leave as-is. +- Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for + the match. +- An [Autolinker.HtmlTag](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.HtmlTag) instance, which can be used to build/modify an HTML tag before writing out its HTML text. -- Added LICENSE file to repository. -### 0.6.0 +## Full API Docs -- Added options for granular control of which types are linked (urls, email addresses, and/or twitter handles). - (thanks [@aziraphale](https://github.com/aziraphale)) +The full API docs for Autolinker may be referenced at: [http://gregjacobs.github.io/Autolinker.js/docs/](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker) -### 0.5.0 -- Simplified the path / query string / hash processing into a single regular expression instead of 3 separate ones. -- Added support for parenthesis in URLs, such as: `en.wikipedia.org/wiki/IANA_(disambiguation)` (thanks [@dandv](https://github.com/dandv)) -- Add all known top-level domains (TLDs) (thanks [@wouter0100](https://github.com/wouter0100)) +## Changelog -### 0.4.0 - -Merged pull requests from [@afeld](https://github.com/afeld): - -- strip protocol and 'www.' by default - fixes #1 -- truncate URLs from the end -- make simpler regex for detecting prefix -- remove trailing slashes from URLs, and handle periods at the end of paths -- re-use domain+TLD regexes for email matching -- add .me and .io to list of TLDs - -Thanks Aidan :) - -### 0.3.1 - -- Fixed an issue with handling nested HTML tags within anchor tags. - -### 0.3 - -- Implemented the `truncate` option. - -### 0.2 - -- Implemented autolinking Twitter handles. - -### 0.1 - -* Initial implementation, which autolinks URLs and email addresses. Working on linking Twitter handles. +See [Releases](https://github.com/gregjacobs/Autolinker.js/releases) diff --git a/vendors/Autolinker/_/Autolinker.js b/vendors/Autolinker/_/Autolinker.js new file mode 100644 index 000000000..01eaecc78 --- /dev/null +++ b/vendors/Autolinker/_/Autolinker.js @@ -0,0 +1,1781 @@ +/*! + * Autolinker.js + * 0.12.2 + * + * Copyright(c) 2014 Gregory Jacobs + * MIT Licensed. http://www.opensource.org/licenses/mit-license.php + * + * https://github.com/gregjacobs/Autolinker.js + */ +/*global define, module */ +( function( root, factory ) { + + if( typeof define === 'function' && define.amd ) { + define( factory ); // Define as AMD module if an AMD loader is present (ex: RequireJS). + } else if( typeof exports !== 'undefined' ) { + module.exports = factory(); // Define as CommonJS module for Node.js, if available. + } else { + root.Autolinker = factory(); // Finally, define as a browser global if no module loader. + } +}( this, function() { + + /** + * @class Autolinker + * @extends Object + * + * Utility class used to process a given string of text, and wrap the URLs, email addresses, and Twitter handles in + * the appropriate anchor (<a>) tags to turn them into links. + * + * Any of the configuration options may be provided in an Object (map) provided to the Autolinker constructor, which + * will configure how the {@link #link link()} method will process the links. + * + * For example: + * + * var autolinker = new Autolinker( { + * newWindow : false, + * truncate : 30 + * } ); + * + * var html = autolinker.link( "Joe went to www.yahoo.com" ); + * // produces: 'Joe went to yahoo.com' + * + * + * The {@link #static-link static link()} method may also be used to inline options into a single call, which may + * be more convenient for one-off uses. For example: + * + * var html = Autolinker.link( "Joe went to www.yahoo.com", { + * newWindow : false, + * truncate : 30 + * } ); + * // produces: 'Joe went to yahoo.com' + * + * + * ## Custom Replacements of Links + * + * If the configuration options do not provide enough flexibility, a {@link #replaceFn} may be provided to fully customize + * the output of Autolinker. This function is called once for each URL/Email/Twitter handle match that is encountered. + * + * For example: + * + * var input = "..."; // string with URLs, Email Addresses, and Twitter Handles + * + * var linkedText = Autolinker.link( input, { + * replaceFn : function( autolinker, match ) { + * console.log( "href = ", match.getAnchorHref() ); + * console.log( "text = ", match.getAnchorText() ); + * + * switch( match.getType() ) { + * case 'url' : + * console.log( "url: ", match.getUrl() ); + * + * if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes + * tag.setAttr( 'rel', 'nofollow' ); + * tag.addClass( 'external-link' ); + * + * return tag; + * + * } else { + * return true; // let Autolinker perform its normal anchor tag replacement + * } + * + * case 'email' : + * var email = match.getEmail(); + * console.log( "email: ", email ); + * + * if( email === "my@own.address" ) { + * return false; // don't auto-link this particular email address; leave as-is + * } else { + * return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`) + * } + * + * case 'twitter' : + * var twitterHandle = match.getTwitterHandle(); + * console.log( twitterHandle ); + * + * return '' + twitterHandle + ''; + * } + * } + * } ); + * + * + * The function may return the following values: + * + * - `true` (Boolean): Allow Autolinker to replace the match as it normally would. + * - `false` (Boolean): Do not replace the current match at all - leave as-is. + * - Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for + * the match. + * - An {@link Autolinker.HtmlTag} instance, which can be used to build/modify an HTML tag before writing out its HTML text. + * + * @constructor + * @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map). + */ + var Autolinker = function( cfg ) { + Autolinker.Util.assign( this, cfg ); // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs. + }; + + + Autolinker.prototype = { + constructor : Autolinker, // fix constructor property + + /** + * @cfg {Boolean} urls + * + * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be. + */ + urls : true, + + /** + * @cfg {Boolean} email + * + * `true` if email addresses should be automatically linked, `false` if they should not be. + */ + email : true, + + /** + * @cfg {Boolean} twitter + * + * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be. + */ + twitter : true, + + /** + * @cfg {Boolean} newWindow + * + * `true` if the links should open in a new window, `false` otherwise. + */ + newWindow : true, + + /** + * @cfg {Boolean} stripPrefix + * + * `true` if 'http://' or 'https://' and/or the 'www.' should be stripped from the beginning of URL links' text, + * `false` otherwise. + */ + stripPrefix : true, + + /** + * @cfg {Number} truncate + * + * A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of + * a link. If the URL/email/twitter is over this number of characters, it will be truncated to this length by + * adding a two period ellipsis ('..') to the end of the string. + * + * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look + * something like this: 'yahoo.com/some/long/pat..' + */ + + /** + * @cfg {String} className + * + * A CSS class name to add to the generated links. This class will be added to all links, as well as this class + * plus url/email/twitter suffixes for styling url/email/twitter links differently. + * + * For example, if this config is provided as "myLink", then: + * + * - URL links will have the CSS classes: "myLink myLink-url" + * - Email links will have the CSS classes: "myLink myLink-email", and + * - Twitter links will have the CSS classes: "myLink myLink-twitter" + */ + className : "", + + /** + * @cfg {Function} replaceFn + * + * A function to individually process each URL/Email/Twitter match found in the input string. + * + * See the class's description for usage. + * + * This function is called with the following parameters: + * + * @cfg {Autolinker} replaceFn.autolinker The Autolinker instance, which may be used to retrieve child objects from (such + * as the instance's {@link #getTagBuilder tag builder}). + * @cfg {Autolinker.match.Match} replaceFn.match The Match instance which can be used to retrieve information about the + * {@link Autolinker.match.Url URL}/{@link Autolinker.match.Email email}/{@link Autolinker.match.Twitter Twitter} + * match that the `replaceFn` is currently processing. + */ + + + /** + * @private + * @property {RegExp} htmlCharacterEntitiesRegex + * + * The regular expression that matches common HTML character entities. + * + * Ignoring & as it could be part of a query string -- handling it separately. + */ + htmlCharacterEntitiesRegex: /( | |<|<|>|>)/gi, + + /** + * @private + * @property {RegExp} matcherRegex + * + * The regular expression that matches URLs, email addresses, and Twitter handles. + * + * This regular expression has the following capturing groups: + * + * 1. Group that is used to determine if there is a Twitter handle match (i.e. \@someTwitterUser). Simply check for its + * existence to determine if there is a Twitter handle match. The next couple of capturing groups give information + * about the Twitter handle match. + * 2. The whitespace character before the \@sign in a Twitter handle. This is needed because there are no lookbehinds in + * JS regular expressions, and can be used to reconstruct the original string in a replace(). + * 3. The Twitter handle itself in a Twitter match. If the match is '@someTwitterUser', the handle is 'someTwitterUser'. + * 4. Group that matches an email address. Used to determine if the match is an email address, as well as holding the full + * address. Ex: 'me@my.com' + * 5. Group that matches a URL in the input text. Ex: 'http://google.com', 'www.google.com', or just 'google.com'. + * This also includes a path, url parameters, or hash anchors. Ex: google.com/path/to/file?q1=1&q2=2#myAnchor + * 6. A protocol-relative ('//') match for the case of a 'www.' prefixed URL. Will be an empty string if it is not a + * protocol-relative match. We need to know the character before the '//' in order to determine if it is a valid match + * or the // was in a string we don't want to auto-link. + * 7. A protocol-relative ('//') match for the case of a known TLD prefixed URL. Will be an empty string if it is not a + * protocol-relative match. See #6 for more info. + */ + matcherRegex : (function() { + var twitterRegex = /(^|[^\w])@(\w{1,15})/, // For matching a twitter handle. Ex: @gregory_jacobs + + emailRegex = /(?:[\-;:&=\+\$,\w\.]+@)/, // something@ for email addresses (a.k.a. local-part) + + protocolRegex = /(?:[A-Za-z]{3,9}:(?:\/\/)?)/, // match protocol, allow in format http:// or mailto: + wwwRegex = /(?:www\.)/, // starting with 'www.' + domainNameRegex = /[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/, // anything looking at all like a domain, non-unicode domains, not ending in a period + tldRegex = /\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/, // match our known top level domains (TLDs) + + // Allow optional path, query string, and hash anchor, not ending in the following characters: "!:,.;" + // http://blog.codinghorror.com/the-problem-with-urls/ + urlSuffixRegex = /(?:[\-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|])?/; // note: optional part of the full regex + + return new RegExp( [ + '(', // *** Capturing group $1, which can be used to check for a twitter handle match. Use group $3 for the actual twitter handle though. $2 may be used to reconstruct the original string in a replace() + // *** Capturing group $2, which matches the whitespace character before the '@' sign (needed because of no lookbehinds), and + // *** Capturing group $3, which matches the actual twitter handle + twitterRegex.source, + ')', + + '|', + + '(', // *** Capturing group $4, which is used to determine an email match + emailRegex.source, + domainNameRegex.source, + tldRegex.source, + ')', + + '|', + + '(', // *** Capturing group $5, which is used to match a URL + '(?:', // parens to cover match for protocol (optional), and domain + '(?:', // non-capturing paren for a protocol-prefixed url (ex: http://google.com) + protocolRegex.source, + domainNameRegex.source, + ')', + + '|', + + '(?:', // non-capturing paren for a 'www.' prefixed url (ex: www.google.com) + '(.?//)?', // *** Capturing group $6 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character + wwwRegex.source, + domainNameRegex.source, + ')', + + '|', + + '(?:', // non-capturing paren for known a TLD url (ex: google.com) + '(.?//)?', // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character + domainNameRegex.source, + tldRegex.source, + ')', + ')', + + urlSuffixRegex.source, // match for path, query string, and/or hash anchor + ')' + ].join( "" ), 'gi' ); + } )(), + + /** + * @private + * @property {RegExp} invalidProtocolRelMatchRegex + * + * The regular expression used to check a potential protocol-relative URL match, coming from the {@link #matcherRegex}. + * A protocol-relative URL is, for example, "//yahoo.com" + * + * This regular expression is used in conjunction with the {@link #matcherRegex}, and checks to see if there is a word character + * before the '//' in order to determine if we should actually autolink a protocol-relative URL. This is needed because there + * is no negative look-behind in JavaScript regular expressions. + * + * For instance, we want to autolink something like "//google.com", but we don't want to autolink something + * like "abc//google.com" + */ + invalidProtocolRelMatchRegex : /^[\w]\/\//, + + /** + * @private + * @property {RegExp} charBeforeProtocolRelMatchRegex + * + * The regular expression used to retrieve the character before a protocol-relative URL match. + * + * This is used in conjunction with the {@link #matcherRegex}, which needs to grab the character before a protocol-relative + * '//' due to the lack of a negative look-behind in JavaScript regular expressions. The character before the match is stripped + * from the URL. + */ + charBeforeProtocolRelMatchRegex : /^(.)?\/\//, + + /** + * @private + * @property {Autolinker.HtmlParser} htmlParser + * + * The HtmlParser instance used to skip over HTML tags, while finding text nodes to process. This is lazily instantiated + * in the {@link #getHtmlParser} method. + */ + + /** + * @private + * @property {Autolinker.AnchorTagBuilder} tagBuilder + * + * The AnchorTagBuilder instance used to build the URL/email/Twitter replacement anchor tags. This is lazily instantiated + * in the {@link #getTagBuilder} method. + */ + + + /** + * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML. + * Does not link URLs found within HTML tags. + * + * For instance, if given the text: `You should go to http://www.yahoo.com`, then the result + * will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` + * + * This method finds the text around any HTML elements in the input `textOrHtml`, which will be the text that is processed. + * Any original HTML elements will be left as-is, as well as the text that is already wrapped in anchor (<a>) tags. + * + * @param {String} textOrHtml The HTML or text to link URLs, email addresses, and Twitter handles within. + * @return {String} The HTML, with URLs/emails/Twitter handles automatically linked. + */ + link : function( textOrHtml ) { + var me = this, // for closure + htmlParser = this.getHtmlParser(), + htmlCharacterEntitiesRegex = this.htmlCharacterEntitiesRegex, + anchorTagStackCount = 0, // used to only process text around anchor tags, and any inner text/html they may have + resultHtml = []; + + htmlParser.parse( textOrHtml, { + // Process HTML nodes in the input `textOrHtml` + processHtmlNode : function( tagText, tagName, isClosingTag ) { + if( tagName === 'a' ) { + if( !isClosingTag ) { // it's the start tag + anchorTagStackCount++; + } else { // it's the end tag + anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous tags by making sure the stack count never goes below 0 + } + } + resultHtml.push( tagText ); // now add the text of the tag itself verbatim + }, + + // Process text nodes in the input `textOrHtml` + processTextNode : function( text ) { + if( anchorTagStackCount === 0 ) { + // If we're not within an tag, process the text node + var unescapedText = Autolinker.Util.splitAndCapture( text, htmlCharacterEntitiesRegex ); // split at HTML entities, but include the HTML entities in the results array + + for ( var i = 0, len = unescapedText.length; i < len; i++ ) { + var textToProcess = unescapedText[ i ], + processedTextNode = me.processTextNode( textToProcess ); + + resultHtml.push( processedTextNode ); + } + + } else { + // `text` is within an tag, simply append the text - we do not want to autolink anything + // already within an ... tag + resultHtml.push( text ); + } + } + } ); + + return resultHtml.join( "" ); + }, + + + /** + * Lazily instantiates and returns the {@link #htmlParser} instance for this Autolinker instance. + * + * @protected + * @return {Autolinker.HtmlParser} + */ + getHtmlParser : function() { + var htmlParser = this.htmlParser; + + if( !htmlParser ) { + htmlParser = this.htmlParser = new Autolinker.HtmlParser(); + } + + return htmlParser; + }, + + + /** + * Returns the {@link #tagBuilder} instance for this Autolinker instance, lazily instantiating it + * if it does not yet exist. + * + * This method may be used in a {@link #replaceFn} to generate the {@link Autolinker.HtmlTag HtmlTag} instance that + * Autolinker would normally generate, and then allow for modifications before returning it. For example: + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + * + * @return {Autolinker.AnchorTagBuilder} + */ + getTagBuilder : function() { + var tagBuilder = this.tagBuilder; + + if( !tagBuilder ) { + tagBuilder = this.tagBuilder = new Autolinker.AnchorTagBuilder( { + newWindow : this.newWindow, + truncate : this.truncate, + className : this.className + } ); + } + + return tagBuilder; + }, + + + /** + * Process the text that lies inbetween HTML tags. This method does the actual wrapping of URLs with + * anchor tags. + * + * @private + * @param {String} text The text to auto-link. + * @return {String} The text with anchor tags auto-filled. + */ + processTextNode : function( text ) { + var me = this, // for closure + charBeforeProtocolRelMatchRegex = this.charBeforeProtocolRelMatchRegex; + + return text.replace( this.matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7 ) { + var twitterMatch = $1, + twitterHandlePrefixWhitespaceChar = $2, // The whitespace char before the @ sign in a Twitter handle match. This is needed because of no lookbehinds in JS regexes. + twitterHandle = $3, // The actual twitterUser (i.e the word after the @ sign in a Twitter handle match) + emailAddressMatch = $4, // For both determining if it is an email address, and stores the actual email address + urlMatch = $5, // The matched URL string + protocolRelativeMatch = $6 || $7, // The '//' for a protocol-relative match, with the character that comes before the '//' + + prefixStr = "", // A string to use to prefix the anchor tag that is created. This is needed for the Twitter handle match + suffixStr = "", // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked. + + match; // Will be an Autolinker.match.Match object + + + // Return out with no changes for match types that are disabled (url, email, twitter), or for matches that are + // invalid (false positives from the matcherRegex, which can't use look-behinds since they are unavailable in JS). + if( !me.isValidMatch( twitterMatch, emailAddressMatch, urlMatch, protocolRelativeMatch ) ) { + return matchStr; + } + + // Handle a closing parenthesis at the end of the match, and exclude it if there is not a matching open parenthesis + // in the match itself. + if( me.matchHasUnbalancedClosingParen( matchStr ) ) { + matchStr = matchStr.substr( 0, matchStr.length - 1 ); // remove the trailing ")" + suffixStr = ")"; // this will be added after the generated tag + } + + + if( emailAddressMatch ) { + match = new Autolinker.match.Email( { matchedText: matchStr, email: emailAddressMatch } ); + + } else if( twitterMatch ) { + // fix up the `matchStr` if there was a preceding whitespace char, which was needed to determine the match + // itself (since there are no look-behinds in JS regexes) + if( twitterHandlePrefixWhitespaceChar ) { + prefixStr = twitterHandlePrefixWhitespaceChar; + matchStr = matchStr.slice( 1 ); // remove the prefixed whitespace char from the match + } + match = new Autolinker.match.Twitter( { matchedText: matchStr, twitterHandle: twitterHandle } ); + + } else { // url match + // If it's a protocol-relative '//' match, remove the character before the '//' (which the matcherRegex needed + // to match due to the lack of a negative look-behind in JavaScript regular expressions) + if( protocolRelativeMatch ) { + var charBeforeMatch = protocolRelativeMatch.match( charBeforeProtocolRelMatchRegex )[ 1 ] || ""; + + if( charBeforeMatch ) { // fix up the `matchStr` if there was a preceding char before a protocol-relative match, which was needed to determine the match itself (since there are no look-behinds in JS regexes) + prefixStr = charBeforeMatch; + matchStr = matchStr.slice( 1 ); // remove the prefixed char from the match + } + } + + match = new Autolinker.match.Url( { + matchedText : matchStr, + url : matchStr, + protocolRelativeMatch : protocolRelativeMatch, + stripPrefix : me.stripPrefix + } ); + } + + // Generate the replacement text for the match + var matchReturnVal = me.createMatchReturnVal( match, matchStr ); + return prefixStr + matchReturnVal + suffixStr; + } ); + }, + + + /** + * Determines if a given match found by {@link #processTextNode} is valid. Will return `false` for: + * + * 1) Disabled link types (i.e. having a Twitter match, but {@link #twitter} matching is disabled) + * 2) URL matches which do not have at least have one period ('.') in the domain name (effectively skipping over + * matches like "abc:def") + * 3) A protocol-relative url match (a URL beginning with '//') whose previous character is a word character + * (effectively skipping over strings like "abc//google.com") + * + * Otherwise, returns `true`. + * + * @private + * @param {String} twitterMatch The matched Twitter handle, if there was one. Will be empty string if the match is not a + * Twitter match. + * @param {String} emailAddressMatch The matched Email address, if there was one. Will be empty string if the match is not + * an Email address match. + * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match. + * @param {String} protocolRelativeMatch The protocol-relative string for a URL match (i.e. '//'), possibly with a preceding + * character (ex, a space, such as: ' //', or a letter, such as: 'a//'). The match is invalid if there is a word character + * preceding the '//'. + * @return {Boolean} `true` if the match given is valid and should be processed, or `false` if the match is invalid and/or + * should just not be processed (such as, if it's a Twitter match, but {@link #twitter} matching is disabled}. + */ + isValidMatch : function( twitterMatch, emailAddressMatch, urlMatch, protocolRelativeMatch ) { + if( + ( twitterMatch && !this.twitter ) || ( emailAddressMatch && !this.email ) || ( urlMatch && !this.urls ) || + ( urlMatch && urlMatch.indexOf( '.' ) === -1 ) || // At least one period ('.') must exist in the URL match for us to consider it an actual URL + ( urlMatch && /^[A-Za-z]{3,9}:/.test( urlMatch ) && !/:.*?[A-Za-z]/.test( urlMatch ) ) || // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0" + ( protocolRelativeMatch && this.invalidProtocolRelMatchRegex.test( protocolRelativeMatch ) ) // a protocol-relative match which has a word character in front of it (so we can skip something like "abc//google.com") + ) { + return false; + } + + return true; + }, + + + /** + * Determines if a match found has an unmatched closing parenthesis. If so, this parenthesis will be removed + * from the match itself, and appended after the generated anchor tag in {@link #processTextNode}. + * + * A match may have an extra closing parenthesis at the end of the match because the regular expression must include parenthesis + * for URLs such as "wikipedia.com/something_(disambiguation)", which should be auto-linked. + * + * However, an extra parenthesis *will* be included when the URL itself is wrapped in parenthesis, such as in the case of + * "(wikipedia.com/something_(disambiguation))". In this case, the last closing parenthesis should *not* be part of the URL + * itself, and this method will return `true`. + * + * @private + * @param {String} matchStr The full match string from the {@link #matcherRegex}. + * @return {Boolean} `true` if there is an unbalanced closing parenthesis at the end of the `matchStr`, `false` otherwise. + */ + matchHasUnbalancedClosingParen : function( matchStr ) { + var lastChar = matchStr.charAt( matchStr.length - 1 ); + + if( lastChar === ')' ) { + var openParensMatch = matchStr.match( /\(/g ), + closeParensMatch = matchStr.match( /\)/g ), + numOpenParens = ( openParensMatch && openParensMatch.length ) || 0, + numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0; + + if( numOpenParens < numCloseParens ) { + return true; + } + } + + return false; + }, + + + /** + * Creates the return string value for a given match in the input string, for the {@link #processTextNode} method. + * + * This method handles the {@link #replaceFn}, if one was provided. + * + * @private + * @param {Autolinker.match.Match} match The Match object that represents the match. + * @param {String} matchStr The original match string, after having been preprocessed to fix match edge cases (see + * the `prefixStr` and `suffixStr` vars in {@link #processTextNode}. + * @return {String} The string that the `match` should be replaced with. This is usually the anchor tag string, but + * may be the `matchStr` itself if the match is not to be replaced. + */ + createMatchReturnVal : function( match, matchStr ) { + // Handle a custom `replaceFn` being provided + var replaceFnResult; + if( this.replaceFn ) { + replaceFnResult = this.replaceFn.call( this, this, match ); // Autolinker instance is the context, and the first arg + } + + if( typeof replaceFnResult === 'string' ) { + return replaceFnResult; // `replaceFn` returned a string, use that + + } else if( replaceFnResult === false ) { + return matchStr; // no replacement for the match + + } else if( replaceFnResult instanceof Autolinker.HtmlTag ) { + return replaceFnResult.toString(); + + } else { // replaceFnResult === true, or no/unknown return value from function + // Perform Autolinker's default anchor tag generation + var tagBuilder = this.getTagBuilder(), + anchorTag = tagBuilder.build( match ); // returns an Autolinker.HtmlTag instance + + return anchorTag.toString(); + } + } + + }; + + + /** + * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML. + * Does not link URLs found within HTML tags. + * + * For instance, if given the text: `You should go to http://www.yahoo.com`, then the result + * will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` + * + * Example: + * + * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } ); + * // Produces: "Go to google.com" + * + * @static + * @method link + * @param {String} html The HTML text to link URLs within. + * @param {Object} [options] Any of the configuration options for the Autolinker class, specified in an Object (map). + * See the class description for an example call. + * @return {String} The HTML text, with URLs automatically linked + */ + Autolinker.link = function( text, options ) { + var autolinker = new Autolinker( options ); + return autolinker.link( text ); + }; + + + // Namespace for `match` classes + Autolinker.match = {}; + /*global Autolinker */ + /*jshint eqnull:true, boss:true */ + /** + * @class Autolinker.Util + * @singleton + * + * A few utility methods for Autolinker. + */ + Autolinker.Util = { + + /** + * @property {Function} abstractMethod + * + * A function object which represents an abstract method. + */ + abstractMethod : function() { throw "abstract"; }, + + + /** + * Assigns (shallow copies) the properties of `src` onto `dest`. + * + * @param {Object} dest The destination object. + * @param {Object} src The source object. + * @return {Object} The destination object. + */ + assign : function( dest, src ) { + for( var prop in src ) { + if( src.hasOwnProperty( prop ) ) { + dest[ prop ] = src[ prop ]; + } + } + + return dest; + }, + + + /** + * Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype. + * + * @param {Function} superclass The constructor function for the superclass. + * @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the + * special property `constructor`, which will be used as the new subclass's constructor function. + * @return {Function} The new subclass function. + */ + extend : function( superclass, protoProps ) { + var superclassProto = superclass.prototype; + + var F = function() {}; + F.prototype = superclassProto; + + var subclass; + if( protoProps.hasOwnProperty( 'constructor' ) ) { + subclass = protoProps.constructor; + } else { + subclass = function() { superclassProto.constructor.apply( this, arguments ); }; + } + + var subclassProto = subclass.prototype = new F(); // set up prototype chain + subclassProto.constructor = subclass; // fix constructor property + subclassProto.superclass = superclassProto; + + delete protoProps.constructor; // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there + Autolinker.Util.assign( subclassProto, protoProps ); + + return subclass; + }, + + + /** + * Truncates the `str` at `len - ellipsisChars.length`, and adds the `ellipsisChars` to the + * end of the string (by default, two periods: '..'). If the `str` length does not exceed + * `len`, the string will be returned unchanged. + * + * @param {String} str The string to truncate and add an ellipsis to. + * @param {Number} truncateLen The length to truncate the string at. + * @param {String} [ellipsisChars=..] The ellipsis character(s) to add to the end of `str` + * when truncated. Defaults to '..' + */ + ellipsis : function( str, truncateLen, ellipsisChars ) { + if( str.length > truncateLen ) { + ellipsisChars = ( ellipsisChars == null ) ? '..' : ellipsisChars; + str = str.substring( 0, truncateLen - ellipsisChars.length ) + ellipsisChars; + } + return str; + }, + + + /** + * Supports `Array.prototype.indexOf()` functionality for old IE (IE8 and below). + * + * @param {Array} arr The array to find an element of. + * @param {*} element The element to find in the array, and return the index of. + * @return {Number} The index of the `element`, or -1 if it was not found. + */ + indexOf : function( arr, element ) { + if( Array.prototype.indexOf ) { + return arr.indexOf( element ); + + } else { + for( var i = 0, len = arr.length; i < len; i++ ) { + if( arr[ i ] === element ) return i; + } + return -1; + } + }, + + + + /** + * Performs the functionality of what modern browsers do when `String.prototype.split()` is called + * with a regular expression that contains capturing parenthesis. + * + * For example: + * + * // Modern browsers: + * "a,b,c".split( /(,)/ ); // --> [ 'a', ',', 'b', ',', 'c' ] + * + * // Old IE (including IE8): + * "a,b,c".split( /(,)/ ); // --> [ 'a', 'b', 'c' ] + * + * This method emulates the functionality of modern browsers for the old IE case. + * + * @param {String} str The string to split. + * @param {RegExp} splitRegex The regular expression to split the input `str` on. The splitting + * character(s) will be spliced into the array, as in the "modern browsers" example in the + * description of this method. + * Note #1: the supplied regular expression **must** have the 'g' flag specified. + * Note #2: for simplicity's sake, the regular expression does not need + * to contain capturing parenthesis - it will be assumed that any match has them. + * @return {String[]} The split array of strings, with the splitting character(s) included. + */ + splitAndCapture : function( str, splitRegex ) { + if( !splitRegex.global ) throw new Error( "`splitRegex` must have the 'g' flag set" ); + + var result = [], + lastIdx = 0, + match; + + while( match = splitRegex.exec( str ) ) { + result.push( str.substring( lastIdx, match.index ) ); + result.push( match[ 0 ] ); // push the splitting char(s) + + lastIdx = match.index + match[ 0 ].length; + } + result.push( str.substring( lastIdx ) ); + + return result; + } + + }; + /*global Autolinker */ + /** + * @private + * @class Autolinker.HtmlParser + * @extends Object + * + * An HTML parser implementation which simply walks an HTML string and calls the provided visitor functions to process + * HTML and text nodes. + * + * Autolinker uses this to only link URLs/emails/Twitter handles within text nodes, basically ignoring HTML tags. + */ + Autolinker.HtmlParser = Autolinker.Util.extend( Object, { + + /** + * @private + * @property {RegExp} htmlRegex + * + * The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and + * attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html. + * + * Capturing groups: + * + * 1. If it is an end tag, this group will have the '/'. + * 2. The tag name. + */ + htmlRegex : (function() { + var tagNameRegex = /[0-9a-zA-Z:]+/, + attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/, // the unicode range accounts for excluding control chars, and the delete char + attrValueRegex = /(?:".*?"|'.*?'|[^'"=<>`\s]+)/, // double quoted, single quoted, or unquoted attribute values + nameEqualsValueRegex = attrNameRegex.source + '(?:\\s*=\\s*' + attrValueRegex.source + ')?'; // optional '=[value]' + + return new RegExp( [ + '<(?:!|(/))?', // Beginning of a tag. Either '<' for a start tag, ' tag. The slash or an empty string is Capturing Group 1. + + // The tag name (Capturing Group 2) + '(' + tagNameRegex.source + ')', + + // Zero or more attributes following the tag name + '(?:', + '\\s+', // one or more whitespace chars before an attribute + + // Either: + // A. tag="value", or + // B. "value" alone (for tag. Ex: ) + '(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')', + ')*', + + '\\s*/?', // any trailing spaces and optional '/' before the closing '>' + '>' + ].join( "" ), 'g' ); + } )(), + + + /** + * Walks an HTML string, calling the `options.processHtmlNode` function for each HTML tag that is encountered, and calling + * the `options.processTextNode` function when each text around HTML tags is encountered. + * + * @param {String} html The HTML to parse. + * @param {Object} [options] An Object (map) which may contain the following properties: + * + * @param {Function} [options.processHtmlNode] A visitor function which allows processing of an encountered HTML node. + * This function is called with the following arguments: + * @param {String} [options.processHtmlNode.tagText] The HTML tag text that was found. + * @param {String} [options.processHtmlNode.tagName] The tag name for the HTML tag that was found. Ex: 'a' for an anchor tag. + * @param {String} [options.processHtmlNode.isClosingTag] `true` if the tag is a closing tag (ex: </a>), `false` otherwise. + * + * @param {Function} [options.processTextNode] A visitor function which allows processing of an encountered text node. + * This function is called with the following arguments: + * @param {String} [options.processTextNode.text] The text node that was matched. + */ + parse : function( html, options ) { + options = options || {}; + + var processHtmlNodeVisitor = options.processHtmlNode || function() {}, + processTextNodeVisitor = options.processTextNode || function() {}, + htmlRegex = this.htmlRegex, + currentResult, + lastIndex = 0; + + // Loop over the HTML string, ignoring HTML tags, and processing the text that lies between them, + // wrapping the URLs in anchor tags + while( ( currentResult = htmlRegex.exec( html ) ) !== null ) { + var tagText = currentResult[ 0 ], + tagName = currentResult[ 2 ], + isClosingTag = !!currentResult[ 1 ], + inBetweenTagsText = html.substring( lastIndex, currentResult.index ); + + if( inBetweenTagsText ) { + processTextNodeVisitor( inBetweenTagsText ); + } + + processHtmlNodeVisitor( tagText, tagName, isClosingTag ); + + lastIndex = currentResult.index + tagText.length; + } + + // Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements. + if( lastIndex < html.length ) { + var text = html.substring( lastIndex ); + + if( text ) { + processTextNodeVisitor( text ); + } + } + } + + } ); + /*global Autolinker */ + /*jshint boss:true */ + /** + * @class Autolinker.HtmlTag + * @extends Object + * + * Represents an HTML tag, which can be used to easily build/modify HTML tags programmatically. + * + * Autolinker uses this abstraction to create HTML tags, and then write them out as strings. You may also use + * this class in your code, especially within a {@link Autolinker#replaceFn replaceFn}. + * + * ## Examples + * + * Example instantiation: + * + * var tag = new Autolinker.HtmlTag( { + * tagName : 'a', + * attrs : { 'href': 'http://google.com', 'class': 'external-link' }, + * innerHtml : 'Google' + * } ); + * + * tag.toString(); // Google + * + * // Individual accessor methods + * tag.getTagName(); // 'a' + * tag.getAttr( 'href' ); // 'http://google.com' + * tag.hasClass( 'external-link' ); // true + * + * + * Using mutator methods (which may be used in combination with instantiation config properties): + * + * var tag = new Autolinker.HtmlTag(); + * tag.setTagName( 'a' ); + * tag.setAttr( 'href', 'http://google.com' ); + * tag.addClass( 'external-link' ); + * tag.setInnerHtml( 'Google' ); + * + * tag.getTagName(); // 'a' + * tag.getAttr( 'href' ); // 'http://google.com' + * tag.hasClass( 'external-link' ); // true + * + * tag.toString(); // Google + * + * + * ## Example use within a {@link Autolinker#replaceFn replaceFn} + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance, configured with the Match's href and anchor text + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + * + * + * ## Example use with a new tag for the replacement + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = new Autolinker.HtmlTag( { + * tagName : 'button', + * attrs : { 'title': 'Load URL: ' + match.getAnchorHref() }, + * innerHtml : 'Load URL: ' + match.getAnchorText() + * } ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test + */ + Autolinker.HtmlTag = Autolinker.Util.extend( Object, { + + /** + * @cfg {String} tagName + * + * The tag name. Ex: 'a', 'button', etc. + * + * Not required at instantiation time, but should be set using {@link #setTagName} before {@link #toString} + * is executed. + */ + + /** + * @cfg {Object.} attrs + * + * An key/value Object (map) of attributes to create the tag with. The keys are the attribute names, and the + * values are the attribute values. + */ + + /** + * @cfg {String} innerHtml + * + * The inner HTML for the tag. + * + * Note the camel case name on `innerHtml`. Acronyms are camelCased in this utility (such as not to run into the acronym + * naming inconsistency that the DOM developers created with `XMLHttpRequest`). You may alternatively use {@link #innerHTML} + * if you prefer, but this one is recommended. + */ + + /** + * @cfg {String} innerHTML + * + * Alias of {@link #innerHtml}, accepted for consistency with the browser DOM api, but prefer the camelCased version + * for acronym names. + */ + + + /** + * @protected + * @property {RegExp} whitespaceRegex + * + * Regular expression used to match whitespace in a string of CSS classes. + */ + whitespaceRegex : /\s+/, + + + /** + * @constructor + * @param {Object} [cfg] The configuration properties for this class, in an Object (map) + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + + this.innerHtml = this.innerHtml || this.innerHTML; // accept either the camelCased form or the fully capitalized acronym + }, + + + /** + * Sets the tag name that will be used to generate the tag with. + * + * @param {String} tagName + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setTagName : function( tagName ) { + this.tagName = tagName; + return this; + }, + + + /** + * Retrieves the tag name. + * + * @return {String} + */ + getTagName : function() { + return this.tagName || ""; + }, + + + /** + * Sets an attribute on the HtmlTag. + * + * @param {String} attrName The attribute name to set. + * @param {String} attrValue The attribute value to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setAttr : function( attrName, attrValue ) { + var tagAttrs = this.getAttrs(); + tagAttrs[ attrName ] = attrValue; + + return this; + }, + + + /** + * Retrieves an attribute from the HtmlTag. If the attribute does not exist, returns `undefined`. + * + * @param {String} name The attribute name to retrieve. + * @return {String} The attribute's value, or `undefined` if it does not exist on the HtmlTag. + */ + getAttr : function( attrName ) { + return this.getAttrs()[ attrName ]; + }, + + + /** + * Sets one or more attributes on the HtmlTag. + * + * @param {Object.} attrs A key/value Object (map) of the attributes to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setAttrs : function( attrs ) { + var tagAttrs = this.getAttrs(); + Autolinker.Util.assign( tagAttrs, attrs ); + + return this; + }, + + + /** + * Retrieves the attributes Object (map) for the HtmlTag. + * + * @return {Object.} A key/value object of the attributes for the HtmlTag. + */ + getAttrs : function() { + return this.attrs || ( this.attrs = {} ); + }, + + + /** + * Sets the provided `cssClass`, overwriting any current CSS classes on the HtmlTag. + * + * @param {String} cssClass One or more space-separated CSS classes to set (overwrite). + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setClass : function( cssClass ) { + return this.setAttr( 'class', cssClass ); + }, + + + /** + * Convenience method to add one or more CSS classes to the HtmlTag. Will not add duplicate CSS classes. + * + * @param {String} cssClass One or more space-separated CSS classes to add. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + addClass : function( cssClass ) { + var classAttr = this.getClass(), + whitespaceRegex = this.whitespaceRegex, + indexOf = Autolinker.Util.indexOf, // to support IE8 and below + classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), + newClasses = cssClass.split( whitespaceRegex ), + newClass; + + while( newClass = newClasses.shift() ) { + if( indexOf( classes, newClass ) === -1 ) { + classes.push( newClass ); + } + } + + this.getAttrs()[ 'class' ] = classes.join( " " ); + return this; + }, + + + /** + * Convenience method to remove one or more CSS classes from the HtmlTag. + * + * @param {String} cssClass One or more space-separated CSS classes to remove. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + removeClass : function( cssClass ) { + var classAttr = this.getClass(), + whitespaceRegex = this.whitespaceRegex, + indexOf = Autolinker.Util.indexOf, // to support IE8 and below + classes = ( !classAttr ) ? [] : classAttr.split( whitespaceRegex ), + removeClasses = cssClass.split( whitespaceRegex ), + removeClass; + + while( classes.length && ( removeClass = removeClasses.shift() ) ) { + var idx = indexOf( classes, removeClass ); + if( idx !== -1 ) { + classes.splice( idx, 1 ); + } + } + + this.getAttrs()[ 'class' ] = classes.join( " " ); + return this; + }, + + + /** + * Convenience method to retrieve the CSS class(es) for the HtmlTag, which will each be separated by spaces when + * there are multiple. + * + * @return {String} + */ + getClass : function() { + return this.getAttrs()[ 'class' ] || ""; + }, + + + /** + * Convenience method to check if the tag has a CSS class or not. + * + * @param {String} cssClass The CSS class to check for. + * @return {Boolean} `true` if the HtmlTag has the CSS class, `false` otherwise. + */ + hasClass : function( cssClass ) { + return ( ' ' + this.getClass() + ' ' ).indexOf( ' ' + cssClass + ' ' ) !== -1; + }, + + + /** + * Sets the inner HTML for the tag. + * + * @param {String} html The inner HTML to set. + * @return {Autolinker.HtmlTag} This HtmlTag instance, so that method calls may be chained. + */ + setInnerHtml : function( html ) { + this.innerHtml = html; + + return this; + }, + + + /** + * Retrieves the inner HTML for the tag. + * + * @return {String} + */ + getInnerHtml : function() { + return this.innerHtml || ""; + }, + + + /** + * Override of superclass method used to generate the HTML string for the tag. + * + * @return {String} + */ + toString : function() { + var tagName = this.getTagName(), + attrsStr = this.buildAttrsStr(); + + attrsStr = ( attrsStr ) ? ' ' + attrsStr : ''; // prepend a space if there are actually attributes + + return [ '<', tagName, attrsStr, '>', this.getInnerHtml(), '' ].join( "" ); + }, + + + /** + * Support method for {@link #toString}, returns the string space-separated key="value" pairs, used to populate + * the stringified HtmlTag. + * + * @protected + * @return {String} Example return: `attr1="value1" attr2="value2"` + */ + buildAttrsStr : function() { + if( !this.attrs ) return ""; // no `attrs` Object (map) has been set, return empty string + + var attrs = this.getAttrs(), + attrsArr = []; + + for( var prop in attrs ) { + if( attrs.hasOwnProperty( prop ) ) { + attrsArr.push( prop + '="' + attrs[ prop ] + '"' ); + } + } + return attrsArr.join( " " ); + } + + } ); + /*global Autolinker */ + /*jshint sub:true */ + /** + * @protected + * @class Autolinker.AnchorTagBuilder + * @extends Object + * + * Builds anchor (<a>) tags for the Autolinker utility when a match is found. + * + * Normally this class is instantiated, configured, and used internally by an {@link Autolinker} instance, but may + * actually be retrieved in a {@link Autolinker#replaceFn replaceFn} to create {@link Autolinker.HtmlTag HtmlTag} instances + * which may be modified before returning from the {@link Autolinker#replaceFn replaceFn}. For example: + * + * var html = Autolinker.link( "Test google.com", { + * replaceFn : function( autolinker, match ) { + * var tag = autolinker.getTagBuilder().build( match ); // returns an {@link Autolinker.HtmlTag} instance + * tag.setAttr( 'rel', 'nofollow' ); + * + * return tag; + * } + * } ); + * + * // generated html: + * // Test google.com + */ + Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { + + /** + * @cfg {Boolean} newWindow + * @inheritdoc Autolinker#newWindow + */ + + /** + * @cfg {Number} truncate + * @inheritdoc Autolinker#truncate + */ + + /** + * @cfg {String} className + * @inheritdoc Autolinker#className + */ + + + /** + * @constructor + * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + }, + + + /** + * Generates the actual anchor (<a>) tag to use in place of the matched URL/email/Twitter text, + * via its `match` object. + * + * @param {Autolinker.match.Match} match The Match instance to generate an anchor tag from. + * @return {Autolinker.HtmlTag} The HtmlTag instance for the anchor tag. + */ + build : function( match ) { + var tag = new Autolinker.HtmlTag( { + tagName : 'a', + attrs : this.createAttrs( match.getType(), match.getAnchorHref() ), + innerHtml : this.processAnchorText( match.getAnchorText() ) + } ); + + return tag; + }, + + + /** + * Creates the Object (map) of the HTML attributes for the anchor (<a>) tag being generated. + * + * @protected + * @param {"url"/"email"/"twitter"} matchType The type of match that an anchor tag is being generated for. + * @param {String} href The href for the anchor tag. + * @return {Object} A key/value Object (map) of the anchor tag's attributes. + */ + createAttrs : function( matchType, anchorHref ) { + var attrs = { + 'href' : anchorHref // we'll always have the `href` attribute + }; + + var cssClass = this.createCssClass( matchType ); + if( cssClass ) { + attrs[ 'class' ] = cssClass; + } + if( this.newWindow ) { + attrs[ 'target' ] = "_blank"; + } + + return attrs; + }, + + + /** + * Creates the CSS class that will be used for a given anchor tag, based on the `matchType` and the {@link #className} + * config. + * + * @private + * @param {"url"/"email"/"twitter"} matchType The type of match that an anchor tag is being generated for. + * @return {String} The CSS class string for the link. Example return: "myLink myLink-url". If no {@link #className} + * was configured, returns an empty string. + */ + createCssClass : function( matchType ) { + var className = this.className; + + if( !className ) + return ""; + else + return className + " " + className + "-" + matchType; // ex: "myLink myLink-url", "myLink myLink-email", or "myLink myLink-twitter" + }, + + + /** + * Processes the `anchorText` by truncating the text according to the {@link #truncate} config. + * + * @private + * @param {String} anchorText The anchor tag's text (i.e. what will be displayed). + * @return {String} The processed `anchorText`. + */ + processAnchorText : function( anchorText ) { + anchorText = this.doTruncate( anchorText ); + + return anchorText; + }, + + + /** + * Performs the truncation of the `anchorText`, if the `anchorText` is longer than the {@link #truncate} option. + * Truncates the text to 2 characters fewer than the {@link #truncate} option, and adds ".." to the end. + * + * @private + * @param {String} text The anchor tag's text (i.e. what will be displayed). + * @return {String} The truncated anchor text. + */ + doTruncate : function( anchorText ) { + return Autolinker.Util.ellipsis( anchorText, this.truncate || Number.POSITIVE_INFINITY ); + } + + } ); + /*global Autolinker */ + /** + * @abstract + * @class Autolinker.match.Match + * + * Represents a match found in an input string which should be Autolinked. A Match object is what is provided in a + * {@link Autolinker#replaceFn replaceFn}, and may be used to query for details about the match. + * + * For example: + * + * var input = "..."; // string with URLs, Email Addresses, and Twitter Handles + * + * var linkedText = Autolinker.link( input, { + * replaceFn : function( autolinker, match ) { + * console.log( "href = ", match.getAnchorHref() ); + * console.log( "text = ", match.getAnchorText() ); + * + * switch( match.getType() ) { + * case 'url' : + * console.log( "url: ", match.getUrl() ); + * + * case 'email' : + * console.log( "email: ", match.getEmail() ); + * + * case 'twitter' : + * console.log( "twitter: ", match.getTwitterHandle() ); + * } + * } + * } ); + * + * See the {@link Autolinker} class for more details on using the {@link Autolinker#replaceFn replaceFn}. + */ + Autolinker.match.Match = Autolinker.Util.extend( Object, { + + /** + * @cfg {String} matchedText (required) + * + * The original text that was matched. + */ + + + /** + * @constructor + * @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + }, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @abstract + * @return {String} + */ + getType : Autolinker.Util.abstractMethod, + + + /** + * Returns the original text that was matched. + * + * @return {String} + */ + getMatchedText : function() { + return this.matchedText; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @abstract + * @return {String} + */ + getAnchorHref : Autolinker.Util.abstractMethod, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @abstract + * @return {String} + */ + getAnchorText : Autolinker.Util.abstractMethod + + } ); + /*global Autolinker */ + /** + * @class Autolinker.match.Email + * @extends Autolinker.match.Match + * + * Represents a Email match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + Autolinker.match.Email = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} email (required) + * + * The email address that was matched. + */ + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'email'; + }, + + + /** + * Returns the email address that was matched. + * + * @return {String} + */ + getEmail : function() { + return this.email; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return 'mailto:' + this.email; + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return this.email; + } + + } ); + /*global Autolinker */ + /** + * @class Autolinker.match.Twitter + * @extends Autolinker.match.Match + * + * Represents a Twitter match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + Autolinker.match.Twitter = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} twitterHandle (required) + * + * The Twitter handle that was matched. + */ + + + /** + * Returns the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'twitter'; + }, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getTwitterHandle : function() { + return this.twitterHandle; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return 'https://twitter.com/' + this.twitterHandle; + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return '@' + this.twitterHandle; + } + + } ); + /*global Autolinker */ + /** + * @class Autolinker.match.Url + * @extends Autolinker.match.Match + * + * Represents a Url match found in an input string which should be Autolinked. + * + * See this class's superclass ({@link Autolinker.match.Match}) for more details. + */ + Autolinker.match.Url = Autolinker.Util.extend( Autolinker.match.Match, { + + /** + * @cfg {String} url (required) + * + * The url that was matched. + */ + + /** + * @cfg {Boolean} protocolRelativeMatch (required) + * + * `true` if the URL is a protocol-relative match. A protocol-relative match is a URL that starts with '//', + * and will be either http:// or https:// based on the protocol that the site is loaded under. + */ + + /** + * @cfg {Boolean} stripPrefix (required) + * @inheritdoc {@link Autolinker#stripPrefix} + */ + + + /** + * @private + * @property {RegExp} urlPrefixRegex + * + * A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs. + */ + urlPrefixRegex: /^(https?:\/\/)?(www\.)?/i, + + /** + * @private + * @property {RegExp} protocolRelativeRegex + * + * The regular expression used to remove the protocol-relative '//' from the {@link #url} string, for purposes + * of {@link #getAnchorText}. A protocol-relative URL is, for example, "//yahoo.com" + */ + protocolRelativeRegex : /^\/\//, + + /** + * @protected + * @property {RegExp} checkForProtocolRegex + * + * A regular expression used to check if the {@link #url} is missing a protocol (in which case, 'http://' + * will be added). + */ + checkForProtocolRegex: /^[A-Za-z]{3,9}:/, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'url'; + }, + + + /** + * Returns the url that was matched, assuming the protocol to be 'http://' if the match + * was missing a protocol. + * + * @return {String} + */ + getUrl : function() { + var url = this.url; + + // if the url string doesn't begin with a protocol, assume http:// + if( !this.protocolRelativeMatch && !this.checkForProtocolRegex.test( url ) ) { + url = this.url = 'http://' + url; + } + + return url; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + var url = this.getUrl(); + + return url.replace( /&/g, '&' ); // any &'s in the URL should be converted back to '&' if they were displayed as & in the source html + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + var anchorText = this.getUrl(); + + if( this.protocolRelativeMatch ) { + // Strip off any protocol-relative '//' from the anchor text + anchorText = this.stripProtocolRelativePrefix( anchorText ); + } + if( this.stripPrefix ) { + anchorText = this.stripUrlPrefix( anchorText ); + } + anchorText = this.removeTrailingSlash( anchorText ); // remove trailing slash, if there is one + + return anchorText; + }, + + + // --------------------------------------- + + // Utility Functionality + + /** + * Strips the URL prefix (such as "http://" or "https://") from the given text. + * + * @private + * @param {String} text The text of the anchor that is being generated, for which to strip off the + * url prefix (such as stripping off "http://") + * @return {String} The `anchorText`, with the prefix stripped. + */ + stripUrlPrefix : function( text ) { + return text.replace( this.urlPrefixRegex, '' ); + }, + + + /** + * Strips any protocol-relative '//' from the anchor text. + * + * @private + * @param {String} text The text of the anchor that is being generated, for which to strip off the + * protocol-relative prefix (such as stripping off "//") + * @return {String} The `anchorText`, with the protocol-relative prefix stripped. + */ + stripProtocolRelativePrefix : function( text ) { + return text.replace( this.protocolRelativeRegex, '' ); + }, + + + /** + * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed. + * + * @private + * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing + * slash ('/') that may exist. + * @return {String} The `anchorText`, with the trailing slash removed. + */ + removeTrailingSlash : function( anchorText ) { + if( anchorText.charAt( anchorText.length - 1 ) === '/' ) { + anchorText = anchorText.slice( 0, -1 ); + } + return anchorText; + } + + } ); + + return Autolinker; + +} ) ); \ No newline at end of file diff --git a/vendors/Autolinker/_/Autolinker.min.js b/vendors/Autolinker/_/Autolinker.min.js new file mode 100644 index 000000000..5974a6de3 --- /dev/null +++ b/vendors/Autolinker/_/Autolinker.min.js @@ -0,0 +1,10 @@ +/*! + * Autolinker.js + * 0.12.2 + * + * Copyright(c) 2014 Gregory Jacobs + * MIT Licensed. http://www.opensource.org/licenses/mit-license.php + * + * https://github.com/gregjacobs/Autolinker.js + */ +!function(a,b){"function"==typeof define&&define.amd?define(b):"undefined"!=typeof exports?module.exports=b():a.Autolinker=b()}(this,function(){var a=function(b){a.Util.assign(this,b)};return a.prototype={constructor:a,urls:!0,email:!0,twitter:!0,newWindow:!0,stripPrefix:!0,className:"",htmlCharacterEntitiesRegex:/( | |<|<|>|>)/gi,matcherRegex:function(){var a=/(^|[^\w])@(\w{1,15})/,b=/(?:[\-;:&=\+\$,\w\.]+@)/,c=/(?:[A-Za-z]{3,9}:(?:\/\/)?)/,d=/(?:www\.)/,e=/[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/,f=/\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/,g=/(?:[\-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|])?/;return new RegExp(["(",a.source,")","|","(",b.source,e.source,f.source,")","|","(","(?:","(?:",c.source,e.source,")","|","(?:","(.?//)?",d.source,e.source,")","|","(?:","(.?//)?",e.source,f.source,")",")",g.source,")"].join(""),"gi")}(),invalidProtocolRelMatchRegex:/^[\w]\/\//,charBeforeProtocolRelMatchRegex:/^(.)?\/\//,link:function(b){var c=this,d=this.getHtmlParser(),e=this.htmlCharacterEntitiesRegex,f=0,g=[];return d.parse(b,{processHtmlNode:function(a,b,c){"a"===b&&(c?f=Math.max(f-1,0):f++),g.push(a)},processTextNode:function(b){if(0===f)for(var d=a.Util.splitAndCapture(b,e),h=0,i=d.length;i>h;h++){var j=d[h],k=c.processTextNode(j);g.push(k)}else g.push(b)}}),g.join("")},getHtmlParser:function(){var b=this.htmlParser;return b||(b=this.htmlParser=new a.HtmlParser),b},getTagBuilder:function(){var b=this.tagBuilder;return b||(b=this.tagBuilder=new a.AnchorTagBuilder({newWindow:this.newWindow,truncate:this.truncate,className:this.className})),b},processTextNode:function(b){var c=this,d=this.charBeforeProtocolRelMatchRegex;return b.replace(this.matcherRegex,function(b,e,f,g,h,i,j,k){var l,m=e,n=f,o=g,p=h,q=i,r=j||k,s="",t="";if(!c.isValidMatch(m,p,q,r))return b;if(c.matchHasUnbalancedClosingParen(b)&&(b=b.substr(0,b.length-1),t=")"),p)l=new a.match.Email({matchedText:b,email:p});else if(m)n&&(s=n,b=b.slice(1)),l=new a.match.Twitter({matchedText:b,twitterHandle:o});else{if(r){var u=r.match(d)[1]||"";u&&(s=u,b=b.slice(1))}l=new a.match.Url({matchedText:b,url:b,protocolRelativeMatch:r,stripPrefix:c.stripPrefix})}var v=c.createMatchReturnVal(l,b);return s+v+t})},isValidMatch:function(a,b,c,d){return a&&!this.twitter||b&&!this.email||c&&!this.urls||c&&-1===c.indexOf(".")||c&&/^[A-Za-z]{3,9}:/.test(c)&&!/:.*?[A-Za-z]/.test(c)||d&&this.invalidProtocolRelMatchRegex.test(d)?!1:!0},matchHasUnbalancedClosingParen:function(a){var b=a.charAt(a.length-1);if(")"===b){var c=a.match(/\(/g),d=a.match(/\)/g),e=c&&c.length||0,f=d&&d.length||0;if(f>e)return!0}return!1},createMatchReturnVal:function(b,c){var d;if(this.replaceFn&&(d=this.replaceFn.call(this,this,b)),"string"==typeof d)return d;if(d===!1)return c;if(d instanceof a.HtmlTag)return d.toString();var e=this.getTagBuilder(),f=e.build(b);return f.toString()}},a.link=function(b,c){var d=new a(c);return d.link(b)},a.match={},a.Util={abstractMethod:function(){throw"abstract"},assign:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},extend:function(b,c){var d=b.prototype,e=function(){};e.prototype=d;var f;f=c.hasOwnProperty("constructor")?c.constructor:function(){d.constructor.apply(this,arguments)};var g=f.prototype=new e;return g.constructor=f,g.superclass=d,delete c.constructor,a.Util.assign(g,c),f},ellipsis:function(a,b,c){return a.length>b&&(c=null==c?"..":c,a=a.substring(0,b-c.length)+c),a},indexOf:function(a,b){if(Array.prototype.indexOf)return a.indexOf(b);for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},splitAndCapture:function(a,b){if(!b.global)throw new Error("`splitRegex` must have the 'g' flag set");for(var c,d=[],e=0;c=b.exec(a);)d.push(a.substring(e,c.index)),d.push(c[0]),e=c.index+c[0].length;return d.push(a.substring(e)),d}},a.HtmlParser=a.Util.extend(Object,{htmlRegex:function(){var a=/[0-9a-zA-Z:]+/,b=/[^\s\0"'>\/=\x01-\x1F\x7F]+/,c=/(?:".*?"|'.*?'|[^'"=<>`\s]+)/,d=b.source+"(?:\\s*=\\s*"+c.source+")?";return new RegExp(["<(?:!|(/))?","("+a.source+")","(?:","\\s+","(?:",d,"|",c.source+")",")*","\\s*/?",">"].join(""),"g")}(),parse:function(a,b){b=b||{};for(var c,d=b.processHtmlNode||function(){},e=b.processTextNode||function(){},f=this.htmlRegex,g=0;null!==(c=f.exec(a));){var h=c[0],i=c[2],j=!!c[1],k=a.substring(g,c.index);k&&e(k),d(h,i,j),g=c.index+h.length}if(g",this.getInnerHtml(),""].join("")},buildAttrsStr:function(){if(!this.attrs)return"";var a=this.getAttrs(),b=[];for(var c in a)a.hasOwnProperty(c)&&b.push(c+'="'+a[c]+'"');return b.join(" ")}}),a.AnchorTagBuilder=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},build:function(b){var c=new a.HtmlTag({tagName:"a",attrs:this.createAttrs(b.getType(),b.getAnchorHref()),innerHtml:this.processAnchorText(b.getAnchorText())});return c},createAttrs:function(a,b){var c={href:b},d=this.createCssClass(a);return d&&(c["class"]=d),this.newWindow&&(c.target="_blank"),c},createCssClass:function(a){var b=this.className;return b?b+" "+b+"-"+a:""},processAnchorText:function(a){return a=this.doTruncate(a)},doTruncate:function(b){return a.Util.ellipsis(b,this.truncate||Number.POSITIVE_INFINITY)}}),a.match.Match=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},getType:a.Util.abstractMethod,getMatchedText:function(){return this.matchedText},getAnchorHref:a.Util.abstractMethod,getAnchorText:a.Util.abstractMethod}),a.match.Email=a.Util.extend(a.match.Match,{getType:function(){return"email"},getEmail:function(){return this.email},getAnchorHref:function(){return"mailto:"+this.email},getAnchorText:function(){return this.email}}),a.match.Twitter=a.Util.extend(a.match.Match,{getType:function(){return"twitter"},getTwitterHandle:function(){return this.twitterHandle},getAnchorHref:function(){return"https://twitter.com/"+this.twitterHandle},getAnchorText:function(){return"@"+this.twitterHandle}}),a.match.Url=a.Util.extend(a.match.Match,{urlPrefixRegex:/^(https?:\/\/)?(www\.)?/i,protocolRelativeRegex:/^\/\//,checkForProtocolRegex:/^[A-Za-z]{3,9}:/,getType:function(){return"url"},getUrl:function(){var a=this.url;return this.protocolRelativeMatch||this.checkForProtocolRegex.test(a)||(a=this.url="http://"+a),a},getAnchorHref:function(){var a=this.getUrl();return a.replace(/&/g,"&")},getAnchorText:function(){var a=this.getUrl();return this.protocolRelativeMatch&&(a=this.stripProtocolRelativePrefix(a)),this.stripPrefix&&(a=this.stripUrlPrefix(a)),a=this.removeTrailingSlash(a)},stripUrlPrefix:function(a){return a.replace(this.urlPrefixRegex,"")},stripProtocolRelativePrefix:function(a){return a.replace(this.protocolRelativeRegex,"")},removeTrailingSlash:function(a){return"/"===a.charAt(a.length-1)&&(a=a.slice(0,-1)),a}}),a}); \ No newline at end of file diff --git a/vendors/Autolinker/_/LICENSE b/vendors/Autolinker/_/LICENSE new file mode 100644 index 000000000..3ff145b9f --- /dev/null +++ b/vendors/Autolinker/_/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Gregory Jacobs (http://greg-jacobs.com) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendors/Autolinker/_/README.md b/vendors/Autolinker/_/README.md new file mode 100644 index 000000000..c6df0f15f --- /dev/null +++ b/vendors/Autolinker/_/README.md @@ -0,0 +1,350 @@ +# Autolinker.js + +Because I had so much trouble finding a good autolinking implementation out in the wild, I decided to roll my own. It +seemed that everything I found out there was either an implementation that didn't cover every case, or was just limited +in one way or another. + +So, this utility attempts to handle everything. It: + +- Autolinks URLs, whether or not they start with the protocol (i.e. 'http://'). In other words, it will automatically link the + text "google.com", as well as "http://google.com". +- Will properly handle URLs with special characters +- Will properly handle URLs with query parameters or a named anchor (i.e. hash) +- Will autolink email addresses. +- Will autolink Twitter handles. +- Will properly handle HTML input. The utility will not change the `href` attribute inside anchor (<a>) tags (or any other + tag/attribute for that matter), and will not accidentally wrap the inner text of an anchor tag with a new one (which would cause + doubly-nested anchor tags). + +Hope that this utility helps you as well! + + +## Installation + +#### Download + +Simply clone or download the zip of the project, and link to either `dist/Autolinker.js` or `dist/Autolinker.min.js` with a script tag: + +```html + +``` + +#### Using with the [Bower](http://bower.io) package manager: + +Command line: + +```shell +bower install Autolinker.js --save +``` + +#### Using with [Node.js](http://nodejs.org) via [npm](https://www.npmjs.org/): + +Command Line: + +```shell +npm install autolinker --save +``` + +JavaScript: + +```javascript +var Autolinker = require( 'autolinker' ); +// note: npm wants an all-lowercase package name, but the utility is a class and should be +// aliased with a captial letter +``` + + +## Usage + +Using the static `link()` method: + +```javascript +var linkedText = Autolinker.link( textToAutolink[, options] ); +``` + +Using as a class: + +```javascript +var autolinker = new Autolinker( [ options ] ); + +var linkedText = autolinker.link( textToAutoLink ); +``` + +Note: if using the same options to autolink multiple pieces of html/text, it is slightly more efficient to create a single +Autolinker instance, and run the `link()` method repeatedly (i.e. use the "class" form above). + + +#### Example: + +```javascript +var linkedText = Autolinker.link( "Check out google.com", { className: "myLink" } ); +// Produces: "Check out google.com" +``` + +## Options + +These are the options which may be specified for linking. These are specified by providing an Object as the second parameter to `Autolinker.link()`. These include: + +- **newWindow** : Boolean
+ `true` to have the links should open in a new window when clicked, `false` otherwise. Defaults to `true`.

+- **stripPrefix** : Boolean
+ `true` to have the 'http://' or 'https://' and/or the 'www.' stripped from the beginning of links, `false` otherwise. Defaults to `true`.

+- **truncate** : Number
+ A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of a link. If the URL/email/twitter is over the number of characters, it will be truncated to this length by replacing the end of the string with a two period ellipsis ('..').

+ Example: a url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters may look like this: 'yahoo.com/some/long/pat..'
+- **className** : String
+ A CSS class name to add to the generated anchor tags. This class will be added to all links, as well as this class + plus "url"/"email"/"twitter" suffixes for styling url/email/twitter links differently. + + For example, if this config is provided as "myLink", then: + + 1) URL links will have the CSS classes: "myLink myLink-url"
+ 2) Email links will have the CSS classes: "myLink myLink-email", and
+ 3) Twitter links will have the CSS classes: "myLink myLink-twitter"
+ +- **urls** : Boolean
+ `true` to have URLs auto-linked, `false` to skip auto-linking of URLs. Defaults to `true`.
+- **email** : Boolean
+ `true` to have email addresses auto-linked, `false` to skip auto-linking of email addresses. Defaults to `true`.

+- **twitter** : Boolean
+ `true` to have Twitter handles auto-linked, `false` to skip auto-linking of Twitter handles. Defaults to `true`. +- **replaceFn** : Function
+ A function to use to programmatically make replacements of matches in the input string, one at a time. See the section + Custom Replacement Function for more details. + + +For example, if you wanted to disable links from opening in new windows, you could do: + +```javascript +var linkedText = Autolinker.link( "Check out google.com", { newWindow: false } ); +// Produces: "Check out google.com" +``` + +And if you wanted to truncate the length of URLs (while also not opening in a new window), you could do: + +```javascript +var linkedText = Autolinker.link( "http://www.yahoo.com/some/long/path/to/a/file", { truncate: 25, newWindow: false } ); +// Produces: "yahoo.com/some/long/pat.." +``` + +## More Examples +One could update an entire DOM element that has unlinked text to auto-link them as such: + +```javascript +var myTextEl = document.getElementById( 'text' ); +myTextEl.innerHTML = Autolinker.link( myTextEl.innerHTML ); +``` + +Using the same pre-configured Autolinker instance in multiple locations of a codebase (usually by dependency injection): + +```javascript +var autolinker = new Autolinker( { newWindow: false, truncate: 25 } ); + +//... + +autolinker.link( "Check out http://www.yahoo.com/some/long/path/to/a/file" ); +// Produces: "Check out yahoo.com/some/long/pat.." + +//... + +autolinker.link( "Go to www.google.com" ); +// Produces: "Go to google.com" + +``` + + +## Custom Replacement Function + +A custom replacement function (`replaceFn`) may be provided to replace url/email/twitter matches on an individual basis, based +on the return from this function. + +Full example, for purposes of documenting the API: + +```javascript +var input = "..."; // string with URLs, Email Addresses, and Twitter Handles + +var linkedText = Autolinker.link( input, { + replaceFn : function( autolinker, match ) { + console.log( "href = ", match.getAnchorHref() ); + console.log( "text = ", match.getAnchorText() ); + + switch( match.getType() ) { + case 'url' : + console.log( "url: ", match.getUrl() ); + + if( match.getUrl().indexOf( 'mysite.com' ) === -1 ) { + var tag = autolinker.getTagBuilder().build( match ); // returns an `Autolinker.HtmlTag` instance, which provides mutator methods for easy changes + tag.setAttr( 'rel', 'nofollow' ); + tag.addClass( 'external-link' ); + + return tag; + + } else { + return true; // let Autolinker perform its normal anchor tag replacement + } + + case 'email' : + var email = match.getEmail(); + console.log( "email: ", email ); + + if( email === "my@own.address" ) { + return false; // don't auto-link this particular email address; leave as-is + } else { + return; // no return value will have Autolinker perform its normal anchor tag replacement (same as returning `true`) + } + + case 'twitter' : + var twitterHandle = match.getTwitterHandle(); + console.log( twitterHandle ); + + return '' + twitterHandle + ''; + } + } +} ); +``` + + +The function is provided two arguments: + +1. The Autolinker instance that is performing replacements. This can be used to query the options that the Autolinker + instance is configured with, or to retrieve its TagBuilder instance (via `autolinker.getTagBuilder()`). +2. An `Autolinker.match.Match` object which details the match that is to be replaced. + + +A replacement of the match is made based on the return value of the function. The following return values may be provided: + +- No return value (`undefined`), or `true` (Boolean): Delegate back to Autolinker to replace the match as it normally would. +- `false` (Boolean): Do not replace the current match at all - leave as-is. +- Any String: If a string is returned from the function, the string will be used directly as the replacement HTML for + the match. +- An `Autolinker.HtmlTag` instance, which can be used to build/modify an HTML tag before writing out its HTML text. + + +## Full API Docs + +The full API docs for Autolinker may be referenced at: [http://gregjacobs.github.io/Autolinker.js/docs/](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker) + + +## Changelog: + +### 0.12.3 + +- Add `Autolinker.match.Match#getMatchedText` method + +### 0.12.2 + +- Add documentation generation, and update inline documentation. + +### 0.12.1 + +- Expose the `Autolinker.HtmlTag` class, and allow it to be used in the `replaceFn` + +### 0.12.0 + +- Add `replaceFn` option + +### 0.11.0 + +- Allow Autolinker to link fully-capitalized URLs/Emails/Twitter handles. + +### 0.10.1 + +- Added fix to not autolink strings like "version:1.0", which were accidentally being interpreted as a protocol:domain string. + +### 0.10.0 + +- Added support for protocol-relative URLs (ex: `//google.com`, which will effectively either have the `http://` or `https://` + protocol depending on the protocol that is hosting the website) + +### 0.9.4 + +- Fixed an issue where a string in the form of `abc:def` would be autolinked as a protocol and domain name URL. Autolinker now + requires the domain name to have at least one period in it to be considered. + +### 0.9.3 + +- Fixed an issue where Twitter handles wouldn't be autolinked if they existed as the sole entity within parenthesis or brackets + (thanks [@busticated](https://github.com/busticated) for pointing this out and providing unit tests) + +### 0.9.2 + +- Fixed an issue with nested tags within an existing <a> tag, where the nested tags' inner text would be accidentally + removed from the output (thanks [@mjsabin01](https://github.com/mjsabin01)) + +### 0.9.1 + +- Added a patch to attempt to better handle extraneous </a> tags in the input string if any exist. This is for when the + input may have some invalid markup (for instance, on sites which allow user comments, blog posts, etc.). + +### 0.9.0 + +- Added better support for the processing of existing HTML in the input string. Now handles namespaced tags, and attribute names + with dashes or any other Unicode character (thanks [@aziraphale](https://github.com/aziraphale)) + +### 0.8.0 + +- Added `className` option for easily styling produced links (thanks [@busticated](https://github.com/busticated)) +- Refactored into a JS class. Autolinker can now be instantiated using: + +```javascript +var autolinker = new Autolinker( { newWindow: false, truncate: 25 } ); + +autolinker.link( "Check out http://www.yahoo.com/some/long/path/to/a/file" ); +// Produces: "Check out yahoo.com/some/long/pat.." +``` + +This allows options to be set on a single instance, and used throughout a codebase by injecting the `autolinker` instance as a dependency to the modules/classes that use it. (Note: Autolinker may still be used with the static `Autolinker.link()` method as was previously available as well.) + +### 0.7.0 + +- Changed build system to Grunt. +- Added AMD and CommonJS module loading support (ex: RequireJS, and Node.js's module loader). +- Added command line Jasmine test runner (`grunt test`) +- Upgraded Jasmine from 1.3.1 to 2.0 +- Added license header to dist files. + +(Thanks to [@busticated](https://github.com/busticated)!) + +### 0.6.1 + +- Added LICENSE file to repository. + +### 0.6.0 + +- Added options for granular control of which types are linked (urls, email addresses, and/or twitter handles). + (thanks [@aziraphale](https://github.com/aziraphale)) + +### 0.5.0 + +- Simplified the path / query string / hash processing into a single regular expression instead of 3 separate ones. +- Added support for parenthesis in URLs, such as: `en.wikipedia.org/wiki/IANA_(disambiguation)` (thanks [@dandv](https://github.com/dandv)) +- Add all known top-level domains (TLDs) (thanks [@wouter0100](https://github.com/wouter0100)) + +### 0.4.0 + +Merged pull requests from [@afeld](https://github.com/afeld): + +- strip protocol and 'www.' by default - fixes #1 +- truncate URLs from the end +- make simpler regex for detecting prefix +- remove trailing slashes from URLs, and handle periods at the end of paths +- re-use domain+TLD regexes for email matching +- add .me and .io to list of TLDs + +Thanks Aidan :) + +### 0.3.1 + +- Fixed an issue with handling nested HTML tags within anchor tags. + +### 0.3 + +- Implemented the `truncate` option. + +### 0.2 + +- Implemented autolinking Twitter handles. + +### 0.1 + +* Initial implementation, which autolinks URLs and email addresses. Working on linking Twitter handles. diff --git a/vendors/Autolinker/_/_/Autolinker.js b/vendors/Autolinker/_/_/Autolinker.js new file mode 100644 index 000000000..5b91fa54e --- /dev/null +++ b/vendors/Autolinker/_/_/Autolinker.js @@ -0,0 +1,1020 @@ +/*! + * Autolinker.js + * 0.11.2 + * + * Copyright(c) 2014 Gregory Jacobs + * MIT Licensed. http://www.opensource.org/licenses/mit-license.php + * + * https://github.com/gregjacobs/Autolinker.js + */ +/*global define, module */ +( function( root, factory ) { + + if( typeof define === 'function' && define.amd ) { + define( factory ); // Define as AMD module if an AMD loader is present (ex: RequireJS). + } else if( typeof exports !== 'undefined' ) { + module.exports = factory(); // Define as CommonJS module for Node.js, if available. + } else { + root.Autolinker = factory(); // Finally, define as a browser global if no module loader. + } +}( this, function() { + + /** + * @class Autolinker + * @extends Object + * + * Utility class used to process a given string of text, and wrap the URLs, email addresses, and Twitter handles in + * the appropriate anchor (<a>) tags to turn them into links. + * + * Any of the configuration options may be provided in an Object (map) provided to the Autolinker constructor, which + * will configure how the {@link #link link()} method will process the links. + * + * For example: + * + * var autolinker = new Autolinker( { + * newWindow : false, + * truncate : 30 + * } ); + * + * var html = autolinker.link( "Joe went to www.yahoo.com" ); + * // produces: 'Joe went to yahoo.com' + * + * + * The {@link #static-link static link()} method may also be used to inline options into a single call, which may + * be more convenient for one-off uses. For example: + * + * var html = Autolinker.link( "Joe went to www.yahoo.com", { + * newWindow : false, + * truncate : 30 + * } ); + * // produces: 'Joe went to yahoo.com' + * + * @constructor + * @param {Object} [config] The configuration options for the Autolinker instance, specified in an Object (map). + */ + var Autolinker = function( cfg ) { + Autolinker.Util.assign( this, cfg ); // assign the properties of `cfg` onto the Autolinker instance. Prototype properties will be used for missing configs. + }; + + + Autolinker.prototype = { + constructor : Autolinker, // fix constructor property + + /** + * @cfg {Boolean} urls + * + * `true` if miscellaneous URLs should be automatically linked, `false` if they should not be. + */ + urls : true, + + /** + * @cfg {Boolean} email + * + * `true` if email addresses should be automatically linked, `false` if they should not be. + */ + email : true, + + /** + * @cfg {Boolean} twitter + * + * `true` if Twitter handles ("@example") should be automatically linked, `false` if they should not be. + */ + twitter : true, + + /** + * @cfg {Boolean} newWindow + * + * `true` if the links should open in a new window, `false` otherwise. + */ + newWindow : true, + + /** + * @cfg {Boolean} stripPrefix + * + * `true` if 'http://' or 'https://' and/or the 'www.' should be stripped from the beginning of URL links' text, + * `false` otherwise. + */ + stripPrefix : true, + + /** + * @cfg {Number} truncate + * + * A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of + * a link. If the URL/email/twitter is over this number of characters, it will be truncated to this length by + * adding a two period ellipsis ('..') to the end of the string. + * + * For example: A url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters might look + * something like this: 'yahoo.com/some/long/pat..' + */ + + /** + * @cfg {String} className + * + * A CSS class name to add to the generated links. This class will be added to all links, as well as this class + * plus url/email/twitter suffixes for styling url/email/twitter links differently. + * + * For example, if this config is provided as "myLink", then: + * + * 1) URL links will have the CSS classes: "myLink myLink-url" + * 2) Email links will have the CSS classes: "myLink myLink-email", and + * 3) Twitter links will have the CSS classes: "myLink myLink-twitter" + */ + className : "", + + + /** + * @private + * @property {RegExp} htmlRegex + * + * The regular expression used to pull out HTML tags from a string. Handles namespaced HTML tags and + * attribute names, as specified by http://www.w3.org/TR/html-markup/syntax.html. + * + * Capturing groups: + * + * 1. If it is an end tag, this group will have the '/'. + * 2. The tag name. + */ + htmlRegex : (function() { + var tagNameRegex = /[0-9a-zA-Z:]+/, + attrNameRegex = /[^\s\0"'>\/=\x01-\x1F\x7F]+/, // the unicode range accounts for excluding control chars, and the delete char + attrValueRegex = /(?:".*?"|'.*?'|[^'"=<>`\s]+)/, // double quoted, single quoted, or unquoted attribute values + nameEqualsValueRegex = attrNameRegex.source + '(?:\\s*=\\s*' + attrValueRegex.source + ')?'; // optional '=[value]' + + return new RegExp( [ + '<(?:!|(/))?', // Beginning of a tag. Either '<' for a start tag, ' tag. The slash or an empty string is Capturing Group 1. + + // The tag name (Capturing Group 2) + '(' + tagNameRegex.source + ')', + + // Zero or more attributes following the tag name + '(?:', + '\\s+', // one or more whitespace chars before an attribute + + // Either: + // A. tag="value", or + // B. "value" alone (for tag. Ex: ) + '(?:', nameEqualsValueRegex, '|', attrValueRegex.source + ')', + ')*', + + '\\s*/?', // any trailing spaces and optional '/' before the closing '>' + '>' + ].join( "" ), 'g' ); + } )(), + + /** + * @private + * @property {RegExp} matcherRegex + * + * The regular expression that matches URLs, email addresses, and Twitter handles. + * + * This regular expression has the following capturing groups: + * + * 1. Group that is used to determine if there is a Twitter handle match (i.e. @someTwitterUser). Simply check for its + * existence to determine if there is a Twitter handle match. The next couple of capturing groups give information + * about the Twitter handle match. + * 2. The whitespace character before the @sign in a Twitter handle. This is needed because there are no lookbehinds in + * JS regular expressions, and can be used to reconstruct the original string in a replace(). + * 3. The Twitter handle itself in a Twitter match. If the match is '@someTwitterUser', the handle is 'someTwitterUser'. + * 4. Group that matches an email address. Used to determine if the match is an email address, as well as holding the full + * address. Ex: 'me@my.com' + * 5. Group that matches a URL in the input text. Ex: 'http://google.com', 'www.google.com', or just 'google.com'. + * This also includes a path, url parameters, or hash anchors. Ex: google.com/path/to/file?q1=1&q2=2#myAnchor + * 6. A protocol-relative ('//') match for the case of a 'www.' prefixed URL. Will be an empty string if it is not a + * protocol-relative match. We need to know the character before the '//' in order to determine if it is a valid match + * or the // was in a string we don't want to auto-link. + * 7. A protocol-relative ('//') match for the case of a known TLD prefixed URL. Will be an empty string if it is not a + * protocol-relative match. See #6 for more info. + */ + matcherRegex : (function() { + var twitterRegex = /(^|[^\w])@(\w{1,15})/, // For matching a twitter handle. Ex: @gregory_jacobs + + emailRegex = /(?:[\-;:&=\+\$,\w\.]+@)/, // something@ for email addresses (a.k.a. local-part) + + protocolRegex = /(?:[A-Za-z]{3,9}:(?:\/\/)?)/, // match protocol, allow in format http:// or mailto: + wwwRegex = /(?:www\.)/, // starting with 'www.' + domainNameRegex = /[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/, // anything looking at all like a domain, non-unicode domains, not ending in a period + tldRegex = /\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/, // match our known top level domains (TLDs) + + // Allow optional path, query string, and hash anchor, not ending in the following characters: "!:,.;" + // http://blog.codinghorror.com/the-problem-with-urls/ + urlSuffixRegex = /(?:[\-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|])?/; // note: optional part of the full regex + + return new RegExp( [ + '(', // *** Capturing group $1, which can be used to check for a twitter handle match. Use group $3 for the actual twitter handle though. $2 may be used to reconstruct the original string in a replace() + // *** Capturing group $2, which matches the whitespace character before the '@' sign (needed because of no lookbehinds), and + // *** Capturing group $3, which matches the actual twitter handle + twitterRegex.source, + ')', + + '|', + + '(', // *** Capturing group $4, which is used to determine an email match + emailRegex.source, + domainNameRegex.source, + tldRegex.source, + ')', + + '|', + + '(', // *** Capturing group $5, which is used to match a URL + '(?:', // parens to cover match for protocol (optional), and domain + '(?:', // non-capturing paren for a protocol-prefixed url (ex: http://google.com) + protocolRegex.source, + domainNameRegex.source, + ')', + + '|', + + '(?:', // non-capturing paren for a 'www.' prefixed url (ex: www.google.com) + '(.?//)?', // *** Capturing group $6 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character + wwwRegex.source, + domainNameRegex.source, + ')', + + '|', + + '(?:', // non-capturing paren for known a TLD url (ex: google.com) + '(.?//)?', // *** Capturing group $7 for an optional protocol-relative URL. Must be at the beginning of the string or start with a non-word character + domainNameRegex.source, + tldRegex.source, + ')', + ')', + + urlSuffixRegex.source, // match for path, query string, and/or hash anchor + ')' + ].join( "" ), 'gi' ); + } )(), + + /** + * @private + * @property {RegExp} invalidProtocolRelMatchRegex + * + * The regular expression used to check a potential protocol-relative URL match, coming from the {@link #matcherRegex}. + * A protocol-relative URL is, for example, "//yahoo.com" + * + * This regular expression is used in conjunction with the {@link #matcherRegex}, and checks to see if there is a word character + * before the '//' in order to determine if we should actually autolink a protocol-relative URL. This is needed because there + * is no negative look-behind in JavaScript regular expressions. + * + * For instance, we want to autolink something like "//google.com", but we don't want to autolink something + * like "abc//google.com" + */ + invalidProtocolRelMatchRegex : /^[\w]\/\//, + + /** + * @private + * @property {RegExp} charBeforeProtocolRelMatchRegex + * + * The regular expression used to retrieve the character before a protocol-relative URL match. + * + * This is used in conjunction with the {@link #matcherRegex}, which needs to grab the character before a protocol-relative + * '//' due to the lack of a negative look-behind in JavaScript regular expressions. The character before the match is stripped + * from the URL. + */ + charBeforeProtocolRelMatchRegex : /^(.)?\/\//, + + + /** + * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML. + * Does not link URLs found within HTML tags. + * + * For instance, if given the text: `You should go to http://www.yahoo.com`, then the result + * will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` + * + * @method link + * @param {String} textOrHtml The HTML or text to link URLs, email addresses, and Twitter handles within. + * @return {String} The HTML, with URLs/emails/twitter handles automatically linked. + */ + link : function( textOrHtml ) { + return this.processHtml( textOrHtml ); + }, + + + /** + * Lazily instantiates and returns the {@link #anchorTagBuilder} instance for this Autolinker instance. + * + * @return {Autolinker.AnchorTagBuilder} + */ + getAnchorTagBuilder : function() { + var anchorTagBuilder = this.anchorTagBuilder; + + if( !anchorTagBuilder ) { + anchorTagBuilder = this.anchorTagBuilder = new Autolinker.AnchorTagBuilder( { + newWindow : this.newWindow, + stripPrefix : this.stripPrefix, + truncate : this.truncate, + className : this.className + } ); + } + + return anchorTagBuilder; + }, + + + /** + * Processes the given HTML to auto-link URLs/emails/Twitter handles. + * + * Finds the text around any HTML elements in the input `html`, which will be the text that is processed. + * Any original HTML elements will be left as-is, as well as the text that is already wrapped in anchor tags. + * + * @private + * @method processHtml + * @param {String} html The input text or HTML to process in order to auto-link. + * @return {String} + */ + processHtml : function( html ) { + // Loop over the HTML string, ignoring HTML tags, and processing the text that lies between them, + // wrapping the URLs in anchor tags + var htmlRegex = this.htmlRegex, + currentResult, + inBetweenTagsText, + lastIndex = 0, + anchorTagStackCount = 0, + resultHtml = []; + + while( ( currentResult = htmlRegex.exec( html ) ) !== null ) { + var tagText = currentResult[ 0 ], + tagName = currentResult[ 2 ], + isClosingTag = !!currentResult[ 1 ]; + + inBetweenTagsText = html.substring( lastIndex, currentResult.index ); + lastIndex = currentResult.index + tagText.length; + + // Process around anchor tags, and any inner text / html they may have + if( tagName === 'a' ) { + if( !isClosingTag ) { // it's the start tag + anchorTagStackCount++; + resultHtml.push( this.processTextNode( inBetweenTagsText ) ); + + } else { // it's the end tag + anchorTagStackCount = Math.max( anchorTagStackCount - 1, 0 ); // attempt to handle extraneous tags by making sure the stack count never goes below 0 + if( anchorTagStackCount === 0 ) { + resultHtml.push( inBetweenTagsText ); // We hit the matching tag, simply add all of the text from the start tag to the end tag without linking it + } + } + + } else if( anchorTagStackCount === 0 ) { // not within an anchor tag, link the "in between" text + resultHtml.push( this.processTextNode( inBetweenTagsText ) ); + + } else { + // if we have a tag that is in between anchor tags (ex: google.com), + // just append the inner text + resultHtml.push( inBetweenTagsText ); + } + + resultHtml.push( tagText ); // now add the text of the tag itself verbatim + } + + // Process any remaining text after the last HTML element. Will process all of the text if there were no HTML elements. + if( lastIndex < html.length ) { + var processedTextNode = this.processTextNode( html.substring( lastIndex ) ); + resultHtml.push( processedTextNode ); + } + + return resultHtml.join( "" ); + }, + + + /** + * Process the text that lies inbetween HTML tags. This method does the actual wrapping of URLs with + * anchor tags. + * + * @private + * @param {String} text The text to auto-link. + * @return {String} The text with anchor tags auto-filled. + */ + processTextNode : function( text ) { + var me = this, // for closure + invalidProtocolRelMatchRegex = this.invalidProtocolRelMatchRegex, + charBeforeProtocolRelMatchRegex = this.charBeforeProtocolRelMatchRegex, + anchorTagBuilder = this.getAnchorTagBuilder(); + + return text.replace( this.matcherRegex, function( matchStr, $1, $2, $3, $4, $5, $6, $7 ) { + var twitterMatch = $1, + twitterHandlePrefixWhitespaceChar = $2, // The whitespace char before the @ sign in a Twitter handle match. This is needed because of no lookbehinds in JS regexes + twitterHandle = $3, // The actual twitterUser (i.e the word after the @ sign in a Twitter handle match) + emailAddressMatch = $4, // For both determining if it is an email address, and stores the actual email address + urlMatch = $5, // The matched URL string + protocolRelativeMatch = $6 || $7, // The '//' for a protocol-relative match, with the character that comes before the '//' + + prefixStr = "", // A string to use to prefix the anchor tag that is created. This is needed for the Twitter handle match + suffixStr = "", // A string to suffix the anchor tag that is created. This is used if there is a trailing parenthesis that should not be auto-linked. + + match; // Will be an Autolinker.Match object + + + // Return out with no changes for match types that are disabled (url, email, twitter), or for matches + // that are invalid. + if( !me.isValidMatch( twitterMatch, emailAddressMatch, urlMatch, protocolRelativeMatch ) ) { + return matchStr; + } + + + // Handle a closing parenthesis at the end of the match, and exclude it if there is not a matching open parenthesis + // in the match. This handles cases like the string "wikipedia.com/something_(disambiguation)" (which should be auto- + // linked, and when it is enclosed in parenthesis itself, such as: "(wikipedia.com/something_(disambiguation))" (in + // which the outer parens should *not* be auto-linked. + var lastChar = matchStr.charAt( matchStr.length - 1 ); + if( lastChar === ')' ) { + var openParensMatch = matchStr.match( /\(/g ), + closeParensMatch = matchStr.match( /\)/g ), + numOpenParens = ( openParensMatch && openParensMatch.length ) || 0, + numCloseParens = ( closeParensMatch && closeParensMatch.length ) || 0; + + if( numOpenParens < numCloseParens ) { + matchStr = matchStr.substr( 0, matchStr.length - 1 ); // remove the trailing ")" + suffixStr = ")"; // this will be added after the tag + } + } + + + if( twitterMatch ) { + prefixStr = twitterHandlePrefixWhitespaceChar; + match = new Autolinker.TwitterMatch( { twitterHandle: twitterHandle } ); + + } else if( emailAddressMatch ) { + match = new Autolinker.EmailMatch( { emailAddress: emailAddressMatch } ); + + } else { // url match + // If it's a protocol-relative '//' match, remove the character before the '//' (which the matcherRegex needed + // to match due to the lack of a negative look-behind in JavaScript regular expressions) + if( protocolRelativeMatch ) { + var charBeforeMatch = protocolRelativeMatch.match( charBeforeProtocolRelMatchRegex )[ 1 ] || ""; + + if( charBeforeMatch ) { + prefixStr = charBeforeMatch; // re-add the character before the '//' to what will be placed before the generated tag + matchStr = matchStr.slice( 1 ); + } + } + + match = new Autolinker.UrlMatch( { url: matchStr, protocolRelativeMatch: protocolRelativeMatch } ); + } + + // wrap the match in an anchor tag + var anchorTag = anchorTagBuilder.createAnchorTag( match.getType(), match.getAnchorHref(), match.getAnchorText() ); + return prefixStr + anchorTag + suffixStr; + } ); + }, + + + /** + * Determines if a given match found by {@link #processTextNode} is valid. Will return `false` for: + * + * 1) Disabled link types (i.e. having a Twitter match, but {@link #twitter} matching is disabled) + * 2) URL matches which do not have at least have one period ('.') in the domain name (effectively skipping over + * matches like "abc:def") + * 3) A protocol-relative url match (a URL beginning with '//') whose previous character is a word character + * (effectively skipping over strings like "abc//google.com") + * + * Otherwise, returns `true`. + * + * @private + * @param {String} twitterMatch The matched Twitter handle, if there was one. Will be empty string if the match is not a + * Twitter match. + * @param {String} emailAddressMatch The matched Email address, if there was one. Will be empty string if the match is not + * an Email address match. + * @param {String} urlMatch The matched URL, if there was one. Will be an empty string if the match is not a URL match. + * @param {String} protocolRelativeMatch The protocol-relative string for a URL match (i.e. '//'), possibly with a preceding + * character (ex, a space, such as: ' //', or a letter, such as: 'a//'). The match is invalid if there is a word character + * preceding the '//'. + * @return {Boolean} `true` if the match given is valid and should be processed, or `false` if the match is invalid and/or + * should just not be processed (such as, if it's a Twitter match, but {@link #twitter} matching is disabled}. + */ + isValidMatch : function( twitterMatch, emailAddressMatch, urlMatch, protocolRelativeMatch ) { + if( + ( twitterMatch && !this.twitter ) || ( emailAddressMatch && !this.email ) || ( urlMatch && !this.urls ) || + ( urlMatch && urlMatch.indexOf( '.' ) === -1 ) || // At least one period ('.') must exist in the URL match for us to consider it an actual URL + ( urlMatch && /^[A-Za-z]{3,9}:/.test( urlMatch ) && !/:.*?[A-Za-z]/.test( urlMatch ) ) || // At least one letter character must exist in the domain name after a protocol match. Ex: skip over something like "git:1.0" + ( protocolRelativeMatch && this.invalidProtocolRelMatchRegex.test( protocolRelativeMatch ) ) // a protocol-relative match which has a word character in front of it (so we can skip something like "abc//google.com") + ) { + return false; + } + + return true; + } + + }; + + + /** + * Automatically links URLs, email addresses, and Twitter handles found in the given chunk of HTML. + * Does not link URLs found within HTML tags. + * + * For instance, if given the text: `You should go to http://www.yahoo.com`, then the result + * will be `You should go to <a href="http://www.yahoo.com">http://www.yahoo.com</a>` + * + * Example: + * + * var linkedText = Autolinker.link( "Go to google.com", { newWindow: false } ); + * // Produces: "Go to google.com" + * + * @static + * @method link + * @param {String} html The HTML text to link URLs within. + * @param {Object} [options] Any of the configuration options for the Autolinker class, specified in an Object (map). + * See the class description for an example call. + * @return {String} The HTML text, with URLs automatically linked + */ + Autolinker.link = function( text, options ) { + var autolinker = new Autolinker( options ); + return autolinker.link( text ); + }; + + /** + * @class Autolinker.Util + * @singleton + * + * A few utility methods for Autolinker. + */ + Autolinker.Util = { + + /** + * @property {Function} abstractMethod + * + * A function object which represents an abstract method. + */ + abstractMethod : function() { throw "abstract"; }, + + + /** + * Assigns (shallow copies) the properties of `src` onto `dest`. + * + * @param {Object} dest The destination object. + * @param {Object} src The source object. + * @return {Object} The destination object. + */ + assign : function( dest, src ) { + for( var prop in src ) { + if( src.hasOwnProperty( prop ) ) { + dest[ prop ] = src[ prop ]; + } + } + + return dest; + }, + + + /** + * Extends `superclass` to create a new subclass, adding the `protoProps` to the new subclass's prototype. + * + * @param {Function} superclass The constructor function for the superclass. + * @param {Object} protoProps The methods/properties to add to the subclass's prototype. This may contain the + * special property `constructor`, which will be used as the new subclass's constructor function. + * @return {Function} The new subclass function. + */ + extend : function( superclass, protoProps ) { + var superclassProto = superclass.prototype; + + var F = function() {}; + F.prototype = superclassProto; + + var subclass; + if( protoProps.hasOwnProperty( 'constructor' ) ) { + subclass = protoProps.constructor; + } else { + subclass = function() { superclassProto.constructor.apply( this, arguments ); }; + } + + var subclassProto = subclass.prototype = new F(); // set up prototype chain + subclassProto.constructor = subclass; // fix constructor property + subclassProto.superclass = superclassProto; + + delete protoProps.constructor; // don't re-assign constructor property to the prototype, since a new function may have been created (`subclass`), which is now already there + Autolinker.Util.assign( subclassProto, protoProps ); + + return subclass; + } + + }; + /** + * @private + * @class Autolinker.AnchorTagBuilder + * @extends Object + * + * Builds the anchor (<a>) tags for the Autolinker utility when a match is found. + */ + Autolinker.AnchorTagBuilder = Autolinker.Util.extend( Object, { + + /** + * @cfg {Boolean} newWindow + * + * See {@link Autolinker#newWindow} for details. + */ + + /** + * @cfg {Boolean} stripPrefix + * + * See {@link Autolinker#stripPrefix} for details. + */ + + /** + * @cfg {Number} truncate + * + * See {@link Autolinker#truncate} for details. + */ + + /** + * @cfg {String} className + * + * See {@link Autolinker#className} for details. + */ + + + /** + * @private + * @property {RegExp} urlPrefixRegex + * + * A regular expression used to remove the 'http://' or 'https://' and/or the 'www.' from URLs. + */ + urlPrefixRegex: /^(https?:\/\/)?(www\.)?/i, + + + /** + * @constructor + * @param {Object} [cfg] The configuration options for the AnchorTagBuilder instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + }, + + + /** + * Generates the actual anchor (<a>) tag to use in place of a source url/email/twitter link. + * + * @param {"url"/"email"/"twitter"} matchType The type of match that an anchor tag is being generated for. + * @param {String} anchorHref The href for the anchor tag. + * @param {String} anchorText The anchor tag's text (i.e. what will be displayed). + * @return {String} The full HTML for the anchor tag. + */ + createAnchorTag : function( matchType, anchorHref, anchorText ) { + var attributesStr = this.createAnchorAttrsStr( matchType, anchorHref ); + anchorText = this.processAnchorText( anchorText ); + + return '' + anchorText + ''; + }, + + + /** + * Creates the string which will be the HTML attributes for the anchor (<a>) tag being generated. + * + * @private + * @param {"url"/"email"/"twitter"} matchType The type of match that an anchor tag is being generated for. + * @param {String} href The href for the anchor tag. + * @return {String} The anchor tag's attribute. Ex: `href="http://google.com" class="myLink myLink-url" target="_blank"` + */ + createAnchorAttrsStr : function( matchType, anchorHref ) { + var attrs = [ 'href="' + anchorHref + '"' ]; // we'll always have the `href` attribute + + var cssClass = this.createCssClass( matchType ); + if( cssClass ) { + attrs.push( 'class="' + cssClass + '"' ); + } + if( this.newWindow ) { + attrs.push( 'target="_blank"' ); + } + + return attrs.join( " " ); + }, + + + /** + * Creates the CSS class that will be used for a given anchor tag, based on the `matchType` and the {@link #className} + * config. + * + * @private + * @param {"url"/"email"/"twitter"} matchType The type of match that an anchor tag is being generated for. + * @return {String} The CSS class string for the link. Example return: "myLink myLink-url". If no {@link #className} + * was configured, returns an empty string. + */ + createCssClass : function( matchType ) { + var className = this.className; + + if( !className ) + return ""; + else + return className + " " + className + "-" + matchType; // ex: "myLink myLink-url", "myLink myLink-email", or "myLink myLink-twitter" + }, + + + /** + * Processes the `anchorText` by stripping the URL prefix (if {@link #stripPrefix} is `true`), removing + * any trailing slash, and truncating the text according to the {@link #truncate} config. + * + * @private + * @param {String} anchorText The anchor tag's text (i.e. what will be displayed). + * @return {String} The processed `anchorText`. + */ + processAnchorText : function( anchorText ) { + if( this.stripPrefix ) { + anchorText = this.stripUrlPrefix( anchorText ); + } + anchorText = this.removeTrailingSlash( anchorText ); // remove trailing slash, if there is one + anchorText = this.doTruncate( anchorText ); + + return anchorText; + }, + + + /** + * Strips the URL prefix (such as "http://" or "https://") from the given text. + * + * @private + * @param {String} text The text of the anchor that is being generated, for which to strip off the + * url prefix (such as stripping off "http://") + * @return {String} The `anchorText`, with the prefix stripped. + */ + stripUrlPrefix : function( text ) { + return text.replace( this.urlPrefixRegex, '' ); + }, + + + /** + * Removes any trailing slash from the given `anchorText`, in preparation for the text to be displayed. + * + * @private + * @param {String} anchorText The text of the anchor that is being generated, for which to remove any trailing + * slash ('/') that may exist. + * @return {String} The `anchorText`, with the trailing slash removed. + */ + removeTrailingSlash : function( anchorText ) { + if( anchorText.charAt( anchorText.length - 1 ) === '/' ) { + anchorText = anchorText.slice( 0, -1 ); + } + return anchorText; + }, + + + /** + * Performs the truncation of the `anchorText`, if the `anchorText` is longer than the {@link #truncate} option. + * Truncates the text to 2 characters fewer than the {@link #truncate} option, and adds ".." to the end. + * + * @private + * @param {String} text The anchor tag's text (i.e. what will be displayed). + * @return {String} The truncated anchor text. + */ + doTruncate : function( anchorText ) { + var truncateLen = this.truncate; + + // Truncate the anchor text if it is longer than the provided 'truncate' option + if( truncateLen && anchorText.length > truncateLen ) { + anchorText = anchorText.substring( 0, truncateLen - 2 ) + '..'; + } + return anchorText; + } + + } ); + /** + * @private + * @abstract + * @class Autolinker.Match + * + * Represents a match found in an input string which should be Autolinked. + */ + Autolinker.Match = Autolinker.Util.extend( Object, { + + /** + * @constructor + * @param {Object} cfg The configuration properties for the Match instance, specified in an Object (map). + */ + constructor : function( cfg ) { + Autolinker.Util.assign( this, cfg ); + }, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @abstract + * @return {String} + */ + getType : Autolinker.Util.abstractMethod, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @abstract + * @return {String} + */ + getAnchorHref : Autolinker.Util.abstractMethod, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @abstract + * @return {String} + */ + getAnchorText : Autolinker.Util.abstractMethod + + } ); + /** + * @private + * @class Autolinker.EmailMatch + * + * Represents a Email match found in an input string which should be Autolinked. + */ + Autolinker.EmailMatch = Autolinker.Util.extend( Autolinker.Match, { + + /** + * @cfg {String} emailAddress (required) + * + * The email address that was matched. + */ + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'email'; + }, + + + /** + * Returns the email address that was matched. + * + * @return {String} + */ + getEmailAddress : function() { + return this.emailAddress; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return 'mailto:' + this.emailAddress; + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return this.emailAddress; + } + + } ); + /** + * @private + * @class Autolinker.TwitterMatch + * + * Represents a Twitter match found in an input string which should be Autolinked. + */ + Autolinker.TwitterMatch = Autolinker.Util.extend( Autolinker.Match, { + + /** + * @cfg {String} twitterHandle (required) + * + * The Twitter handle that was matched. + */ + + + /** + * Returns the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'twitter'; + }, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getTwitterHandle : function() { + return this.twitterHandle; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return 'https://twitter.com/' + this.twitterHandle; + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + return '@' + this.twitterHandle; + } + + } ); + /** + * @private + * @class Autolinker.TwitterMatch + * + * Represents a Url match found in an input string which should be Autolinked. + */ + Autolinker.UrlMatch = Autolinker.Util.extend( Autolinker.Match, { + + /** + * @cfg {String} url (required) + * + * The url that was matched. + */ + + /** + * @cfg {Boolean} protocolRelativeMatch (required) + * + * `true` if the URL is a protocol-relative match. A protocol-relative match is a URL that starts with '//', + * and will be either http:// or https:// based on the protocol that the site is loaded under. + */ + + + /** + * @private + * @property {RegExp} protocolRelativeRegex + * + * The regular expression used to remove the protocol-relative '//' from the {@link #url} string, for purposes + * of {@link #getAnchorText}. A protocol-relative URL is, for example, "//yahoo.com" + */ + protocolRelativeRegex : /^\/\//, + + /** + * @protected + * @property {RegExp} checkForProtocolRegex + * + * A regular expression used to check if the {@link #url} is missing a protocol (in which case, 'http://' + * will be added). + */ + checkForProtocolRegex: /^[A-Za-z]{3,9}:/, + + + /** + * Returns a string name for the type of match that this class represents. + * + * @return {String} + */ + getType : function() { + return 'url'; + }, + + + /** + * Returns the url that was matched, assuming the protocol to be 'http://' if the match + * was missing a protocol. + * + * @return {String} + */ + getUrl : function() { + var url = this.url; + + // if the url string doesn't begin with a protocol, assume http:// + if( !this.protocolRelativeMatch && !this.checkForProtocolRegex.test( url ) ) { + url = this.url = 'http://' + url; + } + + return url; + }, + + + /** + * Returns the anchor href that should be generated for the match. + * + * @return {String} + */ + getAnchorHref : function() { + return this.getUrl(); + }, + + + /** + * Returns the anchor text that should be generated for the match. + * + * @return {String} + */ + getAnchorText : function() { + var url = this.getUrl(); + + if( this.protocolRelativeMatch ) { + // Strip off any protocol-relative '//' from the anchor text + url = url.replace( this.protocolRelativeRegex, '' ); + } + + return url; + } + + } ); + + return Autolinker; + +} ) ); \ No newline at end of file diff --git a/vendors/Autolinker/_/_/Autolinker.min.js b/vendors/Autolinker/_/_/Autolinker.min.js new file mode 100644 index 000000000..6b0a97c63 --- /dev/null +++ b/vendors/Autolinker/_/_/Autolinker.min.js @@ -0,0 +1,10 @@ +/*! + * Autolinker.js + * 0.11.2 + * + * Copyright(c) 2014 Gregory Jacobs + * MIT Licensed. http://www.opensource.org/licenses/mit-license.php + * + * https://github.com/gregjacobs/Autolinker.js + */ +!function(a,b){"function"==typeof define&&define.amd?define(b):"undefined"!=typeof exports?module.exports=b():a.Autolinker=b()}(this,function(){var a=function(b){a.Util.assign(this,b)};return a.prototype={constructor:a,urls:!0,email:!0,twitter:!0,newWindow:!0,stripPrefix:!0,className:"",htmlRegex:function(){var a=/[0-9a-zA-Z:]+/,b=/[^\s\0"'>\/=\x01-\x1F\x7F]+/,c=/(?:".*?"|'.*?'|[^'"=<>`\s]+)/,d=b.source+"(?:\\s*=\\s*"+c.source+")?";return new RegExp(["<(?:!|(/))?","("+a.source+")","(?:","\\s+","(?:",d,"|",c.source+")",")*","\\s*/?",">"].join(""),"g")}(),matcherRegex:function(){var a=/(^|[^\w])@(\w{1,15})/,b=/(?:[\-;:&=\+\$,\w\.]+@)/,c=/(?:[A-Za-z]{3,9}:(?:\/\/)?)/,d=/(?:www\.)/,e=/[A-Za-z0-9\.\-]*[A-Za-z0-9\-]/,f=/\.(?:international|construction|contractors|enterprises|photography|productions|foundation|immobilien|industries|management|properties|technology|christmas|community|directory|education|equipment|institute|marketing|solutions|vacations|bargains|boutique|builders|catering|cleaning|clothing|computer|democrat|diamonds|graphics|holdings|lighting|partners|plumbing|supplies|training|ventures|academy|careers|company|cruises|domains|exposed|flights|florist|gallery|guitars|holiday|kitchen|neustar|okinawa|recipes|rentals|reviews|shiksha|singles|support|systems|agency|berlin|camera|center|coffee|condos|dating|estate|events|expert|futbol|kaufen|luxury|maison|monash|museum|nagoya|photos|repair|report|social|supply|tattoo|tienda|travel|viajes|villas|vision|voting|voyage|actor|build|cards|cheap|codes|dance|email|glass|house|mango|ninja|parts|photo|shoes|solar|today|tokyo|tools|watch|works|aero|arpa|asia|best|bike|blue|buzz|camp|club|cool|coop|farm|fish|gift|guru|info|jobs|kiwi|kred|land|limo|link|menu|mobi|moda|name|pics|pink|post|qpon|rich|ruhr|sexy|tips|vote|voto|wang|wien|wiki|zone|bar|bid|biz|cab|cat|ceo|com|edu|gov|int|kim|mil|net|onl|org|pro|pub|red|tel|uno|wed|xxx|xyz|ac|ad|ae|af|ag|ai|al|am|an|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bm|bn|bo|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|er|es|et|eu|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|st|su|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tp|tr|tt|tv|tw|tz|ua|ug|uk|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\b/,g=/(?:[\-A-Za-z0-9+&@#\/%?=~_()|!:,.;]*[\-A-Za-z0-9+&@#\/%=~_()|])?/;return new RegExp(["(",a.source,")","|","(",b.source,e.source,f.source,")","|","(","(?:","(?:",c.source,e.source,")","|","(?:","(.?//)?",d.source,e.source,")","|","(?:","(.?//)?",e.source,f.source,")",")",g.source,")"].join(""),"gi")}(),invalidProtocolRelMatchRegex:/^[\w]\/\//,charBeforeProtocolRelMatchRegex:/^(.)?\/\//,link:function(a){return this.processHtml(a)},getAnchorTagBuilder:function(){var b=this.anchorTagBuilder;return b||(b=this.anchorTagBuilder=new a.AnchorTagBuilder({newWindow:this.newWindow,stripPrefix:this.stripPrefix,truncate:this.truncate,className:this.className})),b},processHtml:function(a){for(var b,c,d=this.htmlRegex,e=0,f=0,g=[];null!==(b=d.exec(a));){var h=b[0],i=b[2],j=!!b[1];c=a.substring(e,b.index),e=b.index+h.length,"a"===i?j?(f=Math.max(f-1,0),0===f&&g.push(c)):(f++,g.push(this.processTextNode(c))):g.push(0===f?this.processTextNode(c):c),g.push(h)}if(ey&&(b=b.substr(0,b.length-1),u=")")}if(n)t=o,m=new a.TwitterMatch({twitterHandle:p});else if(q)m=new a.EmailMatch({emailAddress:q});else{if(s){var A=s.match(d)[1]||"";A&&(t=A,b=b.slice(1))}m=new a.UrlMatch({url:b,protocolRelativeMatch:s})}var B=e.createAnchorTag(m.getType(),m.getAnchorHref(),m.getAnchorText());return t+B+u})},isValidMatch:function(a,b,c,d){return a&&!this.twitter||b&&!this.email||c&&!this.urls||c&&-1===c.indexOf(".")||c&&/^[A-Za-z]{3,9}:/.test(c)&&!/:.*?[A-Za-z]/.test(c)||d&&this.invalidProtocolRelMatchRegex.test(d)?!1:!0}},a.link=function(b,c){var d=new a(c);return d.link(b)},a.Util={abstractMethod:function(){throw"abstract"},assign:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c]);return a},extend:function(b,c){var d=b.prototype,e=function(){};e.prototype=d;var f;f=c.hasOwnProperty("constructor")?c.constructor:function(){d.constructor.apply(this,arguments)};var g=f.prototype=new e;return g.constructor=f,g.superclass=d,delete c.constructor,a.Util.assign(g,c),f}},a.AnchorTagBuilder=a.Util.extend(Object,{urlPrefixRegex:/^(https?:\/\/)?(www\.)?/i,constructor:function(b){a.Util.assign(this,b)},createAnchorTag:function(a,b,c){var d=this.createAnchorAttrsStr(a,b);return c=this.processAnchorText(c),""+c+""},createAnchorAttrsStr:function(a,b){var c=['href="'+b+'"'],d=this.createCssClass(a);return d&&c.push('class="'+d+'"'),this.newWindow&&c.push('target="_blank"'),c.join(" ")},createCssClass:function(a){var b=this.className;return b?b+" "+b+"-"+a:""},processAnchorText:function(a){return this.stripPrefix&&(a=this.stripUrlPrefix(a)),a=this.removeTrailingSlash(a),a=this.doTruncate(a)},stripUrlPrefix:function(a){return a.replace(this.urlPrefixRegex,"")},removeTrailingSlash:function(a){return"/"===a.charAt(a.length-1)&&(a=a.slice(0,-1)),a},doTruncate:function(a){var b=this.truncate;return b&&a.length>b&&(a=a.substring(0,b-2)+".."),a}}),a.Match=a.Util.extend(Object,{constructor:function(b){a.Util.assign(this,b)},getType:a.Util.abstractMethod,getAnchorHref:a.Util.abstractMethod,getAnchorText:a.Util.abstractMethod}),a.EmailMatch=a.Util.extend(a.Match,{getType:function(){return"email"},getEmailAddress:function(){return this.emailAddress},getAnchorHref:function(){return"mailto:"+this.emailAddress},getAnchorText:function(){return this.emailAddress}}),a.TwitterMatch=a.Util.extend(a.Match,{getType:function(){return"twitter"},getTwitterHandle:function(){return this.twitterHandle},getAnchorHref:function(){return"https://twitter.com/"+this.twitterHandle},getAnchorText:function(){return"@"+this.twitterHandle}}),a.UrlMatch=a.Util.extend(a.Match,{protocolRelativeRegex:/^\/\//,checkForProtocolRegex:/^[A-Za-z]{3,9}:/,getType:function(){return"url"},getUrl:function(){var a=this.url;return this.protocolRelativeMatch||this.checkForProtocolRegex.test(a)||(a=this.url="http://"+a),a},getAnchorHref:function(){return this.getUrl()},getAnchorText:function(){var a=this.getUrl();return this.protocolRelativeMatch&&(a=a.replace(this.protocolRelativeRegex,"")),a}}),a}); \ No newline at end of file diff --git a/vendors/Autolinker/_/_/LICENSE b/vendors/Autolinker/_/_/LICENSE new file mode 100644 index 000000000..3ff145b9f --- /dev/null +++ b/vendors/Autolinker/_/_/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Gregory Jacobs (http://greg-jacobs.com) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendors/Autolinker/_/_/README.md b/vendors/Autolinker/_/_/README.md new file mode 100644 index 000000000..76f327c38 --- /dev/null +++ b/vendors/Autolinker/_/_/README.md @@ -0,0 +1,255 @@ +# Autolinker.js + +Because I had so much trouble finding a good autolinking implementation out in the wild, I decided to roll my own. It +seemed that everything I found out there was either an implementation that didn't cover every case, or was just limited +in one way or another. + +So, this utility attempts to handle everything. It: + +- Autolinks URLs, whether or not they start with the protocol (i.e. 'http://'). In other words, it will automatically link the + text "google.com", as well as "http://google.com". +- Will properly handle URLs with special characters +- Will properly handle URLs with query parameters or a named anchor (i.e. hash) +- Will autolink email addresses. +- Will autolink Twitter handles. +- Will properly handle HTML input. The utility will not change the `href` attribute inside anchor (<a>) tags (or any other + tag/attribute for that matter), and will not accidentally wrap the inner text of an anchor tag with a new one (which would cause + doubly-nested anchor tags). + +Hope that this utility helps you as well! + + +## Installation + +#### Download + +Simply clone or download the zip of the project, and link to either `dist/Autolinker.js` or `dist/Autolinker.min.js` with a script tag: + +```html + +``` + +#### Using with the [Bower](http://bower.io) package manager: + +Command line: + +```shell +bower install Autolinker.js --save +``` + +#### Using with [Node.js](http://nodejs.org) via [npm](https://www.npmjs.org/): + +Command Line: + +```shell +npm install autolinker --save +``` + +JavaScript: + +```javascript +var Autolinker = require( 'autolinker' ); +// note: npm wants an all-lowercase package name, but the utility is a class and should be +// aliased with a captial letter +``` + + +## Usage + +Using the static `link()` method: + +```javascript +var linkedText = Autolinker.link( textToAutolink[, options] ); +``` + +Using as a class: + +```javascript +var autolinker = new Autolinker( [ options ] ); + +var linkedText = autolinker.link( textToAutoLink ); +``` + + +#### Example: + +```javascript +var linkedText = Autolinker.link( "Check out google.com", { className: "myLink" } ); +// Produces: "Check out google.com" +``` + +## Options + +These are the options which may be specified for linking. These are specified by providing an Object as the second parameter to `Autolinker.link()`. These include: + +- **newWindow** : Boolean
+ `true` to have the links should open in a new window when clicked, `false` otherwise. Defaults to `true`.

+- **stripPrefix** : Boolean
+ `true` to have the 'http://' or 'https://' and/or the 'www.' stripped from the beginning of links, `false` otherwise. Defaults to `true`.

+- **truncate** : Number
+ A number for how many characters long URLs/emails/twitter handles should be truncated to inside the text of a link. If the URL/email/twitter is over the number of characters, it will be truncated to this length by replacing the end of the string with a two period ellipsis ('..').

+ Example: a url like 'http://www.yahoo.com/some/long/path/to/a/file' truncated to 25 characters may look like this: 'yahoo.com/some/long/pat..'
+- **className** : String
+ A CSS class name to add to the generated anchor tags. This class will be added to all links, as well as this class + plus "url"/"email"/"twitter" suffixes for styling url/email/twitter links differently. + + For example, if this config is provided as "myLink", then: + + 1) URL links will have the CSS classes: "myLink myLink-url"
+ 2) Email links will have the CSS classes: "myLink myLink-email", and
+ 3) Twitter links will have the CSS classes: "myLink myLink-twitter"
+ +- **urls** : Boolean
+ `true` to have URLs auto-linked, `false` to skip auto-linking of URLs. Defaults to `true`.
+- **email** : Boolean
+ `true` to have email addresses auto-linked, `false` to skip auto-linking of email addresses. Defaults to `true`.

+- **twitter** : Boolean
+ `true` to have Twitter handles auto-linked, `false` to skip auto-linking of Twitter handles. Defaults to `true`. + +For example, if you wanted to disable links from opening in new windows, you could do: + +```javascript +var linkedText = Autolinker.link( "Check out google.com", { newWindow: false } ); +// Produces: "Check out google.com" +``` + +And if you wanted to truncate the length of URLs (while also not opening in a new window), you could do: + +```javascript +var linkedText = Autolinker.link( "http://www.yahoo.com/some/long/path/to/a/file", { truncate: 25, newWindow: false } ); +// Produces: "yahoo.com/some/long/pat.." +``` + +## More Examples +One could update an entire DOM element that has unlinked text to auto-link them as such: + +```javascript +var myTextEl = document.getElementById( 'text' ); +myTextEl.innerHTML = Autolinker.link( myTextEl.innerHTML ); +``` + +Using the same pre-configured Autolinker instance in multiple locations of a codebase (usually by dependency injection): + +```javascript +var autolinker = new Autolinker( { newWindow: false, truncate: 25 } ); + +//... + +autolinker.link( "Check out http://www.yahoo.com/some/long/path/to/a/file" ); +// Produces: "Check out yahoo.com/some/long/pat.." + +//... + +autolinker.link( "Go to www.google.com" ); +// Produces: "Go to google.com" + +``` + + +## Changelog: + +### 0.11.0 + +- Allow Autolinker to link fully-capitalized URLs/Emails/Twitter handles. + +### 0.10.1 + +- Added fix to not autolink strings like "version:1.0", which were accidentally being interpreted as a protocol:domain string. + +### 0.10.0 + +- Added support for protocol-relative URLs (ex: `//google.com`, which will effectively either have the `http://` or `https://` + protocol depending on the protocol that is hosting the website) + +### 0.9.4 + +- Fixed an issue where a string in the form of `abc:def` would be autolinked as a protocol and domain name URL. Autolinker now + requires the domain name to have at least one period in it to be considered. + +### 0.9.3 + +- Fixed an issue where Twitter handles wouldn't be autolinked if they existed as the sole entity within parenthesis or brackets + (thanks [@busticated](https://github.com/busticated) for pointing this out and providing unit tests) + +### 0.9.2 + +- Fixed an issue with nested tags within an existing <a> tag, where the nested tags' inner text would be accidentally + removed from the output (thanks [@mjsabin01](https://github.com/mjsabin01)) + +### 0.9.1 + +- Added a patch to attempt to better handle extraneous </a> tags in the input string if any exist. This is for when the + input may have some invalid markup (for instance, on sites which allow user comments, blog posts, etc.). + +### 0.9.0 + +- Added better support for the processing of existing HTML in the input string. Now handles namespaced tags, and attribute names + with dashes or any other Unicode character (thanks [@aziraphale](https://github.com/aziraphale)) + +### 0.8.0 + +- Added `className` option for easily styling produced links (thanks [@busticated](https://github.com/busticated)) +- Refactored into a JS class. Autolinker can now be instantiated using: + +```javascript +var autolinker = new Autolinker( { newWindow: false, truncate: 25 } ); + +autolinker.link( "Check out http://www.yahoo.com/some/long/path/to/a/file" ); +// Produces: "Check out yahoo.com/some/long/pat.." +``` + +This allows options to be set on a single instance, and used throughout a codebase by injecting the `autolinker` instance as a dependency to the modules/classes that use it. (Note: Autolinker may still be used with the static `Autolinker.link()` method as was previously available as well.) + +### 0.7.0 + +- Changed build system to Grunt. +- Added AMD and CommonJS module loading support (ex: RequireJS, and Node.js's module loader). +- Added command line Jasmine test runner (`grunt test`) +- Upgraded Jasmine from 1.3.1 to 2.0 +- Added license header to dist files. + +(Thanks to [@busticated](https://github.com/busticated)!) + +### 0.6.1 + +- Added LICENSE file to repository. + +### 0.6.0 + +- Added options for granular control of which types are linked (urls, email addresses, and/or twitter handles). + (thanks [@aziraphale](https://github.com/aziraphale)) + +### 0.5.0 + +- Simplified the path / query string / hash processing into a single regular expression instead of 3 separate ones. +- Added support for parenthesis in URLs, such as: `en.wikipedia.org/wiki/IANA_(disambiguation)` (thanks [@dandv](https://github.com/dandv)) +- Add all known top-level domains (TLDs) (thanks [@wouter0100](https://github.com/wouter0100)) + +### 0.4.0 + +Merged pull requests from [@afeld](https://github.com/afeld): + +- strip protocol and 'www.' by default - fixes #1 +- truncate URLs from the end +- make simpler regex for detecting prefix +- remove trailing slashes from URLs, and handle periods at the end of paths +- re-use domain+TLD regexes for email matching +- add .me and .io to list of TLDs + +Thanks Aidan :) + +### 0.3.1 + +- Fixed an issue with handling nested HTML tags within anchor tags. + +### 0.3 + +- Implemented the `truncate` option. + +### 0.2 + +- Implemented autolinking Twitter handles. + +### 0.1 + +* Initial implementation, which autolinks URLs and email addresses. Working on linking Twitter handles. diff --git a/vendors/Q/.coverignore b/vendors/Q/.coverignore new file mode 100644 index 000000000..1564ca042 --- /dev/null +++ b/vendors/Q/.coverignore @@ -0,0 +1 @@ +spec/ diff --git a/vendors/Q/.jshintrc b/vendors/Q/.jshintrc new file mode 100644 index 000000000..387bb8d21 --- /dev/null +++ b/vendors/Q/.jshintrc @@ -0,0 +1,27 @@ +{ + "browser": true, + "node": true, + + "curly": true, + "eqeqeq": true, + "es3": true, + "newcap": false, + "noarg": true, + "nonew": true, + "quotmark": "double", + "strict": true, + "trailing": true, + "undef": true, + "unused": true, + + "globals": { + "self": false, + "bootstrap": false, + "cajaVM": false, + "define": false, + "ReturnValue": false, + "ses": false, + "setImmediate": false, + "Q": true + } +} diff --git a/vendors/Q/.mailmap b/vendors/Q/.mailmap new file mode 100644 index 000000000..281850a34 --- /dev/null +++ b/vendors/Q/.mailmap @@ -0,0 +1,2 @@ +Domenic Denicola +Kris Kowal diff --git a/vendors/Q/.travis.yml b/vendors/Q/.travis.yml new file mode 100644 index 000000000..d68110e66 --- /dev/null +++ b/vendors/Q/.travis.yml @@ -0,0 +1,5 @@ +language: node_js +node_js: + - "0.10" +script: + npm run lint && npm test diff --git a/vendors/Q/CHANGES.md b/vendors/Q/CHANGES.md new file mode 100644 index 000000000..5d9eca76b --- /dev/null +++ b/vendors/Q/CHANGES.md @@ -0,0 +1,762 @@ + + +## 1.1.2 + + - Removed extraneous files from the npm package by using the "files" + whitelist in package.json instead of the .npmignore blacklist. + (@anton-rudeshko) + +## 1.1.1 + + - Fix a pair of regressions in bootstrapping, one which precluded + WebWorker support, and another that precluded support in + `` + + + diff --git a/vendors/Q/examples/async-generators/1-return.js b/vendors/Q/examples/async-generators/1-return.js new file mode 100644 index 000000000..7aab6db44 --- /dev/null +++ b/vendors/Q/examples/async-generators/1-return.js @@ -0,0 +1,19 @@ +"use strict"; + +var Q = require("../../q"); + +var generator = Q.async(function* () { + var ten = yield 10; + console.log(ten, 10); + var twenty = yield ten + 10; + console.log(twenty, 20); + var thirty = yield twenty + 10; + console.log(thirty, 30); + return thirty + 10; +}); + +generator().then(function (forty) { + console.log(forty, 40); +}, function (reason) { + console.log("reason", reason); +}); diff --git a/vendors/Q/examples/async-generators/2-error-propagation.js b/vendors/Q/examples/async-generators/2-error-propagation.js new file mode 100644 index 000000000..ccd788bd5 --- /dev/null +++ b/vendors/Q/examples/async-generators/2-error-propagation.js @@ -0,0 +1,21 @@ +"use strict"; + +var Q = require("../../q"); + +var generator = Q.async(function* () { + try { + var ten = yield Q.reject(new Error("Rejected!")); + console.log("Should not get here 1"); + } catch (exception) { + console.log("Should get here 1"); + console.log(exception.message, "should be", "Rejected!"); + throw new Error("Threw!"); + } +}); + +generator().then(function () { + console.log("Should not get here 2"); +}, function (reason) { + console.log("Should get here 2"); + console.log(reason.message, "should be", "Threw!"); +}); diff --git a/vendors/Q/examples/async-generators/3-spawn.js b/vendors/Q/examples/async-generators/3-spawn.js new file mode 100644 index 000000000..85fe0672c --- /dev/null +++ b/vendors/Q/examples/async-generators/3-spawn.js @@ -0,0 +1,21 @@ +"use strict"; + +var Q = require("../../q"); + +function foo() { + return Q.delay(5, 1000); +} + +function bar() { + return Q.delay(10, 1000); +} + +Q.spawn(function* () { + var x = yield foo(); + console.log(x); + + var y = yield bar(); + console.log(y); + + console.log("result", x + y); +}); diff --git a/vendors/Q/examples/async-generators/4-flow-control.js b/vendors/Q/examples/async-generators/4-flow-control.js new file mode 100644 index 000000000..017bf8a0d --- /dev/null +++ b/vendors/Q/examples/async-generators/4-flow-control.js @@ -0,0 +1,42 @@ +"use strict"; + +var Q = require("../../q"); + +// We get back blocking semantics: can use promises with `if`, `while`, `for`, +// etc. +var filter = Q.async(function* (promises, test) { + var results = []; + for (var i = 0; i < promises.length; i++) { + var val = yield promises[i]; + if (test(val)) { + results.push(val); + } + } + return results; +}); + +var promises = [ + Q.delay("a", 500), + Q.delay("d", 1000), + Q("l") +]; + +filter(promises, function (letter) { + return "f" > letter; +}).done(function (all) { + console.log(all); // [ "a", "d" ] +}); + + +// we can use try and catch to handle rejected promises +var logRejections = Q.async(function* (work) { + try { + yield work; + console.log("Never end up here"); + } catch (e) { + console.log("Caught:", e.message); + } +}); + +var rejection = Q.reject(new Error("Oh dear")); +logRejections(rejection); // Caught: Oh dear diff --git a/vendors/Q/examples/async-generators/README.md b/vendors/Q/examples/async-generators/README.md new file mode 100644 index 000000000..c409f0b0e --- /dev/null +++ b/vendors/Q/examples/async-generators/README.md @@ -0,0 +1,66 @@ +:warning: Warning: The behavior described here is likely to be quickly +obseleted by developments in standardization and implementation. Tread with +care. + +Q has an `async` function. This can be used to decorate a generator function +such that `yield` is effectively equivalent to `await` or `defer` syntax as +supported by languages like Go and C# 5. + +Generator functions are presently on the standards track for ES6. As of July +2013, they are only fully supported by bleeding edge V8, which hasn't made it +out to a released Chromium yet but will probably be in Chromium 29. Even then, +they must be enabled from [chrome://flags](chrome://flags) as "Experimental +JavaScript features." SpiderMonkey (used in Firefox) includes an older style of +generators, but these are not supported by Q. + +Here's an example of using generators by themselves, without any Q features: + +```js +function* count() { + var i = 0; + while (true) { + yield i++; + } +} + +var counter = count(); +counter.next().value === 0; +counter.next().value === 1; +counter.next().value === 2; +``` + +`yield` can also return a value, if the `next` method of the generator is +called with a parameter: + +```js +var buffer = (function* () { + var x; + while (true) { + x = yield x; + } +}()); + +buffer.next(1).value === undefined; +buffer.next("a").value === 1; +buffer.value(2).value === "a"; +buffer.next().value === 2; +buffer.next().value === undefined; +buffer.next().value === undefined; +``` + +Inside functions wrapped with `Q.async`, we can use `yield` to wait for a +promise to settle: + +```js +var eventualAdd = Q.async(function* (oneP, twoP) { + var one = yield oneP; + var two = yield twoP; + return one + two; +}); + +eventualAdd(eventualOne, eventualTwo).then(function (three) { + three === 3; +}); +``` +You can see more examples of how this works, as well as the `Q.spawn` function, +in the other files in this folder. diff --git a/vendors/Q/package.json b/vendors/Q/package.json new file mode 100644 index 000000000..fa99c9ee0 --- /dev/null +++ b/vendors/Q/package.json @@ -0,0 +1,79 @@ +{ + "name": "q", + "version": "1.1.2", + "description": "A library for promises (CommonJS/Promises/A,B,D)", + "homepage": "https://github.com/kriskowal/q", + "author": "Kris Kowal (https://github.com/kriskowal)", + "keywords": [ + "q", + "promise", + "promises", + "promises-a", + "promises-aplus", + "deferred", + "future", + "async", + "flow control", + "fluent", + "browser", + "node" + ], + "contributors": [ + "Kris Kowal (https://github.com/kriskowal)", + "Irakli Gozalishvili (http://jeditoolkit.com)", + "Domenic Denicola (http://domenicdenicola.com)" + ], + "bugs": { + "mail": "kris@cixar.com", + "url": "http://github.com/kriskowal/q/issues" + }, + "license": { + "type": "MIT", + "url": "http://github.com/kriskowal/q/raw/master/LICENSE" + }, + "main": "q.js", + "files": [ + "LICENSE", + "q.js", + "queue.js" + ], + "repository": { + "type": "git", + "url": "git://github.com/kriskowal/q.git" + }, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + }, + "dependencies": {}, + "devDependencies": { + "jshint": "~2.1.9", + "cover": "*", + "jasmine-node": "1.11.0", + "opener": "*", + "promises-aplus-tests": "1.x", + "grunt": "~0.4.1", + "grunt-cli": "~0.1.9", + "grunt-contrib-uglify": "~0.2.2", + "matcha": "~0.2.0" + }, + "scripts": { + "test": "jasmine-node spec && promises-aplus-tests spec/aplus-adapter", + "test-browser": "opener spec/q-spec.html", + "benchmark": "matcha", + "lint": "jshint q.js", + "cover": "cover run node_modules/jasmine-node/bin/jasmine-node spec && cover report html && opener cover_html/index.html", + "minify": "grunt", + "prepublish": "grunt" + }, + "overlay": { + "teleport": { + "dependencies": { + "system": ">=0.0.4" + } + } + }, + "directories": { + "test": "./spec" + } +} diff --git a/vendors/Q/q.js b/vendors/Q/q.js new file mode 100644 index 000000000..49e945a26 --- /dev/null +++ b/vendors/Q/q.js @@ -0,0 +1,1937 @@ +// vim:ts=4:sts=4:sw=4: +/*! + * + * Copyright 2009-2012 Kris Kowal under the terms of the MIT + * license found at http://github.com/kriskowal/q/raw/master/LICENSE + * + * With parts by Tyler Close + * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found + * at http://www.opensource.org/licenses/mit-license.html + * Forked at ref_send.js version: 2009-05-11 + * + * With parts by Mark Miller + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +(function (definition) { + "use strict"; + + // This file will function properly as a + + + + + + + + + + + + + + + + diff --git a/vendors/Q/spec/q-spec.js b/vendors/Q/spec/q-spec.js new file mode 100644 index 000000000..005e3cbaa --- /dev/null +++ b/vendors/Q/spec/q-spec.js @@ -0,0 +1,2611 @@ +"use strict"; +/*jshint newcap: false*/ +/*global Q: true, describe: false, it: false, expect: false, beforeEach: false, + afterEach: false, require: false, jasmine: false, waitsFor: false, + runs: false */ + +if (typeof Q === "undefined" && typeof require !== "undefined") { + // For Node compatibility. + global.Q = require("../q"); + require("./lib/jasmine-promise"); +} + +var REASON = "this is not an error, but it might show up in the console"; + +// In browsers that support strict mode, it'll be `undefined`; otherwise, the global. +var calledAsFunctionThis = (function () { return this; }()); + +afterEach(function () { + Q.onerror = null; +}); + +describe("computing sum of integers using promises", function() { + it("should compute correct result without blowing stack", function () { + var array = []; + var iters = 1000; + for (var i = 1; i <= iters; i++) { + array.push(i); + } + + var pZero = Q.fulfill(0); + var result = array.reduce(function (promise, nextVal) { + return promise.then(function (currentVal) { + return Q.fulfill(currentVal + nextVal); + }); + }, pZero); + + return result.then(function (value) { + expect(value).toEqual(iters * (iters + 1) / 2); + }); + }); +}); + +describe("Q function", function () { + it("should result in a fulfilled promise when given a value", function () { + expect(Q(5).isFulfilled()).toBe(true); + }); + + it("should be the identity when given promise", function () { + var f = Q.fulfill(5); + var r = Q.reject(new Error("aaargh")); + var p = Q.promise(function () { }); + + expect(Q(f)).toBe(f); + expect(Q(r)).toBe(r); + expect(Q(p)).toBe(p); + }); +}); + +describe("defer and when", function () { + + it("resolve before when", function () { + var turn = 0; + var deferred = Q.defer(); + deferred.resolve(10); + var promise = Q.when(deferred.promise, function (value) { + expect(turn).toEqual(1); + expect(value).toEqual(10); + }); + turn++; + return promise; + }); + + it("reject before when", function () { + var turn = 0; + var deferred = Q.defer(); + deferred.reject(-1); + var promise = Q.when(deferred.promise, function () { + expect(true).toBe(false); + }, function (value) { + expect(turn).toEqual(1); + expect(value).toEqual(-1); + }); + turn++; + return promise; + }); + + it("when before resolve", function () { + var turn = 0; + var deferred = Q.defer(); + var promise = deferred.promise.then(function (value) { + expect(turn).toEqual(2); + expect(value).toEqual(10); + turn++; + }); + Q.nextTick(function () { + expect(turn).toEqual(1); + deferred.resolve(10); + turn++; + }); + turn++; + return promise; + }); + + it("when before reject", function () { + var turn = 0; + var deferred = Q.defer(); + var promise = deferred.promise.then(function () { + expect(true).toBe(false); + }, function (value) { + expect(turn).toEqual(2); + expect(value).toEqual(-1); + turn++; + }); + Q.nextTick(function () { + expect(turn).toEqual(1); + deferred.reject(-1); + turn++; + }); + turn++; + return promise; + }); + + it("resolves multiple observers", function (done) { + var nextTurn = false; + + var resolution = "Taram pam param!"; + var deferred = Q.defer(); + var count = 10; + var i = 0; + + function resolve(value) { + i++; + expect(value).toBe(resolution); + expect(nextTurn).toBe(true); + if (i === count) { + done(); + } + } + + while (++i <= count) { + Q.when(deferred.promise, resolve); + } + + deferred.resolve(resolution); + i = 0; + nextTurn = true; + }); + + it("observers called even after throw", function () { + var threw = false; + var deferred = Q.defer(); + Q.when(deferred.promise, function () { + threw = true; + throw new Error(REASON); + }); + var promise = Q.when(deferred.promise, function (value) { + expect(value).toEqual(10); + }, function () { + expect("not").toEqual("here"); + }); + deferred.resolve(10); + return promise; + }); + + it("returns `undefined` from the deferred's methods", function () { + expect(Q.defer().resolve()).toBe(undefined); + expect(Q.defer().reject()).toBe(undefined); + }); + +}); + +describe("always next tick", function () { + + it("generated by `resolve`", function () { + var turn = 0; + var promise = Q.when(Q(), function () { + expect(turn).toEqual(1); + }); + turn++; + return promise; + }); + + it("generated by `reject`", function () { + var turn = 0; + var promise = Q.when(Q.reject(), function () { + expect(true).toBe(false); + }, function () { + expect(turn).toEqual(1); + }); + turn++; + return promise; + }); + + it("allows overriding global nextTick", function () { + var spy = jasmine.createSpy(); + spyOn(Q, 'nextTick').andCallFake(function immediateTick(task){ + task(); + }); + + Q.when(Q(), spy); + + expect(spy).toHaveBeenCalled(); + expect(Q.nextTick).toHaveBeenCalled(); + }); +}); + +describe("progress", function () { + + it("calls a single progress listener", function () { + var progressed = false; + var deferred = Q.defer(); + + var promise = Q.when( + deferred.promise, + function () { + expect(progressed).toBe(true); + }, + function () { + expect(true).toBe(false); + }, + function () { + progressed = true; + } + ); + + deferred.notify(); + deferred.resolve(); + + return promise; + }); + + it("calls multiple progress listeners", function () { + var progressed1 = false; + var progressed2 = false; + var deferred = Q.defer(); + var promise = Q.when( + deferred.promise, + function () { + expect(progressed1).toBe(true); + expect(progressed2).toBe(true); + }, + function () { + expect(true).toBe(false); + }, + function () { + progressed1 = true; + } + ); + Q.when(deferred.promise, null, null, function () { + progressed2 = true; + }); + + deferred.notify(); + deferred.resolve(); + + return promise; + }); + + it("calls all progress listeners even if one throws", function () { + var progressed1 = false; + var progressed2 = false; + var progressed3 = false; + var deferred = Q.defer(); + var promise = Q.when( + deferred.promise, + function () { + expect(progressed1).toBe(true); + expect(progressed2).toBe(true); + expect(progressed3).toBe(true); + }, + function () { + expect(true).toBe(false); + }, + function () { + progressed1 = true; + } + ); + + Q.onerror = function () { }; + + Q.when(deferred.promise, null, null, function () { + progressed2 = true; + throw new Error("just a test, ok if it shows up in the console"); + }); + Q.when(deferred.promise, null, null, function () { + progressed3 = true; + }); + + deferred.notify(); + deferred.resolve(); + + return promise; + }); + + it("calls the progress listener even if later rejected", function () { + var progressed = false; + var deferred = Q.defer(); + var promise = Q.when( + deferred.promise, + function () { + expect(true).toBe(false); + }, + function () { + expect(progressed).toEqual(true); + }, + function () { + progressed = true; + } + ); + + deferred.notify(); + deferred.reject(); + + return promise; + }); + + it("calls the progress listener with the notify values", function () { + var progressValues = []; + var desiredProgressValues = [{}, {}, "foo", 5]; + var deferred = Q.defer(); + var promise = Q.when( + deferred.promise, + function () { + for (var i = 0; i < desiredProgressValues.length; ++i) { + var desired = desiredProgressValues[i]; + var actual = progressValues[i]; + expect(actual).toBe(desired); + } + }, + function () { + expect(true).toBe(false); + }, + function (value) { + progressValues.push(value); + } + ); + + for (var i = 0; i < desiredProgressValues.length; ++i) { + deferred.notify(desiredProgressValues[i]); + } + deferred.resolve(); + + return promise; + }); + + it("does not call the progress listener if notify is called after fulfillment", function () { + var deferred = Q.defer(); + var called = false; + + Q.when(deferred.promise, null, null, function () { + called = true; + }); + + deferred.resolve(); + deferred.notify(); + + return Q.delay(10).then(function () { + expect(called).toBe(false); + }); + }); + + it("does not call the progress listener if notify is called after rejection", function () { + var deferred = Q.defer(); + var called = false; + + Q.when(deferred.promise, null, null, function () { + called = true; + }); + + deferred.reject(); + deferred.notify(); + + return Q.delay(10).then(function () { + expect(called).toBe(false); + }); + }); + + it("should not save and re-emit progress notifications", function () { + var deferred = Q.defer(); + var progressValues = []; + + deferred.notify(1); + + var promise = Q.when( + deferred.promise, + function () { + expect(progressValues).toEqual([2]); + }, + function () { + expect(true).toBe(false); + }, + function (progressValue) { + progressValues.push(progressValue); + } + ); + + deferred.notify(2); + deferred.resolve(); + + return promise; + }); + + it("should allow attaching progress listeners w/ .progress", function () { + var progressed = false; + var deferred = Q.defer(); + + deferred.promise.progress(function () { + progressed = true; + }); + + deferred.notify(); + deferred.resolve(); + + return deferred.promise; + }); + + it("should allow attaching progress listeners w/ Q.progress", function () { + var progressed = false; + var deferred = Q.defer(); + + Q.progress(deferred.promise, function () { + progressed = true; + }); + + deferred.notify(); + deferred.resolve(); + + return deferred.promise; + }); + + it("should call the progress listener with undefined context", function () { + var progressed = false; + var progressContext = {}; + var deferred = Q.defer(); + var promise = Q.when( + deferred.promise, + function () { + expect(progressed).toBe(true); + expect(progressContext).toBe(calledAsFunctionThis); + }, + function () { + expect(true).toBe(false); + }, + function () { + progressed = true; + progressContext = this; + } + ); + + deferred.notify(); + deferred.resolve(); + + return promise; + }); + + it("should forward only the first notify argument to listeners", function () { + var progressValueArrays = []; + var deferred = Q.defer(); + + var promise = Q.when( + deferred.promise, + function () { + expect(progressValueArrays).toEqual([[1], [2], [4]]); + }, + function () { + expect(true).toBe(false); + }, + function () { + var args = Array.prototype.slice.call(arguments); + progressValueArrays.push(args); + } + ); + + deferred.notify(1); + deferred.notify(2, 3); + deferred.notify(4, 5, 6); + deferred.resolve(); + + return promise; + }); + + it("should work with .then as well", function () { + var progressed = false; + var deferred = Q.defer(); + + var promise = deferred.promise.then( + function () { + expect(progressed).toBe(true); + }, + function () { + expect(true).toBe(false); + }, + function () { + progressed = true; + } + ); + + deferred.notify(); + deferred.resolve(); + + return promise; + }); + + it("should re-throw all errors thrown by listeners to Q.onerror", function () { + var theError = new Error("boo!"); + + var def = Q.defer(); + def.promise.progress(function () { + throw theError; + }); + + var deferred = Q.defer(); + Q.onerror = function (error) { + expect(error).toBe(theError); + deferred.resolve(); + }; + Q.delay(100).then(deferred.reject); + + def.notify(); + + return deferred.promise; + }); +}); + +describe("promises for objects", function () { + + describe("get", function () { + + it("fulfills a promise", function () { + var deferred = Q.defer(); + deferred.resolve({a: 1}); + return deferred.promise.get("a") + .then(function (a) { + expect(a).toBe(1); + }); + }); + + it("propagates a rejection", function () { + var exception = new Error("boo!"); + return Q.fcall(function () { + throw exception; + }) + .get("a") + .then(function () { + expect("be").toBe("not to be"); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + }); + + describe("set", function () { + + it("fulfills a promise", function () { + var object = {}; + return Q(object) + .set("a", 1) + .then(function (result) { + expect(result).toBe(undefined); + expect(object.a).toBe(1); + }); + }); + + it("propagates a rejection", function () { + var exception = new Error("Gah!"); + return Q.reject(exception) + .set("a", 1) + .then(function () { + expect("frozen over").toBe("quite warm"); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + }); + + describe("del", function () { + + it("fulfills a promise", function () { + var object = {a: 10}; + return Q.fcall(function () { + return object; + }) + .del("a") + .then(function (result) { + expect("a" in object).toBe(false); + expect(result).toBe(void 0); + }, function () { + expect("up").toBe("down"); + }); + }); + + it("propagates a rejection", function () { + var exception = new Error("hah-hah"); + return Q.fcall(function () { + throw exception; + }) + .del("a") + .then(function () { + expect(true).toBe(false); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + }); + + describe("post", function () { + + it("fulfills a promise", function () { + var subject = { + a: function a(value) { + this._a = value; + return 1 + value; + } + }; + return Q.when(Q.post(subject, "a", [1]), function (two) { + expect(subject._a).toBe(1); + expect(two).toBe(2); + }); + }); + + it("works as apply when given no name", function () { + return Q(function (a, b, c) { + return a + b + c; + }) + .post(undefined, [1, 2, 3]) + .then(function (sum) { + expect(sum).toEqual(6); + }); + }); + + }); + + describe("send", function () { + + it("fulfills a promise", function () { + var foo; + var subject = { + foo: function (_bar) { + return _bar; + }, + bar: function (_foo, _bar) { + foo = _foo; + return this.foo(_bar); + } + }; + return Q.send(subject, "bar", 1, 2) + .then(function (two) { + expect(foo).toEqual(1); + expect(two).toEqual(2); + }); + }); + + it("is rejected for undefined method", function () { + var subject = {}; + return Q(subject) + .send("foo") + .then(function () { + expect("here").toEqual("not here"); + }, function () { + }); + }); + + it("is rejected for undefined object", function () { + return Q() + .send("foo") + .then(function () { + expect("here").toEqual("not here"); + }, function () { + }); + }); + + }); + + describe("keys", function () { + + function Klass (a, b) { + this.a = a; + this.b = b; + } + Klass.prototype.notOwn = 1; + + it("fulfills a promise", function () { + return Q.keys(new Klass(10, 20)) + .then(function (keys) { + expect(keys.sort()).toEqual(["a", "b"]); + }); + }); + + }); + +}); + +describe("promises for functions", function () { + + describe("fapply", function () { + it("fulfills a promise using arguments", function () { + return Q(function (a, b, c) { + return a + b + c; + }) + .fapply([1, 2, 3]) + .then(function (sum) { + expect(sum).toEqual(6); + }); + }); + }); + + describe("fcall", function () { + it("fulfills a promise using arguments", function () { + return Q(function (a, b, c) { + return a + b + c; + }) + .fcall(1, 2, 3) + .then(function (sum) { + expect(sum).toEqual(6); + }); + }); + }); + + describe("fbind", function () { + + it("accepts a promise for a function", function () { + return Q.fbind(Q(function (high, low) { + return high - low; + })) + (2, 1) + .then(function (difference) { + expect(difference).toEqual(1); + }); + }); + + it("chains partial application on a promise for a function", function () { + return Q(function (a, b) { + return a * b; + }) + .fbind(2)(3) + .then(function (product) { + expect(product).toEqual(6); + }); + }); + + it("returns a fulfilled promise", function () { + var result = {}; + var bound = Q.fbind(function () { + return result; + }); + return bound() + .then(function (_result) { + expect(_result).toBe(result); + }); + }); + + it("returns a rejected promise from a thrown error", function () { + var exception = new Error("Boo!"); + var bound = Q.fbind(function () { + throw exception; + }); + return bound() + .then(function () { + expect("flying pigs").toBe("swillin' pigs"); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + it("passes arguments through", function () { + var x = {}, y = {}; + var bound = Q.fbind(function (a, b) { + expect(a).toBe(x); + expect(b).toBe(y); + }); + return bound(x, y); + }); + + it("passes and also partially applies arguments", function () { + var x = {}, y = {}; + var bound = Q.fbind(function (a, b) { + expect(a).toBe(x); + expect(b).toBe(y); + }, x); + return bound(y); + }); + + it("doesn't bind `this`", function () { + var theThis = { me: "this" }; + var bound = Q.fbind(function () { + expect(this).toBe(theThis); + }); + + return bound.call(theThis); + }); + + }); + +}); + +describe("inspect", function () { + + it("for a fulfilled promise", function () { + expect(Q(10).inspect()).toEqual({ + state: "fulfilled", + value: 10 + }); + }); + + it("for a rejected promise", function () { + var error = new Error("In your face."); + var rejected = Q.reject(error); + expect(rejected.inspect()).toEqual({ + state: "rejected", + reason: error + }); + }); + + it("for a pending, unresolved promise", function () { + var pending = Q.defer().promise; + expect(pending.inspect()).toEqual({ state: "pending" }); + }); + + it("for a promise resolved to a rejected promise", function () { + var deferred = Q.defer(); + var error = new Error("Rejected!"); + var rejected = Q.reject(error); + deferred.resolve(rejected); + + expect(deferred.promise.inspect()).toEqual({ + state: "rejected", + reason: error + }); + }); + + it("for a promise resolved to a fulfilled promise", function () { + var deferred = Q.defer(); + var fulfilled = Q(10); + deferred.resolve(fulfilled); + + expect(deferred.promise.inspect()).toEqual({ + state: "fulfilled", + value: 10 + }); + }); + + it("for a promise resolved to a pending promise", function () { + var a = Q.defer(); + var b = Q.defer(); + a.resolve(b.promise); + + expect(a.promise.inspect()).toEqual({ state: "pending" }); + }); + +}); + +describe("promise states", function () { + + it("of fulfilled value", function () { + expect(Q.isFulfilled(void 0)).toBe(true); + expect(Q.isRejected(false)).toBe(false); + expect(Q.isPending(true)).toBe(false); + }); + + it("of fulfillment", function () { + var promise = Q(10); + expect(Q.isFulfilled(promise)).toBe(true); + expect(promise.isFulfilled()).toBe(true); + expect(Q.isRejected(promise)).toBe(false); + expect(promise.isRejected()).toBe(false); + expect(Q.isPending(promise)).toBe(false); + expect(promise.isPending()).toBe(false); + }); + + it("of rejection", function () { + var error = new Error("Oh, snap."); + var promise = Q.reject(error); + expect(promise.isFulfilled()).toBe(false); + expect(promise.isRejected()).toBe(true); + expect(promise.isPending()).toBe(false); + }); + + it("of rejection with a falsy value", function () { + var promise = Q.reject(undefined); + expect(promise.isFulfilled()).toBe(false); + expect(promise.isRejected()).toBe(true); + expect(promise.isPending()).toBe(false); + }); + + it("of deferred", function () { + var deferred = Q.defer(); + var promise = deferred.promise; + expect(promise.isFulfilled()).toBe(false); + expect(promise.isRejected()).toBe(false); + expect(promise.isPending()).toBe(true); + }); + + it("of deferred rejection", function () { + var deferred = Q.defer(); + var rejection = Q.reject(new Error("Rejected!")); + deferred.resolve(rejection); + var promise = deferred.promise; + expect(promise.isFulfilled()).toBe(false); + expect(promise.isRejected()).toBe(true); + expect(promise.isPending()).toBe(false); + }); + + it("of deferred fulfillment", function () { + var deferred = Q.defer(); + deferred.resolve(10); + var promise = deferred.promise; + expect(promise.isFulfilled()).toBe(true); + expect(promise.isRejected()).toBe(false); + expect(promise.isPending()).toBe(false); + }); + + it("of deferred deferred", function () { + var a = Q.defer(); + var b = Q.defer(); + a.resolve(b.promise); + var promise = a.promise; + expect(promise.isFulfilled()).toBe(false); + expect(promise.isRejected()).toBe(false); + expect(promise.isPending()).toBe(true); + }); + + it("of isFulfilled side effects", function () { + var deferred = Q.defer(); + var finished = false; + + waitsFor(function () { + return finished; + }); + + var parentPromise = deferred.promise; + + var childPromise = parentPromise.then(function () { + expect(parentPromise.isFulfilled()).toBe(true); + expect(childPromise.isFulfilled()).toBe(false); + + return parentPromise.then(function (value) { + finished = true; + return value + 1; + }); + }); + + deferred.resolve(1); + + runs(function () { + expect(childPromise.isPending()).toBe(false); + expect(childPromise.isRejected()).toBe(false); + expect(childPromise.isFulfilled()).toBe(true); + expect(childPromise.inspect().value).toBe(2); + }); + }); + +}); + +describe("propagation", function () { + + it("propagate through then with no callback", function () { + return Q(10) + .then() + .then(function (ten) { + expect(ten).toBe(10); + }); + }); + + it("propagate through then with modifying callback", function () { + return Q(10) + .then(function (ten) { + return ten + 10; + }) + .then(function (twen) { + expect(twen).toBe(20); + }); + }); + + it("errback recovers from exception", function () { + var error = new Error("Bah!"); + return Q.reject(error) + .then(null, function (_error) { + expect(_error).toBe(error); + return 10; + }) + .then(function (value) { + expect(value).toBe(10); + }); + }); + + it("rejection propagates through then with no errback", function () { + var error = new Error("Foolish mortals!"); + return Q.reject(error) + .then() + .then(null, function (_error) { + expect(_error).toBe(error); + }); + }); + + it("rejection intercepted and rethrown", function () { + var error = new Error("Foolish mortals!"); + var nextError = new Error("Silly humans!"); + return Q.reject(error) + .fail(function () { + throw nextError; + }) + .then(null, function (_error) { + expect(_error).toBe(nextError); + }); + }); + + it("resolution is forwarded through deferred promise", function () { + var a = Q.defer(); + var b = Q.defer(); + a.resolve(b.promise); + b.resolve(10); + return a.promise.then(function (eh) { + expect(eh).toEqual(10); + }); + }); + + it("should propagate progress by default", function () { + var d = Q.defer(); + + var progressValues = []; + var promise = d.promise + .then() + .then( + function () { + expect(progressValues).toEqual([1]); + }, + function () { + expect(true).toBe(false); + }, + function (progressValue) { + progressValues.push(progressValue); + } + ); + + d.notify(1); + d.resolve(); + + return promise; + }); + + it("should allow translation of progress in the progressback", function () { + var d = Q.defer(); + + var progressValues = []; + var promise = d.promise + .progress(function (p) { + return p + 5; + }) + .then( + function () { + expect(progressValues).toEqual([10]); + }, + function () { + expect(true).toBe(false); + }, + function (progressValue) { + progressValues.push(progressValue); + } + ); + + d.notify(5); + d.resolve(); + + return promise; + }); + + + it("should stop progress propagation if an error is thrown", function () { + var def = Q.defer(); + var p2 = def.promise.progress(function () { + throw new Error("boo!"); + }); + + Q.onerror = function () { /* just swallow it for this test */ }; + + var progressValues = []; + var result = p2.then( + function () { + expect(progressValues).toEqual([]); + }, + function () { + expect(true).toBe(false); + }, + function (progressValue) { + progressValues.push(progressValue); + } + ); + + def.notify(); + def.resolve(); + return result; + }); +}); + +describe("all", function () { + it("fulfills when passed an empty array", function () { + return Q.all([]); + }); + + it("rejects after any constituent promise is rejected", function () { + var toResolve = Q.defer(); // never resolve + var toReject = Q.defer(); + var promises = [toResolve.promise, toReject.promise]; + var promise = Q.all(promises); + + toReject.reject(new Error("Rejected")); + + return Q.delay(250) + .then(function () { + expect(promise.isRejected()).toBe(true); + }) + .timeout(1000); + }); + + it("resolves foreign thenables", function () { + var normal = Q(1); + var foreign = { then: function (f) { f(2); } }; + + return Q.all([normal, foreign]) + .then(function (result) { + expect(result).toEqual([1, 2]); + }); + }); + + it("fulfills when passed an sparse array", function () { + var toResolve = Q.defer(); + var promises = []; + promises[0] = Q(0); + promises[2] = toResolve.promise; + var promise = Q.all(promises); + + toResolve.resolve(2); + + return promise.then(function (result) { + expect(result).toEqual([0, void 0, 2]); + }); + }); + + it("modifies the input array", function () { + var input = [Q(0), Q(1)]; + + return Q.all(input).then(function (result) { + expect(result).toBe(input); + expect(input).toEqual([0, 1]); + }); + }); + + it("sends { index, value } progress updates", function () { + var deferred1 = Q.defer(); + var deferred2 = Q.defer(); + + var progressValues = []; + + Q.delay(50).then(function () { + deferred1.notify("a"); + }); + Q.delay(100).then(function () { + deferred2.notify("b"); + deferred2.resolve(); + }); + Q.delay(150).then(function () { + deferred1.notify("c"); + deferred1.resolve(); + }); + + return Q.all([deferred1.promise, deferred2.promise]).then( + function () { + expect(progressValues).toEqual([ + { index: 0, value: "a" }, + { index: 1, value: "b" }, + { index: 0, value: "c" } + ]); + }, + undefined, + function (progressValue) { + progressValues.push(progressValue); + } + ) + }); + +}); + +describe("allSettled", function () { + it("works on an empty array", function () { + return Q.allSettled([]) + .then(function (snapshots) { + expect(snapshots).toEqual([]); + }); + }); + + it("deals with a mix of non-promises and promises", function () { + return Q.allSettled([1, Q(2), Q.reject(3)]) + .then(function (snapshots) { + expect(snapshots).toEqual([ + { state: "fulfilled", value: 1 }, + { state: "fulfilled", value: 2 }, + { state: "rejected", reason: 3 } + ]); + }); + }); + + it("is settled after every constituent promise is settled", function () { + var toFulfill = Q.defer(); + var toReject = Q.defer(); + var promises = [toFulfill.promise, toReject.promise]; + var fulfilled; + var rejected; + + Q.fcall(function () { + toReject.reject(); + rejected = true; + }) + .delay(15) + .then(function () { + toFulfill.resolve(); + fulfilled = true; + }); + + return Q.allSettled(promises) + .then(function () { + expect(fulfilled).toBe(true); + expect(rejected).toBe(true); + }); + }); + + it("does not modify the input array", function () { + var input = [1, Q(2), Q.reject(3)]; + + return Q.allSettled(input) + .then(function (snapshots) { + expect(snapshots).not.toBe(input); + expect(snapshots).toEqual([ + { state: "fulfilled", value: 1 }, + { state: "fulfilled", value: 2 }, + { state: "rejected", reason: 3 } + ]); + }); + }); + +}); + +describe("spread", function () { + + it("spreads values across arguments", function () { + return Q.spread([1, 2, 3], function (a, b) { + expect(b).toBe(2); + }); + }); + + it("spreads promises for arrays across arguments", function () { + return Q([Q(10)]) + .spread(function (value) { + expect(value).toEqual(10); + }); + }); + + it("spreads arrays of promises across arguments", function () { + var deferredA = Q.defer(); + var deferredB = Q.defer(); + + var promise = Q.spread([deferredA.promise, deferredB.promise], + function (a, b) { + expect(a).toEqual(10); + expect(b).toEqual(20); + }); + + Q.delay(5).then(function () { + deferredA.resolve(10); + }); + Q.delay(10).then(function () { + deferredB.resolve(20); + }); + + return promise; + }); + + it("calls the errback when given a rejected promise", function () { + var err = new Error(); + return Q.spread([Q(10), Q.reject(err)], + function () { + expect(true).toBe(false); + }, + function (actual) { + expect(actual).toBe(err); + } + ); + }); + +}); + +describe("fin", function () { + + var exception1 = new Error("boo!"); + var exception2 = new TypeError("evil!"); + + describe("when the promise is fulfilled", function () { + + it("should call the callback", function () { + var called = false; + + return Q("foo") + .fin(function () { + called = true; + }) + .then(function () { + expect(called).toBe(true); + }); + }); + + it("should fulfill with the original value", function () { + return Q("foo") + .fin(function () { + return "bar"; + }) + .then(function (result) { + expect(result).toBe("foo"); + }); + }); + + describe("when the callback returns a promise", function () { + + describe("that is fulfilled", function () { + it("should fulfill with the original reason after that promise resolves", function () { + var promise = Q.delay(250); + + return Q("foo") + .fin(function () { + return promise; + }) + .then(function (result) { + expect(Q.isPending(promise)).toBe(false); + expect(result).toBe("foo"); + }); + }); + }); + + describe("that is rejected", function () { + it("should reject with this new rejection reason", function () { + return Q("foo") + .fin(function () { + return Q.reject(exception1); + }) + .then(function () { + expect(false).toBe(true); + }, + function (exception) { + expect(exception).toBe(exception1); + }); + }); + }); + + }); + + describe("when the callback throws an exception", function () { + it("should reject with this new exception", function () { + return Q("foo") + .fin(function () { + throw exception1; + }) + .then(function () { + expect(false).toBe(true); + }, + function (exception) { + expect(exception).toBe(exception1); + }); + }); + }); + + }); + + describe("when the promise is rejected", function () { + + it("should call the callback", function () { + var called = false; + + return Q.reject(exception1) + .fin(function () { + called = true; + }) + .then(function () { + expect(called).toBe(true); + }, function () { + expect(called).toBe(true); + }); + }); + + it("should reject with the original reason", function () { + return Q.reject(exception1) + .fin(function () { + return "bar"; + }) + .then(function () { + expect(false).toBe(true); + }, + function (exception) { + expect(exception).toBe(exception1); + }); + }); + + describe("when the callback returns a promise", function () { + + describe("that is fulfilled", function () { + it("should reject with the original reason after that promise resolves", function () { + var promise = Q.delay(250); + + return Q.reject(exception1) + .fin(function () { + return promise; + }) + .then(function () { + expect(false).toBe(true); + }, + function (exception) { + expect(exception).toBe(exception1); + expect(Q.isPending(promise)).toBe(false); + }); + }); + }); + + describe("that is rejected", function () { + it("should reject with the new reason", function () { + return Q.reject(exception1) + .fin(function () { + return Q.reject(exception2); + }) + .then(function () { + expect(false).toBe(true); + }, + function (exception) { + expect(exception).toBe(exception2); + }); + }); + }); + + }); + + describe("when the callback throws an exception", function () { + it("should reject with this new exception", function () { + return Q.reject(exception1) + .fin(function () { + throw exception2; + }) + .then(function () { + expect(false).toBe(true); + }, + function (exception) { + expect(exception).toBe(exception2); + }); + }); + }); + + }); + +}); + +// Almost like "fin" +describe("tap", function () { + var exception1 = new Error("boo!"); + + describe("when the promise is fulfilled", function () { + it("should call the callback", function () { + var called = false; + return Q("foo") + .tap(function () { + called = true; + }) + .then(function () { + expect(called).toBe(true); + }); + }); + + it("should fulfill with the original value", function () { + return Q("foo") + .tap(function () { + return "bar"; + }) + .then(function (result) { + expect(result).toBe("foo"); + }); + }); + + describe("when the callback returns a promise", function () { + describe("that is fulfilled", function () { + it("should fulfill with the original reason after that promise resolves", function () { + var promise = Q.delay(250); + + return Q("foo") + .tap(function () { + return promise; + }) + .then(function (result) { + expect(Q.isPending(promise)).toBe(false); + expect(result).toBe("foo"); + }); + }); + }); + + describe("that is rejected", function () { + it("should reject with this new rejection reason", function () { + return Q("foo") + .tap(function () { + return Q.reject(exception1); + }) + .then(function () { + expect(false).toBe(true); + }, + function (exception) { + expect(exception).toBe(exception1); + }); + }); + }); + + }); + + describe("when the callback throws an exception", function () { + it("should reject with this new exception", function () { + return Q("foo") + .tap(function () { + throw exception1; + }) + .then(function () { + expect(false).toBe(true); + }, + function (exception) { + expect(exception).toBe(exception1); + }); + }); + }); + + }); + + describe("when the promise is rejected", function () { + it("should not call the callback", function () { + var called = false; + + return Q.reject(exception1) + .tap(function () { + called = true; + }) + .then(function () { + expect(called).toBe(false); + }, function () { + expect(called).toBe(false); + }); + }); + }); +}); + + +describe("done", function () { + describe("when the promise is fulfilled", function () { + describe("and the callback does not throw", function () { + it("should call the callback and return nothing", function () { + var called = false; + + var promise = Q(); + + var returnValue = promise.done(function () { + called = true; + }); + + return promise.fail(function () { }).fin(function () { + expect(called).toBe(true); + expect(returnValue).toBe(undefined); + }); + }); + }); + + describe("and the callback throws", function () { + it("should rethrow that error in the next turn and return nothing", function () { + var turn = 0; + Q.nextTick(function () { + ++turn; + }); + + var returnValue = Q().done( + function () { + throw "foo"; + } + ); + + var deferred = Q.defer(); + Q.onerror = function (error) { + expect(turn).toBe(1); + expect(error).toBe("foo"); + expect(returnValue).toBe(undefined); + deferred.resolve(); + }; + Q.delay(100).then(deferred.reject); + + return deferred.promise; + }); + }); + }); + + describe("when the promise is rejected", function () { + describe("and the errback handles it", function () { + it("should call the errback and return nothing", function () { + var called = false; + + var promise = Q.reject(new Error()); + + var returnValue = promise.done( + function () { }, + function () { + called = true; + } + ); + + return promise.fail(function () { }).fin(function () { + expect(called).toBe(true); + expect(returnValue).toBe(undefined); + }); + }); + }); + + describe("and the errback throws", function () { + it("should rethrow that error in the next turn and return nothing", function () { + var turn = 0; + Q.nextTick(function () { + ++turn; + }); + + var returnValue = Q.reject("bar").done( + null, + function () { + throw "foo"; + } + ); + + var deferred = Q.defer(); + Q.onerror = function (error) { + expect(turn).toBe(1); + expect(error).toBe("foo"); + expect(returnValue).toBe(undefined); + deferred.resolve(); + }; + Q.delay(100).then(deferred.reject); + + return deferred.promise; + }); + }); + + describe("and there is no errback", function () { + it("should throw the original error in the next turn", function () { + var turn = 0; + Q.nextTick(function () { + ++turn; + }); + + var returnValue = Q.reject("bar").done(); + + var deferred = Q.defer(); + Q.onerror = function (error) { + expect(turn).toBe(1); + expect(error).toBe("bar"); + expect(returnValue).toBe(undefined); + deferred.resolve(); + }; + Q.delay(10).then(deferred.reject); + + return deferred.promise; + }); + }); + }); + + it("should attach a progress listener", function () { + var deferred = Q.defer(); + + var spy = jasmine.createSpy(); + deferred.promise.done(null, null, spy); + + deferred.notify(10); + deferred.resolve(); + + return deferred.promise.then(function () { + expect(spy).toHaveBeenCalledWith(10); + }); + }); +}); + +describe("timeout", function () { + it("should do nothing if the promise fulfills quickly", function () { + return Q.delay(10).timeout(200); + }); + + it("should do nothing if the promise rejects quickly", function () { + var goodError = new Error("haha!"); + return Q.delay(10) + .then(function () { + throw goodError; + }) + .timeout(200) + .then(undefined, function (error) { + expect(error).toBe(goodError); + }); + }); + + it("should reject with a timeout error if the promise is too slow", function () { + return Q.delay(100) + .timeout(10) + .then( + function () { + expect(true).toBe(false); + }, + function (error) { + expect(/time/i.test(error.message)).toBe(true); + } + ); + }); + + it("should pass through progress notifications", function () { + var deferred = Q.defer(); + + var progressValsSeen = []; + var promise = Q.timeout(deferred.promise, 300).then(function () { + expect(progressValsSeen).toEqual([1, 2, 3]); + }, undefined, function (progressVal) { + progressValsSeen.push(progressVal); + }); + + Q.delay(5).then(function () { deferred.notify(1); }); + Q.delay(15).then(function () { deferred.notify(2); }); + Q.delay(25).then(function () { deferred.notify(3); }); + Q.delay(35).then(function () { deferred.resolve(); }); + + return promise; + }); + + it("should reject with a custom timeout error if the promise is too slow and msg was provided", function () { + return Q.delay(100) + .timeout(10, "custom") + .then( + function () { + expect(true).toBe(false); + }, + function (error) { + expect(/custom/i.test(error.message)).toBe(true); + expect(error.code).toBe("ETIMEDOUT"); + } + ); + }); + + it("should reject with a custom timeout error if the promise is too slow and Error object was provided", function () { + var customError = new Error("custom"); + customError.isCustom = true; + return Q.delay(100) + .timeout(10, customError) + .then( + function () { + expect(true).toBe(false); + }, + function (error) { + expect(/custom/i.test(error.message)).toBe(true); + expect(error.isCustom).toBe(true); + } + ); + }); + +}); + +describe("delay", function () { + it("should delay fulfillment", function () { + var promise = Q(5).delay(50); + + setTimeout(function () { + expect(promise.isPending()).toBe(true); + }, 40); + + return promise; + }); + + it("should not delay rejection", function () { + var promise = Q.reject(5).delay(50); + + return Q.delay(20).then(function () { + expect(promise.isPending()).toBe(false); + }); + }); + + it("should treat a single argument as a time", function () { + var promise = Q.delay(50); + + setTimeout(function () { + expect(promise.isPending()).toBe(true); + }, 40); + + return promise; + }); + + it("should treat two arguments as a value + a time", function () { + var promise = Q.delay("what", 50); + + setTimeout(function () { + expect(promise.isPending()).toBe(true); + }, 40); + + return promise.then(function (value) { + expect(value).toBe("what"); + }); + }); + + it("should delay after resolution", function () { + var promise1 = Q.delay("what", 30); + var promise2 = promise1.delay(30); + + setTimeout(function () { + expect(promise1.isPending()).toBe(false); + expect(promise2.isPending()).toBe(true); + }, 40); + + return promise2.then(function (value) { + expect(value).toBe("what"); + }); + }); + + + it("should pass through progress notifications from passed promises", function () { + var deferred = Q.defer(); + + var progressValsSeen = []; + var promise = Q.delay(deferred.promise, 100).then(function () { + expect(progressValsSeen).toEqual([1, 2, 3]); + }, undefined, function (progressVal) { + progressValsSeen.push(progressVal); + }); + + Q.delay(5).then(function () { deferred.notify(1); }); + Q.delay(15).then(function () { deferred.notify(2); }); + Q.delay(25).then(function () { deferred.notify(3); }); + Q.delay(35).then(function () { deferred.resolve(); }); + + return promise; + }); +}); + +describe("thenResolve", function () { + describe("Resolving with a non-thenable value", function () { + it("returns a promise for that object once the promise is resolved", function () { + var waited = false; + return Q.delay(20) + .then(function () { + waited = true; + }) + .thenResolve("foo") + .then(function (val) { + expect(waited).toBe(true); + expect(val).toBe("foo"); + }); + }); + + describe("based off a rejected promise", function () { + it("does nothing, letting the rejection flow through", function () { + return Q.reject("boo") + .thenResolve("foo") + .then( + function () { + expect(true).toBe(false); + }, + function (reason) { + expect(reason).toBe("boo"); + } + ); + }); + }); + }); + + describe("Resolving with an promise", function () { + it("returns a promise for the result of that promise once the promise is resolved", function () { + var waited = false; + return Q.delay(20) + .then(function () { + waited = true; + }) + .thenResolve(Q("foo")) + .then(function (val) { + expect(waited).toBe(true); + expect(val).toBe("foo"); + }); + }); + }); +}); + +describe("thenReject", function () { + describe("Rejecting with a reason", function () { + it("returns a promise rejected with that object once the original promise is resolved", function () { + var waited = false; + return Q.delay(20) + .then(function () { + waited = true; + }) + .thenReject("foo") + .then( + function () { + expect(true).toBe(false); + }, + function (reason) { + expect(waited).toBe(true); + expect(reason).toBe("foo"); + } + ); + }); + + describe("based off a rejected promise", function () { + it("does nothing, letting the rejection flow through", function () { + return Q.reject("boo") + .thenResolve("foo") + .then( + function () { + expect(true).toBe(false); + }, + function (reason) { + expect(reason).toBe("boo"); + } + ); + }); + }); + }); +}); + +describe("thenables", function () { + + it("assimilates a thenable with fulfillment with resolve", function () { + return Q({ + then: function (resolved) { + resolved(10); + } + }) + .then(function (ten) { + expect(ten).toEqual(10); + }) + .then(function (undefined) { + expect(undefined).toEqual(void 0); + }); + }); + + it("assimilates a thenable with progress and fulfillment (using resolve)", function () { + var progressValueArrays = []; + return Q({ + then: function (fulfilled, rejected, progressed) { + Q.nextTick(function () { + progressed(1, 2); + progressed(3, 4, 5); + fulfilled(); + }); + } + }) + .progress(function () { + progressValueArrays.push(Array.prototype.slice.call(arguments)); + }) + .then(function () { + expect(progressValueArrays).toEqual([[1], [3]]); + }); + }); + + it("assimilates a thenable with progress and fulfillment (using when)", function () { + var progressValueArrays = []; + return Q.when({ + then: function (fulfilled, rejected, progressed) { + Q.nextTick(function () { + progressed(1, 2); + progressed(3, 4, 5); + fulfilled(); + }); + } + }) + .progress(function () { + progressValueArrays.push(Array.prototype.slice.call(arguments)); + }) + .then(function () { + expect(progressValueArrays).toEqual([[1], [3]]); + }); + }); + + it("flows fulfillment into a promise pipeline", function () { + return Q({ + then: function (resolved) { + resolved([10]); + } + }) + .get(0) + .then(function (ten) { + expect(ten).toEqual(10); + }); + }); + + it("assimilates an immediately-fulfilled thenable in allSettled", function () { + return Q.allSettled([ + {then: function (win) { + win(10); + }} + ]) + .then(function (snapshots) { + expect(snapshots).toEqual([{ state: "fulfilled", value: 10 }]); + }); + }); + + it("assimilates an eventually-fulfilled thenable in allSettled", function () { + return Q.allSettled([ + {then: function (win) { + setTimeout(function () { + win(10); + }, 100); + }} + ]) + .then(function (snapshots) { + expect(snapshots).toEqual([{ state: "fulfilled", value: 10 }]); + }); + }); + +}); + +describe("node support", function () { + + var exception = new Error("That is not your favorite color."); + + var obj = { + method: function (a, b, c, callback) { + callback(null, a + b + c); + }, + thispChecker: function (callback) { + callback(null, this === obj); + }, + errorCallbacker: function (a, b, c, callback) { + callback(exception); + }, + errorThrower: function () { + throw exception; + } + }; + + describe("nfapply", function () { + + it("fulfills with callback result", function () { + return Q.nfapply(function (a, b, c, callback) { + callback(null, a + b + c); + }, [1, 2, 3]) + .then(function (sum) { + expect(sum).toEqual(6); + }); + }); + + it("rejects with callback error", function () { + var exception = new Error("That is not your favorite color."); + return Q.nfapply(function (a, b, c, callback) { + callback(exception); + }, [1, 2, 3]) + .then(function () { + expect(true).toBe(false); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + }); + + describe("nfcall", function () { + it("fulfills with callback result", function () { + return Q.nfcall(function (a, b, c, callback) { + callback(null, a + b + c); + }, 1, 2, 3) + .then(function (sum) { + expect(sum).toEqual(6); + }); + }); + + it("rejects with callback error", function () { + var exception = new Error("That is not your favorite color."); + return Q.nfcall(function (a, b, c, callback) { + callback(exception); + }, 1, 2, 3) + .then(function () { + expect(true).toBe(false); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + }); + + describe("nfbind", function () { + + it("mixes partial application with complete application", function () { + return Q.nfbind(function (a, b, c, d, callback) { + callback(null, a + b + c + d); + }, 1, 2).call({}, 3, 4) + .then(function (ten) { + expect(ten).toBe(10); + }); + }); + + }); + + describe("nbind", function () { + + it("binds this, and mixes partial application with complete application", function () { + return Q.nbind(function (a, b, c, callback) { + callback(null, this + a + b + c); + }, 1, 2).call(3 /* effectively ignored as fn bound to 1 */, 4, 5) + .then(function (twelve) { + expect(twelve).toBe(12); + }); + }); + + it("second arg binds this", function() { + var expectedThis = { test: null }; + + return Q.nbind(function(callback) { + callback(null, this); + }, expectedThis).call() + .then(function(actualThis) { + expect(actualThis).toEqual(expectedThis); + }); + }); + + }); + + describe("npost", function () { + + it("fulfills with callback result", function () { + return Q.npost(obj, "method", [1, 2, 3]) + .then(function (sum) { + expect(sum).toEqual(6); + }); + }); + + it("gets the correct thisp", function () { + return Q.npost(obj, "thispChecker", []) + .then(function (result) { + expect(result).toBe(true); + }); + }); + + it("rejects with callback error", function () { + return Q.npost(obj, "errorCallbacker", [1, 2, 3]) + .then(function () { + expect("blue").toBe("no, yellow!"); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + it("rejects with thrown error", function () { + return Q.npost(obj, "errorThrower", [1, 2, 3]) + .then(function () { + expect(true).toBe(false); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + it("works on promises for objects with Node methods", function () { + return Q(obj) + .npost("method", [1, 2, 3]) + .then(function (sum) { + expect(sum).toEqual(6); + }); + }); + + }); + + describe("nsend", function () { + + it("fulfills with callback result", function () { + return Q.nsend(obj, "method", 1, 2, 3) + .then(function (sum) { + expect(sum).toEqual(6); + }); + }); + + it("gets the correct thisp", function () { + return Q.nsend(obj, "thispChecker") + .then(function (result) { + expect(result).toBe(true); + }); + }); + + it("rejects with callback error", function () { + return Q.nsend(obj, "errorCallbacker", 1, 2, 3) + .then(function () { + expect("blue").toBe("no, yellow!"); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + it("rejects with thrown error", function () { + return Q.nsend(obj, "errorThrower", 1, 2, 3) + .then(function () { + expect(true).toBe(false); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + it("works on promises for objects with Node methods", function () { + return Q(obj) + .nsend("method", 1, 2, 3) + .then(function (sum) { + expect(sum).toEqual(6); + }); + }); + + }); + + describe("deferred.makeNodeResolver", function () { + + it("fulfills a promise with a single callback argument", function () { + var deferred = Q.defer(); + var callback = deferred.makeNodeResolver(); + callback(null, 10); + return deferred.promise.then(function (value) { + expect(value).toBe(10); + }); + }); + + it("fulfills a promise with multiple callback arguments", function () { + var deferred = Q.defer(); + var callback = deferred.makeNodeResolver(); + callback(null, 10, 20); + return deferred.promise.then(function (value) { + expect(value).toEqual([10, 20]); + }); + }); + + it("rejects a promise", function () { + var deferred = Q.defer(); + var callback = deferred.makeNodeResolver(); + var exception = new Error("Holy Exception of Anitoch"); + callback(exception); + return deferred.promise.then(function () { + expect(5).toBe(3); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + + }); + + describe("nodeify", function () { + + it("calls back with a resolution", function () { + var spy = jasmine.createSpy(); + Q(10).nodeify(spy); + waitsFor(function () { + return spy.argsForCall.length; + }); + runs(function () { + expect(spy.argsForCall).toEqual([[null, 10]]); + }); + }); + + it("calls back with an error", function () { + var spy = jasmine.createSpy(); + Q.reject(10).nodeify(spy); + waitsFor(function () { + return spy.argsForCall.length; + }); + runs(function () { + expect(spy.argsForCall).toEqual([[10]]); + }); + }); + + it("forwards a promise", function () { + return Q(10).nodeify().then(function (ten) { + expect(ten).toBe(10); + }); + }); + + }); + +}); + +describe("isPromise", function () { + it("returns true if passed a promise", function () { + expect(Q.isPromise(Q(10))).toBe(true); + }); + + it("returns false if not passed a promise", function () { + expect(Q.isPromise(undefined)).toBe(false); + expect(Q.isPromise(null)).toBe(false); + expect(Q.isPromise(10)).toBe(false); + expect(Q.isPromise("str")).toBe(false); + expect(Q.isPromise("")).toBe(false); + expect(Q.isPromise(true)).toBe(false); + expect(Q.isPromise(false)).toBe(false); + expect(Q.isPromise({})).toBe(false); + expect(Q.isPromise({ + then: function () {} + })).toBe(false); + expect(Q.isPromise(function () {})).toBe(false); + }); +}); + +describe("isPromiseAlike", function () { + it("returns true if passed a promise like object", function () { + expect(Q.isPromiseAlike(Q(10))).toBe(true); + expect(Q.isPromiseAlike({ + then: function () {} + })).toBe(true); + }); + + it("returns false if not passed a promise like object", function () { + expect(Q.isPromiseAlike(undefined)).toBe(false); + expect(Q.isPromiseAlike(null)).toBe(false); + expect(Q.isPromiseAlike(10)).toBe(false); + expect(Q.isPromiseAlike("str")).toBe(false); + expect(Q.isPromiseAlike("")).toBe(false); + expect(Q.isPromiseAlike(true)).toBe(false); + expect(Q.isPromiseAlike(false)).toBe(false); + expect(Q.isPromiseAlike({})).toBe(false); + expect(Q.isPromiseAlike(function () {})).toBe(false); + }); +}); + +if (typeof require === "function") { + var domain; + try { + domain = require("domain"); + } catch (e) { } + + if (domain) { + var EventEmitter = require("events").EventEmitter; + + describe("node domain support", function () { + var d; + + beforeEach(function () { + d = domain.create(); + }); + afterEach(function() { + d.dispose(); + }); + + it("should work for non-promise async inside a promise handler", + function (done) { + var error = new Error("should be caught by the domain"); + + d.run(function () { + Q().then(function () { + setTimeout(function () { + throw error; + }, 10); + }); + }); + + var errorTimeout = setTimeout(function () { + done(new Error("Wasn't caught")); + }, 100); + + d.on("error", function (theError) { + expect(theError).toBe(error); + clearTimeout(errorTimeout); + done(); + }); + }); + + it("should transfer errors from `done` into the domain", + function (done) { + var error = new Error("should be caught by the domain"); + + d.run(function () { + Q.reject(error).done(); + }); + + var errorTimeout = setTimeout(function () { + done(new Error("Wasn't caught")); + }, 100); + + d.on("error", function (theError) { + expect(theError).toBe(error); + clearTimeout(errorTimeout); + done(); + }); + }); + + it("should take care of re-used event emitters", function (done) { + // See discussion in https://github.com/kriskowal/q/issues/120 + var error = new Error("should be caught by the domain"); + + var e = new EventEmitter(); + + d.run(function () { + callAsync().done(); + }); + setTimeout(function () { + e.emit("beep"); + }, 100); + + var errorTimeout = setTimeout(function () { + done(new Error("Wasn't caught")); + }, 500); + + d.on("error", function (theError) { + expect(theError).toBe(error); + clearTimeout(errorTimeout); + done(); + }); + + function callAsync() { + var def = Q.defer(); + e.once("beep", function () { + def.reject(error); + }); + return def.promise; + } + }); + }); + } +} + +describe("decorator functions", function () { + describe("promised", function () { + var exception = new Error("That is not the meaning of life."); + it("resolves promised arguments", function () { + var sum = Q.promised(function add(a, b) { + return a + b; + }); + return sum(Q(4), Q(5)).then(function (sum) { + expect(sum).toEqual(9); + }); + }); + it("resolves promised `this`", function () { + var inc = Q.promised(function inc(a) { + return this + a; + }); + return inc.call(Q(4), Q(5)).then(function (sum) { + expect(sum).toEqual(9); + }); + }); + it("is rejected if an argument is rejected", function () { + var sum = Q.promised(function add(a, b) { + return a + b; + }); + return sum(Q.reject(exception), Q(4)).then(function () { + expect(4).toEqual(42); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + it("is rejected if `this` is rejected", function () { + var inc = Q.promised(function inc(a) { + return this + a; + }); + return inc.call(Q.reject(exception), Q(4)).then(function () { + expect(4).toEqual(42); + }, function (_exception) { + expect(_exception).toBe(exception); + }); + }); + }); +}); + +describe("stack trace formatting", function () { + it("doesn't mangle a stack trace that gets handled twice", function () { + var d1 = Q.defer(); + var d2 = Q.defer(); + var captured = []; + d1.promise.done(); + d2.promise.done(); + + Q.onerror = function (err) { + captured.push(err.stack); + }; + + var error = new Error("boom!"); + d1.reject(error); + d2.reject(error); + + return Q.all([d1.promise.fail(function () {}), d2.promise.fail(function () { })]) + .then(function () { + expect(captured[0]).toEqual(captured[1]); + }); + }); +}); + +describe("possible regressions", function () { + + describe("gh-9", function () { + it("treats falsy values as resolved values without error", function () { + expect(Q.isPending(null)).toEqual(false); + expect(Q.isPending(void 0)).toEqual(false); + expect(Q.isPending(false)).toEqual(false); + expect(Q.isPending()).toEqual(false); + }); + }); + + describe("gh-22", function () { + it("ensures that the array prototype is intact", function () { + var keys = []; + for (var key in []) { + keys.push(key); + } + expect(keys.length).toBe(0); + }); + }); + + describe("gh-73", function () { + it("does not choke on non-error rejection reasons", function () { + Q.reject(REASON).done(); + + var deferred = Q.defer(); + + Q.onerror = function (error) { + expect(error).toBe(REASON); + deferred.resolve(); + }; + Q.delay(10).then(deferred.reject); + + return deferred.promise; + }); + }); + + describe("gh-90", function () { + it("does not choke on rejection reasons with an undefined `stack`", function () { + var error = new RangeError(REASON); + error.stack = undefined; + Q.reject(error).done(); + + var deferred = Q.defer(); + + Q.onerror = function (theError) { + expect(theError).toBe(error); + deferred.resolve(); + }; + Q.delay(10).then(deferred.reject); + + return deferred.promise; + }); + }); + + describe("gh-75", function () { + it("does not double-resolve misbehaved promises", function () { + var badPromise = Q.makePromise({ + post: function () { return "hello"; } + }); + + var resolutions = 0; + function onResolution() { + ++resolutions; + } + + return Q.when(badPromise, onResolution, onResolution).then(function () { + expect(resolutions).toBe(1); + }); + }); + }); + +}); + +describe("unhandled rejection reporting", function () { + beforeEach(function () { + Q.resetUnhandledRejections(); + }); + + it("doesn't report a resolve, then reject (gh-252)", function () { + var deferred = Q.defer(); + deferred.resolve(); + deferred.reject(); + + expect(Q.getUnhandledReasons().length).toEqual(0); + }); + + it("doesn't report when you chain off a rejection", function () { + return Q.reject("this will be handled").get("property").fail(function () { + // now it should be handled. + }).fin(function() { + expect(Q.getUnhandledReasons().length).toEqual(0); + }); + }); + + it("reports the most basic case", function () { + Q.reject("a reason"); + + expect(Q.getUnhandledReasons()).toEqual(["(no stack) a reason"]); + }); + + it("reports a stack trace", function () { + var error = new Error("a reason"); + Q.reject(error); + + expect(Q.getUnhandledReasons()).toEqual([error.stack]); + }); + + it("doesn't let you mutate the internal array", function () { + Q.reject("a reason"); + + Q.getUnhandledReasons().length = 0; + expect(Q.getUnhandledReasons()).toEqual(["(no stack) a reason"]); + }); + + it("resets after calling `Q.resetUnhandledRejections`", function () { + Q.reject("a reason"); + + Q.resetUnhandledRejections(); + expect(Q.getUnhandledReasons()).toEqual([]); + }); + + it("stops tracking after calling `Q.stopUnhandledRejectionTracking`", function () { + Q.reject("a reason"); + + Q.stopUnhandledRejectionTracking(); + + Q.reject("another reason"); + + expect(Q.getUnhandledReasons()).toEqual([]); + }); +}); diff --git a/vendors/Q/spec/queue-spec.js b/vendors/Q/spec/queue-spec.js new file mode 100644 index 000000000..4cec14e23 --- /dev/null +++ b/vendors/Q/spec/queue-spec.js @@ -0,0 +1,180 @@ + +var Q = require("../q"); +var Queue = require("../queue"); + +global.Q = Q; +require("./lib/jasmine-promise"); + +describe("queue", function () { + + it("should enqueue then dequeue", function () { + var queue = Queue(); + queue.put(1); + return queue.get().then(function (value) { + expect(value).toBe(1); + }); + }); + + it("should dequeue then enqueue", function () { + var queue = Queue(); + var promise = queue.get().then(function (value) { + expect(value).toBe(1); + }); + queue.put(1); + return promise; + }); + + it("should stream", function () { + var queue = Queue(); + + Q.try(function () { + return Q.delay(20).then(function () { + queue.put(1); + }) + }) + .then(function () { + return Q.delay(20).then(function () { + queue.put(2); + }) + }) + .then(function () { + return Q.delay(20).then(function () { + queue.put(3); + }) + }) + .done(); + + return Q.try(function () { + return queue.get() + .then(function (value) { + expect(value).toBe(1); + }); + }) + .then(function () { + return queue.get() + .then(function (value) { + expect(value).toBe(2); + }); + }) + .then(function () { + return queue.get() + .then(function (value) { + expect(value).toBe(3); + }); + }) + + }); + + it("should be order agnostic", function () { + var queue = Queue(); + + Q.try(function () { + return Q.delay(20).then(function () { + queue.put(1); + }) + }) + .then(function () { + return Q.delay(20).then(function () { + queue.put(2); + }) + }) + .then(function () { + return Q.delay(20).then(function () { + queue.put(3); + }) + }) + .done(); + + return Q.all([ + queue.get() + .then(function (value) { + expect(value).toBe(1); + }), + queue.get() + .then(function (value) { + expect(value).toBe(2); + }), + queue.get() + .then(function (value) { + expect(value).toBe(3); + }) + ]); + }); + + it("should close", function () { + + var queue = Queue(); + + Q.try(function () { + return Q.delay(20).then(function () { + queue.put(1); + }) + }) + .then(function () { + return Q.delay(20).then(function () { + queue.put(2); + }) + }) + .then(function () { + queue.close(); + }) + .done(); + + return Q.try(function () { + return queue.get() + .then(function (value) { + expect(value).toBe(1); + }); + }) + .then(function () { + return queue.get() + .then(function (value) { + expect(value).toBe(2); + }); + }) + .then(function () { + return queue.get() + .then(function (value) { + expect(false).toBe(true); // should not get here + }); + }) + .catch(function (error) { + expect(error.message).toBe("Can't get value from closed queue"); + return queue.get(); + }) + .catch(function (error) { + expect(error.message).toBe("Can't get value from closed queue"); + }) + .then(function () { + return queue.closed; + }) + .then(function (error) { + expect(error.message).toBe("Can't get value from closed queue"); + }) + }); + + it("should close with alternate error", function () { + + var queue = Queue(); + queue.close(new Error("Alternate reason")); + + return Q.try(function () { + return queue.get(); + }) + .catch(function (error) { + expect(error.message).toBe("Alternate reason"); + return queue.get(); + }) + .catch(function (error) { + expect(error.message).toBe("Alternate reason"); + }) + .then(function () { + return queue.closed; + }) + .then(function (error) { + expect(error.message).toBe("Alternate reason"); + }) + }); + +}); + diff --git a/vendors/bootstrap/js/bootstrap.min.js b/vendors/bootstrap/js/bootstrap.min.js index 7f9e2cfac..59f43909a 100644 --- a/vendors/bootstrap/js/bootstrap.min.js +++ b/vendors/bootstrap/js/bootstrap.min.js @@ -6,4 +6,4 @@ !function(e){"use strict";e(function(){e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()};var r=e.fn.alert;e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e.fn.alert.noConflict=function(){return e.fn.alert=r,this},e(document).on("click.alert.data-api",t,n.prototype.close)}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.closest('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")};var n=e.fn.button;e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e.fn.button.noConflict=function(){return e.fn.button=n,this},e(document).on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.$indicators=this.$element.find(".carousel-indicators"),this.options=n,this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},getActiveIndex:function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},to:function(t){var n=this.getActiveIndex(),r=this;if(t>this.$items.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){r.to(t)}):n==t?this.pause().cycle():this.slide(t>n?"next":"prev",e(this.$items[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle(!0)),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f;this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u](),f=e.Event("slide",{relatedTarget:i[0],direction:o});if(i.hasClass("active"))return;this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid",function(){var t=e(a.$indicators.children()[a.getActiveIndex()]);t&&t.addClass("active")}));if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}};var n=e.fn.carousel;e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.pause().cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e.fn.carousel.noConflict=function(){return e.fn.carousel=n,this},e(document).on("click.carousel.data-api","[data-slide], [data-slide-to]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=e.extend({},i.data(),n.data()),o;i.carousel(s),(o=n.attr("data-slide-to"))&&i.data("carousel").pause().to(o).cycle(),t.preventDefault()})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning||this.$element.hasClass("in"))return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning||!this.$element.hasClass("in"))return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var n=e.fn.collapse;e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=e.extend({},e.fn.collapse.defaults,r.data(),typeof n=="object"&&n);i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e.fn.collapse.noConflict=function(){return e.fn.collapse=n,this},e(document).on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})}(window.jQuery),!function(e){"use strict";function r(){e(".dropdown-backdrop").remove(),e(t).each(function(){i(e(this)).removeClass("open")})}function i(t){var n=t.attr("data-target"),r;n||(n=t.attr("href"),n=n&&/#/.test(n)&&n.replace(/.*(?=#[^\s]*$)/,"")),r=n&&e(n);if(!r||!r.length)r=t.parent();return r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||("ontouchstart"in document.documentElement&&e('",CKEDITOR.document); +a.insertAfter(c);c.hide();c.$.form&&b._attachToForm()}else b.setData(a.getHtml(),null,true);b.on("loaded",function(){b.fire("uiReady");b.editable(a);b.container=a;b.setData(b.getData(1));b.resetDirty();b.fire("contentDom");b.mode="wysiwyg";b.fire("mode");b.status="ready";b.fireOnce("instanceReady");CKEDITOR.fire("instanceReady",null,b)},null,null,1E4);b.on("destroy",function(){if(c){b.container.clearCustomData();b.container.remove();c.show()}b.element.clearCustomData();delete b.element});return b}; +CKEDITOR.inlineAll=function(){var a,f,b;for(b in CKEDITOR.dtd.$editable)for(var c=CKEDITOR.document.getElementsByTag(b),e=0,d=c.count();e"+(a.title?'{voiceLabel}':"")+'<{outerEl} class="cke_inner cke_reset" role="presentation">{topHtml}<{outerEl} id="{contentId}" class="cke_contents cke_reset" role="presentation">{bottomHtml}'), +b=CKEDITOR.dom.element.createFromHtml(g.output({id:a.id,name:b,langDir:a.lang.dir,langCode:a.langCode,voiceLabel:a.title,topHtml:k?''+k+"":"",contentId:a.ui.spaceId("contents"),bottomHtml:j?''+j+"":"",outerEl:CKEDITOR.env.ie?"span":"div"}));if(f==CKEDITOR.ELEMENT_MODE_REPLACE){d.hide();b.insertAfter(d)}else d.append(b); +a.container=b;k&&a.ui.space("top").unselectable();j&&a.ui.space("bottom").unselectable();d=a.config.width;f=a.config.height;d&&b.setStyle("width",CKEDITOR.tools.cssLength(d));f&&a.ui.space("contents").setStyle("height",CKEDITOR.tools.cssLength(f));b.disableContextMenu();CKEDITOR.env.webkit&&b.on("focus",function(){a.focus()});a.fireOnce("uiReady")}CKEDITOR.replace=function(b,e){return a(b,e,null,CKEDITOR.ELEMENT_MODE_REPLACE)};CKEDITOR.appendTo=function(b,e,d){return a(b,e,d,CKEDITOR.ELEMENT_MODE_APPENDTO)}; +CKEDITOR.replaceAll=function(){for(var a=document.getElementsByTagName("textarea"),b=0;b",r="",a=f+a.replace(e,function(){return r+f})+r}a=a.replace(/\n/g,"
");b||(a=a.replace(RegExp("
(?=)"),function(a){return d.repeat(a,2)}));a=a.replace(/^ | $/g," ");a=a.replace(/(>|\s) /g,function(a,b){return b+" "}).replace(/ (?=<)/g, +" ");q(this,"text",a)},insertElement:function(a,b){b?this.insertElementIntoRange(a,b):this.insertElementIntoSelection(a)},insertElementIntoRange:function(a,b){var c=this.editor,d=c.config.enterMode,e=a.getName(),f=CKEDITOR.dtd.$block[e];if(b.checkReadOnly())return false;b.deleteContents(1);b.startContainer.type==CKEDITOR.NODE_ELEMENT&&b.startContainer.is({tr:1,table:1,tbody:1,thead:1,tfoot:1})&&t(b);var r,n;if(f)for(;(r=b.getCommonAncestor(0,1))&&(n=CKEDITOR.dtd[r.getName()])&&(!n||!n[e]);)if(r.getName()in +CKEDITOR.dtd.span)b.splitElement(r);else if(b.checkStartOfBlock()&&b.checkEndOfBlock()){b.setStartBefore(r);b.collapse(true);r.remove()}else b.splitBlock(d==CKEDITOR.ENTER_DIV?"div":"p",c.editable());b.insertNode(a);return true},insertElementIntoSelection:function(a){k(this);var b=this.editor,d=b.activeEnterMode,b=b.getSelection(),e=b.getRanges()[0],f=a.getName(),f=CKEDITOR.dtd.$block[f];if(this.insertElementIntoRange(a,e)){e.moveToPosition(a,CKEDITOR.POSITION_AFTER_END);if(f)if((f=a.getNext(function(a){return c(a)&& +!m(a)}))&&f.type==CKEDITOR.NODE_ELEMENT&&f.is(CKEDITOR.dtd.$block))f.getDtd()["#"]?e.moveToElementEditStart(f):e.moveToElementEditEnd(a);else if(!f&&d!=CKEDITOR.ENTER_BR){f=e.fixBlock(true,d==CKEDITOR.ENTER_DIV?"div":"p");e.moveToElementEditStart(f)}}b.selectRanges([e]);j(this)},setData:function(a,b){b||(a=this.editor.dataProcessor.toHtml(a));this.setHtml(a);this.fixInitialSelection();if(this.status=="unloaded")this.status="ready";this.editor.fire("dataReady")},getData:function(a){var b=this.getHtml(); +a||(b=this.editor.dataProcessor.toDataFormat(b));return b},setReadOnly:function(a){this.setAttribute("contenteditable",!a)},detach:function(){this.removeClass("cke_editable");this.status="detached";var a=this.editor;this._.detach();delete a.document;delete a.window},isInline:function(){return this.getDocument().equals(CKEDITOR.document)},fixInitialSelection:function(){function a(){var b=c.getDocument().$,d=b.getSelection(),e;if(d.anchorNode&&d.anchorNode==c.$)e=true;else if(CKEDITOR.env.webkit){var f= +c.getDocument().getActive();f&&(f.equals(c)&&!d.anchorNode)&&(e=true)}if(e){e=new CKEDITOR.dom.range(c);e.moveToElementEditStart(c);b=b.createRange();b.setStart(e.startContainer.$,e.startOffset);b.collapse(true);d.removeAllRanges();d.addRange(b)}}function b(){var a=c.getDocument().$,d=a.selection,e=c.getDocument().getActive();if(d.type=="None"&&e.equals(c)){d=new CKEDITOR.dom.range(c);a=a.body.createTextRange();d.moveToElementEditStart(c);d=d.startContainer;d.type!=CKEDITOR.NODE_ELEMENT&&(d=d.getParent()); +a.moveToElementText(d.$);a.collapse(true);a.select()}}var c=this;if(CKEDITOR.env.ie&&(CKEDITOR.env.version<9||CKEDITOR.env.quirks)){if(this.hasFocus){this.focus();b()}}else if(this.hasFocus){this.focus();a()}else this.once("focus",function(){a()},null,null,-999)},setup:function(){var a=this.editor;this.attachListener(a,"beforeGetData",function(){var b=this.getData();this.is("textarea")||a.config.ignoreEmptyParagraph!==false&&(b=b.replace(y,function(a,b){return b}));a.setData(b,null,1)},this);this.attachListener(a, +"getSnapshot",function(a){a.data=this.getData(1)},this);this.attachListener(a,"afterSetData",function(){this.setData(a.getData(1))},this);this.attachListener(a,"loadSnapshot",function(a){this.setData(a.data,1)},this);this.attachListener(a,"beforeFocus",function(){var b=a.getSelection();(b=b&&b.getNative())&&b.type=="Control"||this.focus()},this);this.attachListener(a,"insertHtml",function(a){this.insertHtml(a.data.dataValue,a.data.mode)},this);this.attachListener(a,"insertElement",function(a){this.insertElement(a.data)}, +this);this.attachListener(a,"insertText",function(a){this.insertText(a.data)},this);this.setReadOnly(a.readOnly);this.attachClass("cke_editable");this.attachClass(a.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"cke_editable_inline":a.elementMode==CKEDITOR.ELEMENT_MODE_REPLACE||a.elementMode==CKEDITOR.ELEMENT_MODE_APPENDTO?"cke_editable_themed":"");this.attachClass("cke_contents_"+a.config.contentsLangDirection);a.keystrokeHandler.blockedKeystrokes[8]=+a.readOnly;a.keystrokeHandler.attach(this);this.on("blur", +function(){this.hasFocus=false},null,null,-1);this.on("focus",function(){this.hasFocus=true},null,null,-1);a.focusManager.add(this);if(this.equals(CKEDITOR.document.getActive())){this.hasFocus=true;a.once("contentDom",function(){a.focusManager.focus(this)},this)}this.isInline()&&this.changeAttr("tabindex",a.tabIndex);if(!this.is("textarea")){a.document=this.getDocument();a.window=this.getWindow();var e=a.document;this.changeAttr("spellcheck",!a.config.disableNativeSpellChecker);var f=a.config.contentsLangDirection; +this.getDirection(1)!=f&&this.changeAttr("dir",f);var h=CKEDITOR.getCss();if(h){f=e.getHead();if(!f.getCustomData("stylesheet")){h=e.appendStyleText(h);h=new CKEDITOR.dom.element(h.ownerNode||h.owningElement);f.setCustomData("stylesheet",h);h.data("cke-temp",1)}}f=e.getCustomData("stylesheet_ref")||0;e.setCustomData("stylesheet_ref",f+1);this.setCustomData("cke_includeReadonly",!a.config.disableReadonlyStyling);this.attachListener(this,"click",function(a){var a=a.data,b=(new CKEDITOR.dom.elementPath(a.getTarget(), +this)).contains("a");b&&(a.$.button!=2&&b.isReadOnly())&&a.preventDefault()});var l={8:1,46:1};this.attachListener(a,"key",function(b){if(a.readOnly)return true;var c=b.data.domEvent.getKey(),e;if(c in l){var b=a.getSelection(),f,h=b.getRanges()[0],g=h.startPath(),o,m,j,c=c==8;if(CKEDITOR.env.ie&&CKEDITOR.env.version<11&&(f=b.getSelectedElement())||(f=d(b))){a.fire("saveSnapshot");h.moveToPosition(f,CKEDITOR.POSITION_BEFORE_START);f.remove();h.select();a.fire("saveSnapshot");e=1}else if(h.collapsed)if((o= +g.block)&&(j=o[c?"getPrevious":"getNext"](s))&&j.type==CKEDITOR.NODE_ELEMENT&&j.is("table")&&h[c?"checkStartOfBlock":"checkEndOfBlock"]()){a.fire("saveSnapshot");h[c?"checkEndOfBlock":"checkStartOfBlock"]()&&o.remove();h["moveToElementEdit"+(c?"End":"Start")](j);h.select();a.fire("saveSnapshot");e=1}else if(g.blockLimit&&g.blockLimit.is("td")&&(m=g.blockLimit.getAscendant("table"))&&h.checkBoundaryOfElement(m,c?CKEDITOR.START:CKEDITOR.END)&&(j=m[c?"getPrevious":"getNext"](s))){a.fire("saveSnapshot"); +h["moveToElementEdit"+(c?"End":"Start")](j);h.checkStartOfBlock()&&h.checkEndOfBlock()?j.remove():h.select();a.fire("saveSnapshot");e=1}else if((m=g.contains(["td","th","caption"]))&&h.checkBoundaryOfElement(m,c?CKEDITOR.START:CKEDITOR.END))e=1}return!e});a.blockless&&(CKEDITOR.env.ie&&CKEDITOR.env.needsBrFiller)&&this.attachListener(this,"keyup",function(b){if(b.data.getKeystroke()in l&&!this.getFirst(c)){this.appendBogus();b=a.createRange();b.moveToPosition(this,CKEDITOR.POSITION_AFTER_START);b.select()}}); +this.attachListener(this,"dblclick",function(b){if(a.readOnly)return false;b={element:b.data.getTarget()};a.fire("doubleclick",b)});CKEDITOR.env.ie&&this.attachListener(this,"click",b);CKEDITOR.env.ie||this.attachListener(this,"mousedown",function(b){var c=b.data.getTarget();if(c.is("img","hr","input","textarea","select")&&!c.isReadOnly()){a.getSelection().selectElement(c);c.is("input","textarea","select")&&b.data.preventDefault()}});CKEDITOR.env.gecko&&this.attachListener(this,"mouseup",function(b){if(b.data.$.button== +2){b=b.data.getTarget();if(!b.getOuterHtml().replace(y,"")){var c=a.createRange();c.moveToElementEditStart(b);c.select(true)}}});if(CKEDITOR.env.webkit){this.attachListener(this,"click",function(a){a.data.getTarget().is("input","select")&&a.data.preventDefault()});this.attachListener(this,"mouseup",function(a){a.data.getTarget().is("input","textarea")&&a.data.preventDefault()})}CKEDITOR.env.webkit&&this.attachListener(a,"key",function(b){b=b.data.domEvent.getKey();if(b in l){var c=b==8,d=a.getSelection().getRanges()[0], +b=d.startPath();if(d.collapsed){var e;a:{var f=b.block;if(f)if(d[c?"checkStartOfBlock":"checkEndOfBlock"]())if(!d.moveToClosestEditablePosition(f,!c)||!d.collapsed)e=false;else{if(d.startContainer.type==CKEDITOR.NODE_ELEMENT){var h=d.startContainer.getChild(d.startOffset-(c?1:0));if(h&&h.type==CKEDITOR.NODE_ELEMENT&&h.is("hr")){a.fire("saveSnapshot");h.remove();e=true;break a}}if((d=d.startPath().block)&&(!d||!d.contains(f))){a.fire("saveSnapshot");var j;(j=(c?d:f).getBogus())&&j.remove();e=a.getSelection(); +j=e.createBookmarks();(c?f:d).moveChildren(c?d:f,false);b.lastElement.mergeSiblings();g(f,d,!c);e.selectBookmarks(j);e=true}}else e=false;else e=false}if(!e)return}else{c=d;e=b.block;j=c.endPath().block;if(!e||!j||e.equals(j))b=false;else{a.fire("saveSnapshot");(f=e.getBogus())&&f.remove();c.deleteContents();if(j.getParent()){j.moveChildren(e,false);b.lastElement.mergeSiblings();g(e,j,true)}c=a.getSelection().getRanges()[0];c.collapse(1);c.select();b=true}if(!b)return}a.getSelection().scrollIntoView(); +a.fire("saveSnapshot");return false}},this,null,100)}}},_:{detach:function(){this.editor.setData(this.editor.getData(),0,1);this.clearListeners();this.restoreAttrs();var a;if(a=this.removeCustomData("classes"))for(;a.length;)this.removeClass(a.pop());if(!this.is("textarea")){a=this.getDocument();var b=a.getHead();if(b.getCustomData("stylesheet")){var c=a.getCustomData("stylesheet_ref");if(--c)a.setCustomData("stylesheet_ref",c);else{a.removeCustomData("stylesheet_ref");b.removeCustomData("stylesheet").remove()}}}this.editor.fire("contentDomUnload"); +delete this.editor}}});CKEDITOR.editor.prototype.editable=function(a){var b=this._.editable;if(b&&a)return 0;if(arguments.length)b=this._.editable=a?a instanceof CKEDITOR.editable?a:new CKEDITOR.editable(this,a):(b&&b.detach(),null);return b};var m=CKEDITOR.dom.walker.bogus(),y=/(^|]*>)\s*<(p|div|address|h\d|center|pre)[^>]*>\s*(?:]*>| |\u00A0| )?\s*(:?<\/\2>)?\s*(?=$|<\/body>)/gi,s=CKEDITOR.dom.walker.whitespaces(true),w=CKEDITOR.dom.walker.bookmark(false,true);CKEDITOR.on("instanceLoaded", +function(b){var c=b.editor;c.on("insertElement",function(a){a=a.data;if(a.type==CKEDITOR.NODE_ELEMENT&&(a.is("input")||a.is("textarea"))){a.getAttribute("contentEditable")!="false"&&a.data("cke-editable",a.hasAttribute("contenteditable")?"true":"1");a.setAttribute("contentEditable",false)}});c.on("selectionChange",function(b){if(!c.readOnly){var d=c.getSelection();if(d&&!d.isLocked){d=c.checkDirty();c.fire("lockSnapshot");a(b);c.fire("unlockSnapshot");!d&&c.resetDirty()}}})});CKEDITOR.on("instanceCreated", +function(a){var b=a.editor;b.on("mode",function(){var a=b.editable();if(a&&a.isInline()){var c=b.title;a.changeAttr("role","textbox");a.changeAttr("aria-label",c);c&&a.changeAttr("title",c);var d=b.fire("ariaEditorHelpLabel",{}).label;if(d)if(c=this.ui.space(this.elementMode==CKEDITOR.ELEMENT_MODE_INLINE?"top":"contents")){var e=CKEDITOR.tools.getNextId(),d=CKEDITOR.dom.element.createFromHtml(''+d+"");c.append(d);a.changeAttr("aria-describedby",e)}}})}); +CKEDITOR.addCss(".cke_editable{cursor:text}.cke_editable img,.cke_editable input,.cke_editable textarea{cursor:default}");var q=function(){function a(b){return b.type==CKEDITOR.NODE_ELEMENT}function b(c,d){var e,f,l,p,h=[],g=d.range.startContainer;e=d.range.startPath();for(var g=r[g.getName()],n=0,j=c.getChildren(),m=j.count(),o=-1,C=-1,k=0,q=e.contains(r.$list);n-1)h[o].firstNotAllowed=1;if(C>-1)h[C].lastNotAllowed=1;return h}function d(b,c){var e=[],f=b.getChildren(),l=f.count(),p,h=0,g=r[c],n=!b.is(r.$inline)||b.is("br");for(n&&e.push(" ");h ",q.document);q.insertNode(B);q.setStartAfter(B)}x=new CKEDITOR.dom.elementPath(q.startContainer); +n.endPath=E=new CKEDITOR.dom.elementPath(q.endContainer);if(!q.collapsed){var z=E.block||E.blockLimit,w=q.getCommonAncestor();z&&(!z.equals(w)&&!z.contains(w)&&q.checkEndOfBlock())&&n.zombies.push(z);q.deleteContents()}for(;(s=a(q.startContainer)&&q.startContainer.getChild(q.startOffset-1))&&a(s)&&s.isBlockBoundary()&&x.contains(s);)q.moveToPosition(s,CKEDITOR.POSITION_BEFORE_END);f(q,n.blockLimit,x,E);if(B){q.setEndBefore(B);q.collapse();B.remove()}B=q.startPath();if(z=B.contains(e,false,1)){q.splitElement(z); +n.inlineStylesRoot=z;n.inlineStylesPeak=B.lastElement}B=q.createBookmark();(z=B.startNode.getPrevious(c))&&a(z)&&e(z)&&G.push(z);(z=B.startNode.getNext(c))&&a(z)&&e(z)&&G.push(z);for(z=B.startNode;(z=z.getParent())&&e(z);)G.push(z);q.moveToBookmark(B);if(B=k){B=n.range;if(n.type=="text"&&n.inlineStylesRoot){s=n.inlineStylesPeak;q=s.getDocument().createText("{cke-peak}");for(G=n.inlineStylesRoot.getParent();!s.equals(G);){q=q.appendTo(s.clone());s=s.getParent()}k=q.getOuterHtml().split("{cke-peak}").join(k)}s= +n.blockLimit.getName();if(/^\s+|\s+$/.test(k)&&"span"in CKEDITOR.dtd[s])var y=' ',k=y+k+y;k=n.editor.dataProcessor.toHtml(k,{context:null,fixForBody:false,dontFilter:n.dontFilter,filter:n.editor.activeFilter,enterMode:n.editor.activeEnterMode});s=B.document.createElement("body");s.setHtml(k);if(y){s.getFirst().remove();s.getLast().remove()}if((y=B.startPath().block)&&!(y.getChildCount()==1&&y.getBogus()))a:{var t;if(s.getChildCount()==1&&a(t=s.getFirst())&&t.is(m)){y= +t.getElementsByTag("*");B=0;for(G=y.count();B0;else{D=t.startPath();if(!E.isBlock&&h(n.editor,D.block,D.blockLimit)&&(Q=n.editor.activeEnterMode!=CKEDITOR.ENTER_BR&&n.editor.config.autoParagraph!==false?n.editor.activeEnterMode==CKEDITOR.ENTER_DIV? +"div":"p":false)){Q=y.createElement(Q);Q.appendBogus();t.insertNode(Q);CKEDITOR.env.needsBrFiller&&(J=Q.getBogus())&&J.remove();t.moveToPosition(Q,CKEDITOR.POSITION_BEFORE_END)}if((D=t.startPath().block)&&!D.equals(H)){if(J=D.getBogus()){J.remove();s.push(D)}H=D}E.firstNotAllowed&&(q=1);if(q&&E.isElement){D=t.startContainer;for(M=null;D&&!r[D.getName()][E.name];){if(D.equals(k)){D=null;break}M=D;D=D.getParent()}if(D){if(M){S=t.splitElement(M);n.zombies.push(S);n.zombies.push(M)}}else{M=k.getName(); +T=!B;D=B==x.length-1;M=d(E.node,M);for(var O=[],V=M.length,W=0,Y=void 0,Z=0,U=-1;W0;){d=a.getItem(b); +if(!CKEDITOR.tools.trim(d.getHtml())){d.appendBogus();CKEDITOR.env.ie&&(CKEDITOR.env.version<9&&d.getChildCount())&&d.getFirst().remove()}}}return function(d){var e=d.startContainer,f=e.getAscendant("table",1),h=false;c(f.getElementsByTag("td"));c(f.getElementsByTag("th"));f=d.clone();f.setStart(e,0);f=a(f).lastBackward();if(!f){f=d.clone();f.setEndAt(e,CKEDITOR.POSITION_BEFORE_END);f=a(f).lastForward();h=true}f||(f=e);if(f.is("table")){d.setStartAt(f,CKEDITOR.POSITION_BEFORE_START);d.collapse(true); +f.remove()}else{f.is({tbody:1,thead:1,tfoot:1})&&(f=b(f,"tr",h));f.is("tr")&&(f=b(f,f.getParent().is("thead")?"th":"td",h));(e=f.getBogus())&&e.remove();d.moveToPosition(f,h?CKEDITOR.POSITION_AFTER_START:CKEDITOR.POSITION_BEFORE_END)}}}()})(); +(function(){function a(){var a=this._.fakeSelection,b;if(a){b=this.getSelection(1);if(!b||!b.isHidden()){a.reset();a=0}}if(!a){a=b||this.getSelection(1);if(!a||a.getType()==CKEDITOR.SELECTION_NONE)return}this.fire("selectionCheck",a);b=this.elementPath();if(!b.compare(this._.selectionPreviousPath)){if(CKEDITOR.env.webkit)this._.previousActive=this.document.getActive();this._.selectionPreviousPath=b;this.fire("selectionChange",{selection:a,path:b})}}function f(){q=true;if(!w){b.call(this);w=CKEDITOR.tools.setTimeout(b, +200,this)}}function b(){w=null;if(q){CKEDITOR.tools.setTimeout(a,0,this);q=false}}function c(a){return t(a)||a.type==CKEDITOR.NODE_ELEMENT&&!a.is(CKEDITOR.dtd.$empty)?true:false}function e(a){function b(c,d){return!c||c.type==CKEDITOR.NODE_TEXT?false:a.clone()["moveToElementEdit"+(d?"End":"Start")](c)}if(!(a.root instanceof CKEDITOR.editable))return false;var d=a.startContainer,e=a.getPreviousNode(c,null,d),f=a.getNextNode(c,null,d);return b(e)||b(f,1)||!e&&!f&&!(d.type==CKEDITOR.NODE_ELEMENT&&d.isBlockBoundary()&& +d.getBogus())?true:false}function d(a){return a.getCustomData("cke-fillingChar")}function h(a,b){var c=a&&a.removeCustomData("cke-fillingChar");if(c){if(b!==false){var d,e=a.getDocument().getSelection().getNative(),f=e&&e.type!="None"&&e.getRangeAt(0);if(c.getLength()>1&&f&&f.intersectsNode(c.$)){d=j(e);f=e.focusNode==c.$&&e.focusOffset>0;e.anchorNode==c.$&&e.anchorOffset>0&&d[0].offset--;f&&d[1].offset--}}c.setText(k(c.getText()));d&&g(a.getDocument().$,d)}}function k(a){return a.replace(/\u200B( )?/g, +function(a){return a[1]?" ":""})}function j(a){return[{node:a.anchorNode,offset:a.anchorOffset},{node:a.focusNode,offset:a.focusOffset}]}function g(a,b){var c=a.getSelection(),d=a.createRange();d.setStart(b[0].node,b[0].offset);d.collapse(true);c.removeAllRanges();c.addRange(d);try{c.extend(b[1].node,b[1].offset)}catch(e){}}function m(a){var b=CKEDITOR.dom.element.createFromHtml('
 
', +a.document);a.fire("lockSnapshot");a.editable().append(b);var c=a.getSelection(1),d=a.createRange(),e=c.root.on("selectionchange",function(a){a.cancel()},null,null,0);d.setStartAt(b,CKEDITOR.POSITION_AFTER_START);d.setEndAt(b,CKEDITOR.POSITION_BEFORE_END);c.selectRanges([d]);e.removeListener();a.fire("unlockSnapshot");a._.hiddenSelectionContainer=b}function y(a){var b={37:1,39:1,8:1,46:1};return function(c){var d=c.data.getKeystroke();if(b[d]){var e=a.getSelection().getRanges(),f=e[0];if(e.length== +1&&f.collapsed)if((d=f[d<38?"getPreviousEditableNode":"getNextEditableNode"]())&&d.type==CKEDITOR.NODE_ELEMENT&&d.getAttribute("contenteditable")=="false"){a.getSelection().fake(d);c.data.preventDefault();c.cancel()}}}}function s(a){for(var b=0;b=d.getLength()?g.setStartAfter(d):g.setStartBefore(d));e&&e.type==CKEDITOR.NODE_TEXT&&(h?g.setEndAfter(e):g.setEndBefore(e));d=new CKEDITOR.dom.walker(g);d.evaluator=function(d){if(d.type==CKEDITOR.NODE_ELEMENT&&d.isReadOnly()){var e=c.clone();c.setEndBefore(d);c.collapsed&&a.splice(b--,1);if(!(d.getPosition(g.endContainer)&CKEDITOR.POSITION_CONTAINS)){e.setStartAfter(d); +e.collapsed||a.splice(b+1,0,e)}return true}return false};d.next()}}return a}var w,q,t=CKEDITOR.dom.walker.invisible(1),i=function(){function a(b){return function(a){var c=a.editor.createRange();c.moveToClosestEditablePosition(a.selected,b)&&a.editor.getSelection().selectRanges([c]);return false}}function b(a){return function(b){var c=b.editor,d=c.createRange(),e;if(!(e=d.moveToClosestEditablePosition(b.selected,a)))e=d.moveToClosestEditablePosition(b.selected,!a);e&&c.getSelection().selectRanges([d]); +c.fire("saveSnapshot");b.selected.remove();if(!e){d.moveToElementEditablePosition(c.editable());c.getSelection().selectRanges([d])}c.fire("saveSnapshot");return false}}var c=a(),d=a(1);return{37:c,38:c,39:d,40:d,8:b(),46:b(1)}}();CKEDITOR.on("instanceCreated",function(b){function c(){var a=d.getSelection();a&&a.removeAllRanges()}var d=b.editor;d.on("contentDom",function(){function b(){z=new CKEDITOR.dom.selection(d.getSelection());z.lock()}function c(){l.removeListener("mouseup",c);i.removeListener("mouseup", +c);var a=CKEDITOR.document.$.selection,b=a.createRange();a.type!="None"&&b.parentElement().ownerDocument==e.$&&b.select()}var e=d.document,l=CKEDITOR.document,g=d.editable(),p=e.getBody(),i=e.getDocumentElement(),v=g.isInline(),j,z;CKEDITOR.env.gecko&&g.attachListener(g,"focus",function(a){a.removeListener();if(j!==0)if((a=d.getSelection().getNative())&&a.isCollapsed&&a.anchorNode==g.$){a=d.createRange();a.moveToElementEditStart(g);a.select()}},null,null,-2);g.attachListener(g,CKEDITOR.env.webkit? +"DOMFocusIn":"focus",function(){j&&CKEDITOR.env.webkit&&(j=d._.previousActive&&d._.previousActive.equals(e.getActive()));d.unlockSelection(j);j=0},null,null,-1);g.attachListener(g,"mousedown",function(){j=0});if(CKEDITOR.env.ie||v){A?g.attachListener(g,"beforedeactivate",b,null,null,-1):g.attachListener(d,"selectionCheck",b,null,null,-1);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusOut":"blur",function(){d.lockSelection(z);j=1},null,null,-1);g.attachListener(g,"mousedown",function(){j=0})}if(CKEDITOR.env.ie&& +!v){var B;g.attachListener(g,"mousedown",function(a){if(a.data.$.button==2){a=d.document.getSelection();if(!a||a.getType()==CKEDITOR.SELECTION_NONE)B=d.window.getScrollPosition()}});g.attachListener(g,"mouseup",function(a){if(a.data.$.button==2&&B){d.document.$.documentElement.scrollLeft=B.x;d.document.$.documentElement.scrollTop=B.y}B=null});if(e.$.compatMode!="BackCompat"){if(CKEDITOR.env.ie7Compat||CKEDITOR.env.ie6Compat)i.on("mousedown",function(a){function b(a){a=a.data.$;if(d){var c=p.$.createTextRange(); +try{c.moveToPoint(a.clientX,a.clientY)}catch(e){}d.setEndPoint(f.compareEndPoints("StartToStart",c)<0?"EndToEnd":"StartToStart",c);d.select()}}function c(){i.removeListener("mousemove",b);l.removeListener("mouseup",c);i.removeListener("mouseup",c);d.select()}a=a.data;if(a.getTarget().is("html")&&a.$.y7&&CKEDITOR.env.version<11)i.on("mousedown",function(a){if(a.data.getTarget().is("html")){l.on("mouseup",c);i.on("mouseup",c)}})}}g.attachListener(g,"selectionchange",a,d);g.attachListener(g,"keyup",f,d);g.attachListener(g,CKEDITOR.env.webkit?"DOMFocusIn":"focus",function(){d.forceNextSelectionCheck();d.selectionChange(1)});if(v&&(CKEDITOR.env.webkit||CKEDITOR.env.gecko)){var x;g.attachListener(g,"mousedown",function(){x=1});g.attachListener(e.getDocumentElement(),"mouseup", +function(){x&&f.call(d);x=0})}else g.attachListener(CKEDITOR.env.ie?g:e.getDocumentElement(),"mouseup",f,d);CKEDITOR.env.webkit&&g.attachListener(e,"keydown",function(a){switch(a.data.getKey()){case 13:case 33:case 34:case 35:case 36:case 37:case 39:case 8:case 45:case 46:h(g)}},null,null,-1);g.attachListener(g,"keydown",y(d),null,null,-1)});d.on("setData",function(){d.unlockSelection();CKEDITOR.env.webkit&&c()});d.on("contentDomUnload",function(){d.unlockSelection()});if(CKEDITOR.env.ie9Compat)d.on("beforeDestroy", +c,null,null,9);d.on("dataReady",function(){delete d._.fakeSelection;delete d._.hiddenSelectionContainer;d.selectionChange(1)});d.on("loadSnapshot",function(){var a=CKEDITOR.dom.walker.nodeType(CKEDITOR.NODE_ELEMENT),b=d.editable().getLast(a);if(b&&b.hasAttribute("data-cke-hidden-sel")){b.remove();if(CKEDITOR.env.gecko)(a=d.editable().getFirst(a))&&(a.is("br")&&a.getAttribute("_moz_editor_bogus_node"))&&a.remove()}},null,null,100);d.on("key",function(a){if(d.mode=="wysiwyg"){var b=d.getSelection(); +if(b.isFake){var c=i[a.data.keyCode];if(c)return c({editor:d,selected:b.getSelectedElement(),selection:b,keyEvent:a})}}})});CKEDITOR.on("instanceReady",function(a){function b(){var a=e.editable();if(a)if(a=d(a)){var c=e.document.$.getSelection();if(c.type!="None"&&(c.anchorNode==a.$||c.focusNode==a.$))i=j(c);f=a.getText();a.setText(k(f))}}function c(){var a=e.editable();if(a)if(a=d(a)){a.setText(f);if(i){g(e.document.$,i);i=null}}}var e=a.editor,f,i;if(CKEDITOR.env.webkit){e.on("selectionChange", +function(){var a=e.editable(),b=d(a);b&&(b.getCustomData("ready")?h(a):b.setCustomData("ready",1))},null,null,-1);e.on("beforeSetMode",function(){h(e.editable())},null,null,-1);e.on("beforeUndoImage",b);e.on("afterUndoImage",c);e.on("beforeGetData",b,null,null,0);e.on("getData",c)}});CKEDITOR.editor.prototype.selectionChange=function(b){(b?a:f).call(this)};CKEDITOR.editor.prototype.getSelection=function(a){if((this._.savedSelection||this._.fakeSelection)&&!a)return this._.savedSelection||this._.fakeSelection; +return(a=this.editable())&&this.mode=="wysiwyg"?new CKEDITOR.dom.selection(a):null};CKEDITOR.editor.prototype.lockSelection=function(a){a=a||this.getSelection(1);if(a.getType()!=CKEDITOR.SELECTION_NONE){!a.isLocked&&a.lock();this._.savedSelection=a;return true}return false};CKEDITOR.editor.prototype.unlockSelection=function(a){var b=this._.savedSelection;if(b){b.unlock(a);delete this._.savedSelection;return true}return false};CKEDITOR.editor.prototype.forceNextSelectionCheck=function(){delete this._.selectionPreviousPath}; +CKEDITOR.dom.document.prototype.getSelection=function(){return new CKEDITOR.dom.selection(this)};CKEDITOR.dom.range.prototype.select=function(){var a=this.root instanceof CKEDITOR.editable?this.root.editor.getSelection():new CKEDITOR.dom.selection(this.root);a.selectRanges([this]);return a};CKEDITOR.SELECTION_NONE=1;CKEDITOR.SELECTION_TEXT=2;CKEDITOR.SELECTION_ELEMENT=3;var A=typeof window.getSelection!="function",u=1;CKEDITOR.dom.selection=function(a){if(a instanceof CKEDITOR.dom.selection)var b= +a,a=a.root;var c=a instanceof CKEDITOR.dom.element;this.rev=b?b.rev:u++;this.document=a instanceof CKEDITOR.dom.document?a:a.getDocument();this.root=c?a:this.document.getBody();this.isLocked=0;this._={cache:{}};if(b){CKEDITOR.tools.extend(this._.cache,b._.cache);this.isFake=b.isFake;this.isLocked=b.isLocked;return this}var a=this.getNative(),d,e;if(a)if(a.getRangeAt)d=(e=a.rangeCount&&a.getRangeAt(0))&&new CKEDITOR.dom.node(e.commonAncestorContainer);else{try{e=a.createRange()}catch(f){}d=e&&CKEDITOR.dom.element.get(e.item&& +e.item(0)||e.parentElement())}if(!d||!(d.type==CKEDITOR.NODE_ELEMENT||d.type==CKEDITOR.NODE_TEXT)||!this.root.equals(d)&&!this.root.contains(d)){this._.cache.type=CKEDITOR.SELECTION_NONE;this._.cache.startElement=null;this._.cache.selectedElement=null;this._.cache.selectedText="";this._.cache.ranges=new CKEDITOR.dom.rangeList}return this};var o={img:1,hr:1,li:1,table:1,tr:1,td:1,th:1,embed:1,object:1,ol:1,ul:1,a:1,input:1,form:1,select:1,textarea:1,button:1,fieldset:1,thead:1,tfoot:1};CKEDITOR.dom.selection.prototype= +{getNative:function(){return this._.cache.nativeSel!==void 0?this._.cache.nativeSel:this._.cache.nativeSel=A?this.document.$.selection:this.document.getWindow().$.getSelection()},getType:A?function(){var a=this._.cache;if(a.type)return a.type;var b=CKEDITOR.SELECTION_NONE;try{var c=this.getNative(),d=c.type;if(d=="Text")b=CKEDITOR.SELECTION_TEXT;if(d=="Control")b=CKEDITOR.SELECTION_ELEMENT;if(c.createRange().parentElement())b=CKEDITOR.SELECTION_TEXT}catch(e){}return a.type=b}:function(){var a=this._.cache; +if(a.type)return a.type;var b=CKEDITOR.SELECTION_TEXT,c=this.getNative();if(!c||!c.rangeCount)b=CKEDITOR.SELECTION_NONE;else if(c.rangeCount==1){var c=c.getRangeAt(0),d=c.startContainer;if(d==c.endContainer&&d.nodeType==1&&c.endOffset-c.startOffset==1&&o[d.childNodes[c.startOffset].nodeName.toLowerCase()])b=CKEDITOR.SELECTION_ELEMENT}return a.type=b},getRanges:function(){var a=A?function(){function a(b){return(new CKEDITOR.dom.node(b)).getIndex()}var b=function(b,c){b=b.duplicate();b.collapse(c); +var d=b.parentElement();if(!d.hasChildNodes())return{container:d,offset:0};for(var e=d.children,f,g,h=b.duplicate(),v=0,l=e.length-1,i=-1,j,x;v<=l;){i=Math.floor((v+l)/2);f=e[i];h.moveToElementText(f);j=h.compareEndPoints("StartToStart",b);if(j>0)l=i-1;else if(j<0)v=i+1;else return{container:d,offset:a(f)}}if(i==-1||i==e.length-1&&j<0){h.moveToElementText(d);h.setEndPoint("StartToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;e=d.childNodes;if(!h){f=e[e.length-1];return f.nodeType!=CKEDITOR.NODE_TEXT? +{container:d,offset:e.length}:{container:f,offset:f.nodeValue.length}}for(d=e.length;h>0&&d>0;){g=e[--d];if(g.nodeType==CKEDITOR.NODE_TEXT){x=g;h=h-g.nodeValue.length}}return{container:x,offset:-h}}h.collapse(j>0?true:false);h.setEndPoint(j>0?"StartToStart":"EndToStart",b);h=h.text.replace(/(\r\n|\r)/g,"\n").length;if(!h)return{container:d,offset:a(f)+(j>0?0:1)};for(;h>0;)try{g=f[j>0?"previousSibling":"nextSibling"];if(g.nodeType==CKEDITOR.NODE_TEXT){h=h-g.nodeValue.length;x=g}f=g}catch(m){return{container:d, +offset:a(f)}}return{container:x,offset:j>0?-h:x.nodeValue.length+h}};return function(){var a=this.getNative(),c=a&&a.createRange(),d=this.getType();if(!a)return[];if(d==CKEDITOR.SELECTION_TEXT){a=new CKEDITOR.dom.range(this.root);d=b(c,true);a.setStart(new CKEDITOR.dom.node(d.container),d.offset);d=b(c);a.setEnd(new CKEDITOR.dom.node(d.container),d.offset);a.endContainer.getPosition(a.startContainer)&CKEDITOR.POSITION_PRECEDING&&a.endOffset<=a.startContainer.getIndex()&&a.collapse();return[a]}if(d== +CKEDITOR.SELECTION_ELEMENT){for(var d=[],e=0;e1){g=a[a.length-1];a[0].setEnd(g.endContainer,g.endOffset)}g=a[0];var a=g.collapsed,m,k,v;if((c=g.getEnclosedNode())&&c.type==CKEDITOR.NODE_ELEMENT&&c.getName()in o&&(!c.is("a")||!c.getText()))try{v=c.$.createControlRange(); +v.addElement(c.$);v.select();return}catch(q){}if(g.startContainer.type==CKEDITOR.NODE_ELEMENT&&g.startContainer.getName()in b||g.endContainer.type==CKEDITOR.NODE_ELEMENT&&g.endContainer.getName()in b){g.shrink(CKEDITOR.NODE_ELEMENT,true);a=g.collapsed}v=g.createBookmark();b=v.startNode;if(!a)f=v.endNode;v=g.document.$.body.createTextRange();v.moveToElementText(b.$);v.moveStart("character",1);if(f){i=g.document.$.body.createTextRange();i.moveToElementText(f.$);v.setEndPoint("EndToEnd",i);v.moveEnd("character", +-1)}else{m=b.getNext(j);k=b.hasAscendant("pre");m=!(m&&m.getText&&m.getText().match(i))&&(k||!b.hasPrevious()||b.getPrevious().is&&b.getPrevious().is("br"));k=g.document.createElement("span");k.setHtml("");k.insertBefore(b);m&&g.document.createText("").insertBefore(b)}g.setStartBefore(b);b.remove();if(a){if(m){v.moveStart("character",-1);v.select();g.document.$.selection.clear()}else v.select();g.moveToPosition(k,CKEDITOR.POSITION_BEFORE_START);k.remove()}else{g.setEndBefore(f);f.remove(); +v.select()}}else{f=this.getNative();if(!f)return;this.removeAllRanges();for(v=0;v=0){g.collapse(1);k.setEnd(g.endContainer.$, +g.endOffset)}else throw z;}f.addRange(k)}}this.reset();this.root.fire("selectionchange")}}},fake:function(a){var b=this.root.editor;this.reset();m(b);var c=this._.cache,d=new CKEDITOR.dom.range(this.root);d.setStartBefore(a);d.setEndAfter(a);c.ranges=new CKEDITOR.dom.rangeList(d);c.selectedElement=c.startElement=a;c.type=CKEDITOR.SELECTION_ELEMENT;c.selectedText=c.nativeSel=null;this.isFake=1;this.rev=u++;b._.fakeSelection=this;this.root.fire("selectionchange")},isHidden:function(){var a=this.getCommonAncestor(); +a&&a.type==CKEDITOR.NODE_TEXT&&(a=a.getParent());return!(!a||!a.data("cke-hidden-sel"))},createBookmarks:function(a){a=this.getRanges().createBookmarks(a);this.isFake&&(a.isFake=1);return a},createBookmarks2:function(a){a=this.getRanges().createBookmarks2(a);this.isFake&&(a.isFake=1);return a},selectBookmarks:function(a){for(var b=[],c=0;c]*>)[ \t\r\n]*/gi,"$1");f=f.replace(/([ \t\n\r]+| )/g, +" ");f=f.replace(/]*>/gi,"\n");if(CKEDITOR.env.ie){var h=a.getDocument().createElement("div");h.append(e);e.$.outerHTML="
"+f+"
";e.copyAttributes(h.getFirst());e=h.getFirst().remove()}else e.setHtml(f);b=e}else f?b=y(c?[a.getHtml()]:g(a),b):a.moveChildren(b);b.replace(a);if(d){var c=b,i;if((i=c.getPrevious(F))&&i.type==CKEDITOR.NODE_ELEMENT&&i.is("pre")){d=m(i.getHtml(),/\n$/,"")+"\n\n"+m(c.getHtml(),/^\n/,"");CKEDITOR.env.ie?c.$.outerHTML="
"+d+"
":c.setHtml(d);i.remove()}}else c&& +t(b)}function g(a){var b=[];m(a.getOuterHtml(),/(\S\s*)\n(?:\s|(]+data-cke-bookmark.*?\/span>))*\n(?!$)/gi,function(a,b,c){return b+""+c+"
"}).replace(/([\s\S]*?)<\/pre>/gi,function(a,c){b.push(c)});return b}function m(a,b,c){var d="",e="",a=a.replace(/(^]+data-cke-bookmark.*?\/span>)|(]+data-cke-bookmark.*?\/span>$)/gi,function(a,b,c){b&&(d=b);c&&(e=c);return""});return d+a.replace(b,c)+e}function y(a,b){var c;a.length>1&&(c=new CKEDITOR.dom.documentFragment(b.getDocument()));
+for(var d=0;d"),e=e.replace(/[ \t]{2,}/g,function(a){return CKEDITOR.tools.repeat(" ",a.length-1)+" "});if(c){var f=b.clone();f.setHtml(e);c.append(f)}else b.setHtml(e)}return c||b}function s(a,b){var c=this._.definition,
+d=c.attributes,c=c.styles,e=o(this)[a.getName()],f=CKEDITOR.tools.isEmpty(d)&&CKEDITOR.tools.isEmpty(c),g;for(g in d)if(!((g=="class"||this._.definition.fullMatch)&&a.getAttribute(g)!=l(g,d[g]))&&!(b&&g.slice(0,5)=="data-")){f=a.hasAttribute(g);a.removeAttribute(g)}for(var h in c)if(!(this._.definition.fullMatch&&a.getStyle(h)!=l(h,c[h],true))){f=f||!!a.getStyle(h);a.removeStyle(h)}q(a,e,r[a.getName()]);f&&(this._.definition.alwaysRemoveElement?t(a,1):!CKEDITOR.dtd.$block[a.getName()]||this._.enterMode==
+CKEDITOR.ENTER_BR&&!a.hasAttributes()?t(a):a.renameNode(this._.enterMode==CKEDITOR.ENTER_P?"p":"div"))}function w(a){for(var b=o(this),c=a.getElementsByTag(this.element),d,e=c.count();--e>=0;){d=c.getItem(e);d.isReadOnly()||s.call(this,d,true)}for(var f in b)if(f!=this.element){c=a.getElementsByTag(f);for(e=c.count()-1;e>=0;e--){d=c.getItem(e);d.isReadOnly()||q(d,b[f])}}}function q(a,b,c){if(b=b&&b.attributes)for(var d=0;d",a||b.name,"");return c.join("")},getDefinition:function(){return this._.definition}};CKEDITOR.style.getStyleText=function(a){var b=a._ST;if(b)return b;var b=a.styles,c=
+a.attributes&&a.attributes.style||"",d="";c.length&&(c=c.replace(P,";"));for(var e in b){var f=b[e],g=(e+":"+f).replace(P,";");f=="inherit"?d=d+g:c=c+g}c.length&&(c=CKEDITOR.tools.normalizeCssText(c,true));return a._ST=c+d};CKEDITOR.style.customHandlers={};CKEDITOR.style.addCustomHandler=function(a){var b=function(a){this._={definition:a};this.setup&&this.setup(a)};b.prototype=CKEDITOR.tools.extend(CKEDITOR.tools.prototypedCopy(CKEDITOR.style.prototype),{assignedTo:CKEDITOR.STYLE_OBJECT},a,true);
+return this.customHandlers[a.type]=b};var K=CKEDITOR.POSITION_PRECEDING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED,I=CKEDITOR.POSITION_FOLLOWING|CKEDITOR.POSITION_IDENTICAL|CKEDITOR.POSITION_IS_CONTAINED})();CKEDITOR.styleCommand=function(a,f){this.requiredContent=this.allowedContent=this.style=a;CKEDITOR.tools.extend(this,f,true)};
+CKEDITOR.styleCommand.prototype.exec=function(a){a.focus();this.state==CKEDITOR.TRISTATE_OFF?a.applyStyle(this.style):this.state==CKEDITOR.TRISTATE_ON&&a.removeStyle(this.style)};CKEDITOR.stylesSet=new CKEDITOR.resourceManager("","stylesSet");CKEDITOR.addStylesSet=CKEDITOR.tools.bind(CKEDITOR.stylesSet.add,CKEDITOR.stylesSet);CKEDITOR.loadStylesSet=function(a,f,b){CKEDITOR.stylesSet.addExternal(a,f,"");CKEDITOR.stylesSet.load(a,b)};
+CKEDITOR.tools.extend(CKEDITOR.editor.prototype,{attachStyleStateChange:function(a,f){var b=this._.styleStateChangeCallbacks;if(!b){b=this._.styleStateChangeCallbacks=[];this.on("selectionChange",function(a){for(var e=0;e"}});"use strict";
+(function(){var a={},f={},b;for(b in CKEDITOR.dtd.$blockLimit)b in CKEDITOR.dtd.$list||(a[b]=1);for(b in CKEDITOR.dtd.$block)b in CKEDITOR.dtd.$blockLimit||b in CKEDITOR.dtd.$empty||(f[b]=1);CKEDITOR.dom.elementPath=function(b,e){var d=null,h=null,k=[],j=b,g,e=e||b.getDocument().getBody();do if(j.type==CKEDITOR.NODE_ELEMENT){k.push(j);if(!this.lastElement){this.lastElement=j;if(j.is(CKEDITOR.dtd.$object)||j.getAttribute("contenteditable")=="false")continue}if(j.equals(e))break;if(!h){g=j.getName();
+j.getAttribute("contenteditable")=="true"?h=j:!d&&f[g]&&(d=j);if(a[g]){var m;if(m=!d){if(g=g=="div"){a:{g=j.getChildren();m=0;for(var y=g.count();m-1}:typeof a=="function"?c=a:typeof a=="object"&&(c=
+function(b){return b.getName()in a});var e=this.elements,d=e.length;f&&d--;if(b){e=Array.prototype.slice.call(e,0);e.reverse()}for(f=0;f=c){d=e.createText("");d.insertAfter(this)}else{a=e.createText("");a.insertAfter(d);a.remove()}return d},substring:function(a,
+f){return typeof f!="number"?this.$.nodeValue.substr(a):this.$.nodeValue.substring(a,f)}});
+(function(){function a(a,c,e){var d=a.serializable,f=c[e?"endContainer":"startContainer"],k=e?"endOffset":"startOffset",j=d?c.document.getById(a.startNode):a.startNode,a=d?c.document.getById(a.endNode):a.endNode;if(f.equals(j.getPrevious())){c.startOffset=c.startOffset-f.getLength()-a.getPrevious().getLength();f=a.getNext()}else if(f.equals(a.getPrevious())){c.startOffset=c.startOffset-f.getLength();f=a.getNext()}f.equals(j.getParent())&&c[k]++;f.equals(a.getParent())&&c[k]++;c[e?"endContainer":"startContainer"]=
+f;return c}CKEDITOR.dom.rangeList=function(a){if(a instanceof CKEDITOR.dom.rangeList)return a;a?a instanceof CKEDITOR.dom.range&&(a=[a]):a=[];return CKEDITOR.tools.extend(a,f)};var f={createIterator:function(){var a=this,c=CKEDITOR.dom.walker.bookmark(),e=[],d;return{getNextRange:function(f){d=d===void 0?0:d+1;var k=a[d];if(k&&a.length>1){if(!d)for(var j=a.length-1;j>=0;j--)e.unshift(a[j].createBookmark(true));if(f)for(var g=0;a[d+g+1];){for(var m=k.document,f=0,j=m.getById(e[g].endNode),m=m.getById(e[g+
+1].startNode);;){j=j.getNextSourceNode(false);if(m.equals(j))f=1;else if(c(j)||j.type==CKEDITOR.NODE_ELEMENT&&j.isBlockBoundary())continue;break}if(!f)break;g++}for(k.moveToBookmark(e.shift());g--;){j=a[++d];j.moveToBookmark(e.shift());k.setEnd(j.endContainer,j.endOffset)}}return k}}},createBookmarks:function(b){for(var c=[],e,d=0;db?-1:1}),e=0,f;e
',CKEDITOR.document);a.appendTo(CKEDITOR.document.getHead());try{var f=a.getComputedStyle("border-top-color"),b=a.getComputedStyle("border-right-color");CKEDITOR.env.hc=!!(f&&f==b)}catch(c){CKEDITOR.env.hc=false}a.remove()}if(CKEDITOR.env.hc)CKEDITOR.env.cssClass=CKEDITOR.env.cssClass+" cke_hc"; +CKEDITOR.document.appendStyleText(".cke{visibility:hidden;}");CKEDITOR.status="loaded";CKEDITOR.fireOnce("loaded");if(a=CKEDITOR._.pending){delete CKEDITOR._.pending;for(f=0;fc;c++){var f=a,h=c,d;d=parseInt(a[c],16);d=("0"+(0>e?0|d*(1+e):0|d+(255-d)*e).toString(16)).slice(-2);f[h]=d}return"#"+a.join("")}}(),c=function(){var b=new CKEDITOR.template("background:#{to};background-image:-webkit-gradient(linear,lefttop,leftbottom,from({from}),to({to}));background-image:-moz-linear-gradient(top,{from},{to});background-image:-webkit-linear-gradient(top,{from},{to});background-image:-o-linear-gradient(top,{from},{to});background-image:-ms-linear-gradient(top,{from},{to});background-image:linear-gradient(top,{from},{to});filter:progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='{from}',endColorstr='{to}');");return function(c, +a){return b.output({from:c,to:a})}}(),f={editor:new CKEDITOR.template("{id}.cke_chrome [border-color:{defaultBorder};] {id} .cke_top [ {defaultGradient}border-bottom-color:{defaultBorder};] {id} .cke_bottom [{defaultGradient}border-top-color:{defaultBorder};] {id} .cke_resizer [border-right-color:{ckeResizer}] {id} .cke_dialog_title [{defaultGradient}border-bottom-color:{defaultBorder};] {id} .cke_dialog_footer [{defaultGradient}outline-color:{defaultBorder};border-top-color:{defaultBorder};] {id} .cke_dialog_tab [{lightGradient}border-color:{defaultBorder};] {id} .cke_dialog_tab:hover [{mediumGradient}] {id} .cke_dialog_contents [border-top-color:{defaultBorder};] {id} .cke_dialog_tab_selected, {id} .cke_dialog_tab_selected:hover [background:{dialogTabSelected};border-bottom-color:{dialogTabSelectedBorder};] {id} .cke_dialog_body [background:{dialogBody};border-color:{defaultBorder};] {id} .cke_toolgroup [{lightGradient}border-color:{defaultBorder};] {id} a.cke_button_off:hover, {id} a.cke_button_off:focus, {id} a.cke_button_off:active [{mediumGradient}] {id} .cke_button_on [{ckeButtonOn}] {id} .cke_toolbar_separator [background-color: {ckeToolbarSeparator};] {id} .cke_combo_button [border-color:{defaultBorder};{lightGradient}] {id} a.cke_combo_button:hover, {id} a.cke_combo_button:focus, {id} .cke_combo_on a.cke_combo_button [border-color:{defaultBorder};{mediumGradient}] {id} .cke_path_item [color:{elementsPathColor};] {id} a.cke_path_item:hover, {id} a.cke_path_item:focus, {id} a.cke_path_item:active [background-color:{elementsPathBg};] {id}.cke_panel [border-color:{defaultBorder};] "), +panel:new CKEDITOR.template(".cke_panel_grouptitle [{lightGradient}border-color:{defaultBorder};] .cke_menubutton_icon [background-color:{menubuttonIcon};] .cke_menubutton:hover .cke_menubutton_icon, .cke_menubutton:focus .cke_menubutton_icon, .cke_menubutton:active .cke_menubutton_icon [background-color:{menubuttonIconHover};] .cke_menuseparator [background-color:{menubuttonIcon};] a:hover.cke_colorbox, a:focus.cke_colorbox, a:active.cke_colorbox [border-color:{defaultBorder};] a:hover.cke_colorauto, a:hover.cke_colormore, a:focus.cke_colorauto, a:focus.cke_colormore, a:active.cke_colorauto, a:active.cke_colormore [background-color:{ckeColorauto};border-color:{defaultBorder};] ")}; +return function(g,e){var a=g.uiColor,a={id:"."+g.id,defaultBorder:b(a,-0.1),defaultGradient:c(b(a,0.9),a),lightGradient:c(b(a,1),b(a,0.7)),mediumGradient:c(b(a,0.8),b(a,0.5)),ckeButtonOn:c(b(a,0.6),b(a,0.7)),ckeResizer:b(a,-0.4),ckeToolbarSeparator:b(a,0.5),ckeColorauto:b(a,0.8),dialogBody:b(a,0.7),dialogTabSelected:c("#FFFFFF","#FFFFFF"),dialogTabSelectedBorder:"#FFF",elementsPathColor:b(a,-0.6),elementsPathBg:a,menubuttonIcon:b(a,0.5),menubuttonIconHover:b(a,0.3)};return f[e].output(a).replace(/\[/g, +"{").replace(/\]/g,"}")}}();CKEDITOR.plugins.add("dialogui",{onLoad:function(){var h=function(b){this._||(this._={});this._["default"]=this._.initValue=b["default"]||"";this._.required=b.required||!1;for(var a=[this._],d=1;darguments.length)){var c=h.call(this,a);c.labelId=CKEDITOR.tools.getNextId()+"_label";this._.children=[];var e={role:a.role||"presentation"};a.includeLabel&&(e["aria-labelledby"]=c.labelId);CKEDITOR.ui.dialog.uiElement.call(this,b,a,d,"div",null, +e,function(){var e=[],g=a.required?" cke_required":"";if(a.labelLayout!="horizontal")e.push('",'");else{g={type:"hbox",widths:a.widths,padding:0,children:[{type:"html",html:'