├── src
├── js
│ └── helpers
│ │ └── .gitkeep
├── less
│ ├── mixins
│ │ └── type.less
│ ├── quirks.less
│ ├── contacts.less
│ ├── mixins.less
│ ├── lavaboom-icons.less
│ ├── hotkeys.less
│ ├── print.less
│ ├── type.less
│ ├── notifications.less
│ ├── actions.less
│ ├── lavaboom-login.less
│ ├── lavaboom.less
│ └── radial-progress.less
├── apps
│ ├── LavaMail
│ │ ├── runs
│ │ │ ├── .gitkeep
│ │ │ └── rootScopeHelpers.js
│ │ ├── vendor
│ │ │ └── .gitkeep
│ │ ├── configs
│ │ │ ├── .gitkeep
│ │ │ └── cfgHotkeys.js
│ │ ├── controllers
│ │ │ ├── .gitkeep
│ │ │ ├── ctrlLsOff.js
│ │ │ ├── ctrlHotkeys.js
│ │ │ ├── ctrlComposeEmail.js
│ │ │ ├── ctrlSettings.js
│ │ │ ├── ctrlSettingsSecurityKey.js
│ │ │ ├── ctrlNavigation.js
│ │ │ ├── ctrlSettingsList.js
│ │ │ ├── ctrlSettingsPersonal.js
│ │ │ ├── ctrlSettingsPlan.js
│ │ │ ├── ctrlDownload.js
│ │ │ └── ctrlContactProfileEmail.js
│ │ ├── decorators
│ │ │ ├── .gitkeep
│ │ │ ├── contacts.js
│ │ │ ├── taOptions.js
│ │ │ ├── dialogs.js
│ │ │ └── taTools.js
│ │ ├── directives
│ │ │ ├── .gitkeep
│ │ │ ├── ngRightClick.js
│ │ │ ├── textAngularFocus.js
│ │ │ ├── customDropZone.js
│ │ │ ├── openRawFile.js
│ │ │ ├── emailContextMenu.js
│ │ │ ├── openFile.js
│ │ │ └── tagOnFocusLoss.js
│ │ ├── factories
│ │ │ ├── .gitkeep
│ │ │ ├── label.js
│ │ │ ├── attachment.js
│ │ │ └── thread.js
│ │ ├── filters
│ │ │ ├── .gitkeep
│ │ │ ├── defaultValue.js
│ │ │ ├── fileName.js
│ │ │ ├── lavatime.js
│ │ │ ├── unread.js
│ │ │ ├── fileExtension.js
│ │ │ ├── filesize.js
│ │ │ ├── lavaday.js
│ │ │ ├── lavadate.js
│ │ │ └── members.js
│ │ ├── services
│ │ │ ├── .gitkeep
│ │ │ ├── composeHelpers.js
│ │ │ ├── textAngularHelpers.js
│ │ │ └── router.js
│ │ ├── .bowerrc
│ │ ├── blocks
│ │ │ ├── directives
│ │ │ │ ├── loading.jade
│ │ │ │ ├── noImage.jade
│ │ │ │ ├── snap.jade
│ │ │ │ └── emailContextMenu.jade
│ │ │ ├── contacts
│ │ │ │ ├── contacts.jade
│ │ │ │ ├── import.jade
│ │ │ │ ├── _contactsList.jade
│ │ │ │ └── _contactsProfileEmail.jade
│ │ │ ├── inbox
│ │ │ │ ├── directEmail.jade
│ │ │ │ ├── inbox.jade
│ │ │ │ ├── _attachmentProgress.jade
│ │ │ │ ├── repliedEmail.jade
│ │ │ │ ├── download.jade
│ │ │ │ ├── forwardedEmail.jade
│ │ │ │ ├── _attachment.jade
│ │ │ │ └── threads.jade
│ │ │ ├── settings
│ │ │ │ ├── settings.jade
│ │ │ │ ├── settingsPlan.jade
│ │ │ │ ├── _settingsList.jade
│ │ │ │ └── settingsProfile.jade
│ │ │ ├── misc
│ │ │ │ ├── lsOff.jade
│ │ │ │ ├── hotkeys.jade
│ │ │ │ └── _notifications.jade
│ │ │ ├── compose
│ │ │ │ └── _composeEmail.jade
│ │ │ └── navigation
│ │ │ │ └── navigation.jade
│ │ ├── package.json
│ │ ├── bower.json
│ │ └── index.toml
│ ├── LavaUtils
│ │ ├── runs
│ │ │ ├── .gitkeep
│ │ │ └── rootScopeHelpers.js
│ │ ├── blocks
│ │ │ ├── .gitkeep
│ │ │ └── misc
│ │ │ │ └── defaultSignature.jade
│ │ ├── configs
│ │ │ ├── .gitkeep
│ │ │ └── cfg.js
│ │ ├── constants
│ │ │ ├── .gitkeep
│ │ │ └── consts.js
│ │ ├── decorators
│ │ │ ├── .gitkeep
│ │ │ ├── $templateCache.js
│ │ │ ├── $sanitize.js
│ │ │ ├── $timeout.js
│ │ │ ├── crypto.js
│ │ │ ├── $translate.js
│ │ │ └── LavaboomAPI.js
│ │ ├── directives
│ │ │ ├── .gitkeep
│ │ │ ├── a.js
│ │ │ ├── fileSelect.js
│ │ │ ├── match.js
│ │ │ └── focus.js
│ │ ├── factories
│ │ │ ├── .gitkeep
│ │ │ ├── proxy.js
│ │ │ ├── key.js
│ │ │ └── cache.js
│ │ ├── services
│ │ │ ├── .gitkeep
│ │ │ ├── loader.js
│ │ │ ├── fileReader.js
│ │ │ ├── saver.js
│ │ │ ├── translate.js
│ │ │ ├── notifications.js
│ │ │ └── tests.js
│ │ ├── vendor
│ │ │ └── .gitkeep
│ │ ├── translations
│ │ │ ├── .gitkeep
│ │ │ └── index.json
│ │ ├── .bowerrc
│ │ ├── package.json
│ │ ├── bower.json
│ │ ├── controllers
│ │ │ └── ctrlChecker.js
│ │ └── index.toml
│ ├── LavaLogin
│ │ ├── configs
│ │ │ ├── .gitkeep
│ │ │ └── cfgLoginNavigation.js
│ │ ├── directives
│ │ │ ├── .gitkeep
│ │ │ ├── maxRepeating.js
│ │ │ └── dictionary.js
│ │ ├── services
│ │ │ ├── .gitkeep
│ │ │ └── signUp.js
│ │ ├── vendor
│ │ │ └── .gitkeep
│ │ ├── controllers
│ │ │ ├── .gitkeep
│ │ │ ├── ctrlGenerateKeys.js
│ │ │ ├── ctrlReservedUsername.js
│ │ │ ├── ctrlSelectPlan.js
│ │ │ ├── ctrlSecureUsername.js
│ │ │ ├── ctrlVerify.js
│ │ │ ├── ctrlPassword.js
│ │ │ ├── ctrlAuth.js
│ │ │ ├── ctrlDetails.js
│ │ │ ├── ctrlBackup.js
│ │ │ ├── ctrlLavaboomLogin.js
│ │ │ └── ctrlGeneratingKeys.js
│ │ ├── .bowerrc
│ │ ├── blocks
│ │ │ ├── misc
│ │ │ │ └── validationMessages.jade
│ │ │ └── login
│ │ │ │ ├── generatingKeys.jade
│ │ │ │ ├── loginOrSignup.jade
│ │ │ │ ├── invite.jade
│ │ │ │ ├── importKey.jade
│ │ │ │ ├── reservedUsername.jade
│ │ │ │ ├── choosePasswordIntro.jade
│ │ │ │ ├── generateKeys.jade
│ │ │ │ ├── backupKey.jade
│ │ │ │ ├── auth.jade
│ │ │ │ ├── plan.jade
│ │ │ │ ├── secureUsername.jade
│ │ │ │ ├── details.jade
│ │ │ │ ├── verifyInvite.jade
│ │ │ │ └── choosePassword.jade
│ │ ├── package.json
│ │ └── index.toml
│ ├── app.js
│ └── LavaLoader
│ │ ├── js
│ │ ├── index.js
│ │ └── host.js
│ │ ├── .bowerrc
│ │ └── index.toml
├── img
│ ├── no-image.png
│ ├── assets
│ │ ├── favicon.ico
│ │ ├── mstile-70x70.png
│ │ ├── favicon-160x160.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-192x192.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon-96x96.png
│ │ ├── mstile-144x144.png
│ │ ├── mstile-150x150.png
│ │ ├── mstile-310x150.png
│ │ ├── mstile-310x310.png
│ │ ├── apple-touch-icon.png
│ │ ├── apple-touch-icon-57x57.png
│ │ ├── apple-touch-icon-60x60.png
│ │ ├── apple-touch-icon-72x72.png
│ │ ├── apple-touch-icon-76x76.png
│ │ ├── apple-touch-icon-114x114.png
│ │ ├── apple-touch-icon-120x120.png
│ │ ├── apple-touch-icon-144x144.png
│ │ ├── apple-touch-icon-152x152.png
│ │ ├── apple-touch-icon-180x180.png
│ │ ├── apple-touch-icon-precomposed.png
│ │ └── browserconfig.xml
│ ├── check.svg
│ ├── avatar.svg
│ ├── Lavaboom-logo-no-shadow.svg
│ ├── loader.svg
│ └── Lavaboom-logo-full-min.svg
├── fonts
│ ├── fonts
│ │ ├── lavaboom.eot
│ │ ├── lavaboom.ttf
│ │ └── lavaboom.woff
│ ├── Read Me.txt
│ ├── demo-files
│ │ ├── demo.js
│ │ └── demo.css
│ └── variables.scss
└── tests
│ ├── helpers
│ └── intervalDigest.js
│ └── integration
│ └── utils
│ └── services
│ └── cryptoSpec.js
├── manifest.json
├── .travis.yml
├── website.conf
├── .drone.yml
├── prepare-env.sh
├── gulp
├── config.js
├── browserifyNgAnnotateHelper.js
├── paths.js
├── utils.js
└── draft.js
├── .jshintrc
├── Dockerfile
├── gulpfile.js
├── .eslintrc
├── karma-unit.conf.js
├── serve.js
├── karma-integration.conf.js
└── fabfile.py
/src/js/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/less/mixins/type.less:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/runs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/runs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/configs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/directives/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/services/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/configs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/decorators/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/directives/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/factories/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/services/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/blocks/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/configs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/constants/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/decorators/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/directives/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/factories/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/services/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/translations/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/app.js:
--------------------------------------------------------------------------------
1 | require('AngularApplication');
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "© Lavaboom",
3 | "version": "0.4.4"
4 | }
--------------------------------------------------------------------------------
/src/img/no-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/no-image.png
--------------------------------------------------------------------------------
/src/apps/LavaLoader/js/index.js:
--------------------------------------------------------------------------------
1 | let loader = require('./loader');
2 | loader(window.assets);
--------------------------------------------------------------------------------
/src/img/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/favicon.ico
--------------------------------------------------------------------------------
/src/apps/LavaLoader/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "./bower_components",
3 | "interactive": false
4 | }
--------------------------------------------------------------------------------
/src/apps/LavaLogin/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "./bower_components",
3 | "interactive": false
4 | }
--------------------------------------------------------------------------------
/src/apps/LavaMail/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "./bower_components",
3 | "interactive": false
4 | }
--------------------------------------------------------------------------------
/src/apps/LavaUtils/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "./bower_components",
3 | "interactive": false
4 | }
--------------------------------------------------------------------------------
/src/fonts/fonts/lavaboom.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/fonts/fonts/lavaboom.eot
--------------------------------------------------------------------------------
/src/fonts/fonts/lavaboom.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/fonts/fonts/lavaboom.ttf
--------------------------------------------------------------------------------
/src/fonts/fonts/lavaboom.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/fonts/fonts/lavaboom.woff
--------------------------------------------------------------------------------
/src/img/assets/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/mstile-70x70.png
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/directives/loading.jade:
--------------------------------------------------------------------------------
1 | .pane-status(ng-show="isLoading")
2 | img(src="/img/loader.svg")
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/defaultValue.js:
--------------------------------------------------------------------------------
1 | module.exports = () =>
2 | (v, defaultValue) => v ? v : defaultValue;
--------------------------------------------------------------------------------
/src/img/assets/favicon-160x160.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/favicon-160x160.png
--------------------------------------------------------------------------------
/src/img/assets/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/favicon-16x16.png
--------------------------------------------------------------------------------
/src/img/assets/favicon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/favicon-192x192.png
--------------------------------------------------------------------------------
/src/img/assets/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/favicon-32x32.png
--------------------------------------------------------------------------------
/src/img/assets/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/favicon-96x96.png
--------------------------------------------------------------------------------
/src/img/assets/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/mstile-144x144.png
--------------------------------------------------------------------------------
/src/img/assets/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/mstile-150x150.png
--------------------------------------------------------------------------------
/src/img/assets/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/mstile-310x150.png
--------------------------------------------------------------------------------
/src/img/assets/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/mstile-310x310.png
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/directives/noImage.jade:
--------------------------------------------------------------------------------
1 | div.no-image
2 | span {{'LAVAMAIL.INBOX.LB_NO_IMAGE' | translate}}
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/contacts/contacts.jade:
--------------------------------------------------------------------------------
1 | include ./_contactsList
2 | .details-pane
3 | .main-contacts-view(ui-view='')
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/src/apps/LavaMail/configs/cfgHotkeys.js:
--------------------------------------------------------------------------------
1 | module.exports = (hotkeysProvider) => {
2 | hotkeysProvider.includeCheatSheet = false;
3 | };
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "iojs"
4 | before_script:
5 | - npm install -g bower
6 | - npm install -g gulp
7 |
--------------------------------------------------------------------------------
/src/img/assets/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lavab/web/HEAD/src/img/assets/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/inbox/directEmail.jade:
--------------------------------------------------------------------------------
1 | section.email
2 | section.body(ng-bind-html="body")
3 | footer.signature(ng-bind-html="signature")
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/inbox/inbox.jade:
--------------------------------------------------------------------------------
1 | #mail-list.list-pane.ng-cloak(ui-view="threads")
2 | #mail-thread.details-pane.ng-cloak(ui-view="emails")
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlGenerateKeys.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $state, user, signUp, crypto) => {
2 | if (!user.isAuthenticated())
3 | $state.go('login');
4 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/fileName.js:
--------------------------------------------------------------------------------
1 | module.exports = () => {
2 | return (filename) => {
3 | let i = filename.lastIndexOf('.');
4 | return i >= 0 ? filename.substr(0, i) : filename;
5 | };
6 | };
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlReservedUsername.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $state, signUp) => {
2 | if (!signUp.reserve)
3 | $state.go('login');
4 | $scope.email = signUp.reserve.altEmail;
5 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/lavatime.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, $translate, $filter) => {
2 |
3 |
4 | return (input) => {
5 |
6 | return $filter('date')(new Date(input), 'HH:mm');
7 |
8 | };
9 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/decorators/contacts.js:
--------------------------------------------------------------------------------
1 | module.exports = ($delegate, $rootScope, co, consts, Cache, Proxy) => {
2 | const self = $delegate;
3 |
4 | const proxy = new Proxy($delegate);
5 |
6 | return $delegate;
7 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/unread.js:
--------------------------------------------------------------------------------
1 | module.exports = () =>
2 | number => {
3 | if (!number || number < 1)
4 | return '';
5 |
6 | if (number < 999)
7 | return number;
8 |
9 | return '999+';
10 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/inbox/_attachmentProgress.jade:
--------------------------------------------------------------------------------
1 | .radial-progress(data-progress="0")
2 | .circle
3 | .mask.full
4 | .fill
5 | .mask.half
6 | .fill
7 | .fill.fix
8 | .shadow
9 | .inset
10 | .percentage
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/fileExtension.js:
--------------------------------------------------------------------------------
1 | module.exports = () => {
2 | return (filename) => {
3 | let i = filename.lastIndexOf('.');
4 | return filename && i >= 0 ? ('.' + filename.substr(i, filename.length - i)) : '';
5 | };
6 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/runs/rootScopeHelpers.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, router) => {
2 | $rootScope.showPopup = router.showPopup;
3 | $rootScope.hidePopup = router.hidePopup;
4 | $rootScope.isPopupState = router.isPopupState;
5 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/blocks/misc/defaultSignature.jade:
--------------------------------------------------------------------------------
1 | br
2 | br
3 | p.text-center
4 | a(href="https://lavaboom.com", target="_blank")
5 | strong {{'USER.LB_SIGNATURE_LN1' | translate}}
6 | br
7 | | {{'USER.LB_SIGNATURE_LN2' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaUtils/configs/cfg.js:
--------------------------------------------------------------------------------
1 | module.exports = (LavaboomAPIProvider, LavaboomHttpAPIProvider, coProvider, consts) => {
2 | LavaboomAPIProvider.url = LavaboomHttpAPIProvider.url = consts.API_URI;
3 | coProvider.coJS = window.coJS;
4 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlLsOff.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $modalInstance) => {
2 | $scope.no = function(){
3 | $modalInstance.dismiss('no');
4 | };
5 |
6 | $scope.yes = function(){
7 | $modalInstance.close('yes');
8 | };
9 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlHotkeys.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, hotkey) => {
2 | let hotkeys = hotkey.getKeys();
3 |
4 | $scope.globalHotkeys = hotkeys.filter(h => h.isGlobal);
5 | $scope.localHotkeys = hotkeys.filter(h => !h.isGlobal);
6 | };
7 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlSelectPlan.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $state, signUp) => {
2 | if (!signUp.tokenSignup)
3 | $state.go('invite');
4 |
5 | $scope.selectPlan = () => {
6 | signUp.plan = 'beta';
7 | $state.go('details');
8 | };
9 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlComposeEmail.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope) => {
2 | $scope.status = {
3 | isDropdownOpened: false
4 | };
5 | $scope.switchContextMenu = () => {
6 | $scope.status.isDropdownOpened = !$scope.status.isDropdownOpened;
7 | };
8 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/translations/index.json:
--------------------------------------------------------------------------------
1 | {
2 | "TRANSLATIONS": {
3 | "en_US": {
4 | "name": "English(US)"
5 | },
6 | "de": {
7 | "name": "Deutsch (formal)"
8 | },
9 | "fr": {
10 | "name": "Français (vous)"
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/src/apps/LavaLoader/index.toml:
--------------------------------------------------------------------------------
1 | [MANIFEST]
2 | version = "1.0"
3 |
4 | [APPLICATION]
5 | name = "LavaLoader"
6 | type = "custom"
7 | version = "1.0"
8 | description = "Lava core application performs loading and bootstrapping of the web"
9 |
10 | entry = "./js/index.js"
--------------------------------------------------------------------------------
/src/apps/LavaUtils/directives/a.js:
--------------------------------------------------------------------------------
1 | module.exports = () => ({
2 | restrict: 'E',
3 | link: (scope, elem, attrs) => {
4 | if(attrs.ngClick || attrs.href === '' || attrs.href === '#'){
5 | elem.on('click', (e) => {
6 | e.preventDefault();
7 | });
8 | }
9 | }
10 | });
--------------------------------------------------------------------------------
/website.conf:
--------------------------------------------------------------------------------
1 | server {
2 | root /var/www;
3 | index index.html index.htm;
4 |
5 | server_name localhost;
6 |
7 | location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {
8 | expires 1d;
9 | }
10 |
11 | location / {
12 | try_files $uri $uri/ /index.html;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/apps/LavaUtils/directives/fileSelect.js:
--------------------------------------------------------------------------------
1 | module.exports = ($parse) => ({
2 | link: (scope, element, attrs) => {
3 | const getFile = $parse(attrs.getFile);
4 | element.bind('change', (e) => {
5 | getFile(scope, {file: (e.srcElement || e.target).files[0]});
6 | });
7 | }
8 | });
9 |
--------------------------------------------------------------------------------
/src/less/quirks.less:
--------------------------------------------------------------------------------
1 | .ta-hidden-input {
2 | display: none;
3 | }
4 |
5 | .ui-select-choices-row-inner:empty {
6 | display: none !important;
7 | }
8 |
9 | // all hail iOS, height: 0 means nothing for Safari
10 | // god, why?
11 | .collapse:not(.in) {
12 | display: none !important;
13 | }
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/inbox/repliedEmail.jade:
--------------------------------------------------------------------------------
1 | section.email
2 | section.body(ng-bind-html="body")
3 | footer.signature(ng-bind-html="signature")
4 | blockquote.replied-to(ng-repeat="reply in replies")
5 | header {{'LAVAMAIL.INBOX.REPLY_HEADER' | translate:reply}}:
6 | section(ng-bind-html="reply.body")
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlSettings.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, $scope, notifications) => {
2 | $rootScope.$bind('notifications', () => {
3 | $scope.notificationsInfo = notifications.get('info', 'settings');
4 | $scope.notificationsWarning = notifications.get('warning', 'settings');
5 | });
6 | };
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/misc/validationMessages.jade:
--------------------------------------------------------------------------------
1 | span(ng-message="required") {{'VALIDATION.LB_REQUIRED' | translate}}
2 | span(ng-message="minlength") {{'VALIDATION.LB_MIN' | translate}}
3 | span(ng-message="email") {{'VALIDATION.LB_EMAIL' | translate}}
4 | span(ng-message="match") {{'VALIDATION.LB_MATCH' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaLogin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LavaLogin",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "MIT",
11 | "devDependencies": {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/directives/ngRightClick.js:
--------------------------------------------------------------------------------
1 | module.exports = ($parse) =>
2 | (scope, element, attrs) => {
3 | const fn = $parse(attrs.ngRightClick);
4 | element.bind('contextmenu', event => {
5 | scope.$apply(() => {
6 | event.preventDefault();
7 | fn(scope, {$event: event});
8 | });
9 | });
10 | };
--------------------------------------------------------------------------------
/.drone.yml:
--------------------------------------------------------------------------------
1 | image: registry.lavaboom.io/lavaboom/wrapper-web
2 | script:
3 | - pip install fabric
4 | - fab integrate
5 | notify:
6 | slack:
7 | webhook_url: $$SLACK_URL
8 | channel: $$SLACK_CHANNEL
9 | username: lavadrone
10 | on_started: true
11 | on_success: true
12 | on_failure: true
13 |
--------------------------------------------------------------------------------
/prepare-env.sh:
--------------------------------------------------------------------------------
1 | export GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
2 |
3 | if [ "$GIT_BRANCH" = "staging" ]
4 | then
5 | export API_URI=https://api.lavaboom.io
6 | export ROOT_DOMAIN=lavaboom.io
7 | elif [ "$GIT_BRANCH" = "develop" ]
8 | then
9 | export API_URI=https://api.lavaboom.co
10 | export ROOT_DOMAIN=lavaboom.co
11 | fi
12 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlSecureUsername.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $state, co, signUp) => {
2 | $scope.form = {
3 | username: '',
4 | email: ''
5 | };
6 |
7 | $scope.requestSecure = () => co(function *(){
8 | yield signUp.register($scope.form.username, $scope.form.email);
9 |
10 | yield $state.go('reservedUsername');
11 | });
12 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/directives/match.js:
--------------------------------------------------------------------------------
1 | module.exports = () => ({
2 | require: 'ngModel',
3 | scope: {
4 | otherModelValue: '=match'
5 | },
6 | link: (scope, element, attributes, ngModel) => {
7 | ngModel.$validators.match = modelValue => modelValue == scope.otherModelValue;
8 | scope.$watch('otherModelValue', () => ngModel.$validate());
9 | }
10 | });
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/settings/settings.jade:
--------------------------------------------------------------------------------
1 | include ./_settingsList
2 | .details-pane(ng-controller="CtrlSettings")
3 | .filters.row.no-gutter.lava-icon-row
4 | .filters.row.no-gutter.lava-icon-row(ng-class="'notifications-' + getNotificationsLength(notificationsInfo, notificationsWarning)")
5 | include ../misc/_notifications
6 | .main-settings-view.main-panel(ui-view='')
--------------------------------------------------------------------------------
/src/apps/LavaMail/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LavaMail",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "q-encoding": "^0.1.1",
13 | "utf8": "^2.1.0",
14 | "vcardjs": "^0.3.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/generatingKeys.jade:
--------------------------------------------------------------------------------
1 | form.ng-cloak(ng-class="{'errors': currentErrorMessage}")
2 | img.login-logo(src="#{resolveAsset('/img/Lavaboom-logo-no-shadow.svg')}")
3 |
4 | .progress
5 | .progress-bar(role='progressbar', aria-valuenow='{{progress}}', aria-valuemin='0', aria-valuemax='100', style='width: {{progress}}%;')
6 |
7 | p.lead(ng-bind="label")
8 |
9 | p.text-center(ng-bind="subLabel")
--------------------------------------------------------------------------------
/src/fonts/Read Me.txt:
--------------------------------------------------------------------------------
1 | Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures.
2 |
3 | You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects.
4 |
5 | You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu > Manage Projects) to retrieve your icon selection.
6 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/directives/snap.jade:
--------------------------------------------------------------------------------
1 | div.snap
2 | span {{'LAVAMAIL.INBOX.CORRUPTED_EMAIL' | translate}}
3 | a(href="#", ng-click="openEmail(originalEmail, isHtml)") {{'LAVAMAIL.INBOX.CORRUPTED_EMAIL_OPEN' | translate}}
4 | | {{'LAVAMAIL.INBOX.CORRUPTED_EMAIL_OR' | translate}}
5 | a(href="#", ng-click="downloadEmail(originalEmail, originalEmailName, isHtml)") {{'LAVAMAIL.INBOX.CORRUPTED_EMAIL_DOWNLOAD' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaMail/directives/textAngularFocus.js:
--------------------------------------------------------------------------------
1 | module.exports = ($parse, $timeout, textAngularManager) => {
2 | return {
3 | link: (scope, element, attributes) => {
4 | $timeout(() => {
5 | let editorScope = textAngularManager.retrieveEditor(attributes.name).scope;
6 | editorScope.displayElements.text.triggerHandler('focus');
7 | editorScope.displayElements.text[0].focus();
8 | });
9 | }
10 | };
11 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/directives/emailContextMenu.jade:
--------------------------------------------------------------------------------
1 | span.dropdown(dropdown="", is-open="isOpen")
2 | ul.dropdown-menu(role="menu")
3 | li(ng-show="!isExistingContact")
4 | a(ui-sref="main.contacts.profile({contactId: 'new', email: email})") {{'LAVAMAIL.CONTEXT_MENU.EMAIL.LB_ADD_CONTACT' | translate}}
5 | li(ng-show="!noReply")
6 | a(ui-sref=".popup.compose({to: email})") {{'LAVAMAIL.CONTEXT_MENU.EMAIL.LB_REPLY' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaUtils/directives/focus.js:
--------------------------------------------------------------------------------
1 | module.exports = ($timeout) => ({
2 | link: ( scope, element, attrs ) => {
3 | scope.$watch( attrs.focus, val => {
4 | if ( angular.isDefined( val ) && val ) {
5 | $timeout( () => { element[0].focus(); } );
6 | }
7 | }, true);
8 |
9 | element.bind('blur', () => {
10 | if ( angular.isDefined( attrs.focusLost ) ) {
11 | scope.$apply( attrs.focusLost );
12 | }
13 | });
14 | }
15 | });
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/loginOrSignup.jade:
--------------------------------------------------------------------------------
1 | form.ng-cloak(ng-class="{'errors': currentErrorMessage}")
2 | img.login-logo(src="#{resolveAsset('/img/Lavaboom-logo-no-shadow.svg')}")
3 | p.lead {{'LAVALOGIN.SIGNUP.HEADER' | translate}}
4 |
5 | p
6 | a.btn.btn-block.btn-lg.btn-primary(ui-sref="auth") {{'LAVALOGIN.SIGNUP.LOGIN' | translate}}
7 |
8 | p
9 | a.btn.btn-block.btn-lg.btn-default(ui-sref="invite") {{'LAVALOGIN.SIGNUP.SIGNUP' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaMail/decorators/taOptions.js:
--------------------------------------------------------------------------------
1 | module.exports = ($delegate, taRegisterTool, textAngularHelpers) => {
2 | taRegisterTool('submit', {
3 | iconclass: 'fa fa-bold',
4 | tooltiptext: 'submit',
5 | action: function(){
6 | if (textAngularHelpers.ctrlEnterCallback)
7 | textAngularHelpers.ctrlEnterCallback();
8 | },
9 | commandKeyCode: 10
10 | });
11 |
12 | $delegate.toolbar[1].push('submit');
13 |
14 | return $delegate;
15 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LavaUtils",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "accept-language-parser": "^1.0.2",
13 | "babel-core": "^5.4.4",
14 | "levenshtein": "^1.0.5",
15 | "setimmediate": "^1.0.2"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/index.toml:
--------------------------------------------------------------------------------
1 | [MANIFEST]
2 | version = "1.0"
3 |
4 | [APPLICATION]
5 | name = "LavaLogin"
6 | version = "1.0"
7 | description = "Lava login/signup application"
8 | type = "angular"
9 |
10 | moduleName = "LavaLogin"
11 |
12 | dependencies = [
13 | "LavaUtils",
14 | "lavaboom.api",
15 | "ui.router",
16 | "pascalprecht.translate",
17 | "ngMessages",
18 | "angular-co",
19 | "ngAutodisable"
20 | ]
21 |
22 | productionOnlyDependencies = [
23 | ]
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/invite.jade:
--------------------------------------------------------------------------------
1 | form.ng-cloak(ng-class="{'errors': currentErrorMessage}")
2 | img.login-logo(src="#{resolveAsset('/img/Lavaboom-logo-no-shadow.svg')}")
3 | p.lead {{'LAVALOGIN.INVITE.TITLE' | translate}}
4 |
5 | p
6 | a.btn.btn-block.btn-lg.btn-default(ui-sref="secureUsername") {{'LAVALOGIN.INVITE.BTN_SEND_ME_INVITE' | translate}}
7 | p
8 | a.btn.btn-block.btn-lg.btn-primary(ui-sref="verifyInvite") {{'LAVALOGIN.INVITE.BTN_I_HAVE_INVITE_CODE' | translate}}
9 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/importKey.jade:
--------------------------------------------------------------------------------
1 | form.ng-cloak(ng-class="{'errors': currentErrorMessage}")
2 | p.lead {{'LAVALOGIN.IMPORT_KEY.TITLE' | translate}}
3 |
4 | div.login-alert.alert
5 | p.big.xtra {{'LAVALOGIN.IMPORT_KEY.LB_INFO1' | translate}}
6 | p.big.xtra {{'LAVALOGIN.IMPORT_KEY.LB_INFO2' | translate}}
7 | p.big.xtra.pack {{'LAVALOGIN.IMPORT_KEY.LB_INFO3' | translate}}
8 | footer
9 | button.btn.btn-block.btn-lg.btn-primary {{'LAVALOGIN.IMPORT_KEY.BTN_UPLOAD_KEY' | translate}}
--------------------------------------------------------------------------------
/src/img/assets/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | #000000
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/directives/customDropZone.js:
--------------------------------------------------------------------------------
1 | module.exports = ($timeout, $state) => ({
2 | restrict : 'A',
3 | scope: {
4 | eventFilter: '&'
5 | },
6 | link: (scope, elem) => {
7 | window.addEventListener('dragover', e => {
8 | if (!scope.eventFilter({name: 'dragover', event: e}))
9 | e.preventDefault();
10 | }, false);
11 | window.addEventListener('drop', e => {
12 | if (!scope.eventFilter({name: 'drop', event: e}))
13 | e.preventDefault();
14 | }, false);
15 | }
16 | });
--------------------------------------------------------------------------------
/src/apps/LavaMail/directives/openRawFile.js:
--------------------------------------------------------------------------------
1 | module.exports = ($compile, fileReader) => ({
2 | restrict : 'A',
3 | scope: {
4 | openRawFile: '&',
5 | openRawError: '&'
6 | },
7 | link : (scope, el, attrs) => {
8 | scope.getFile = (file) => {
9 | scope.openRawFile({file});
10 | };
11 |
12 | const buttonEl = angular.element('');
13 | $compile(buttonEl)(scope);
14 |
15 | el.bind('click', () => buttonEl[0].click());
16 | }
17 | });
--------------------------------------------------------------------------------
/src/less/contacts.less:
--------------------------------------------------------------------------------
1 | #contacts-list{
2 | .list-group-item{
3 | > a {
4 | line-height: 4.5rem;
5 | padding: 0 0 0 1.5rem;
6 | .sec-icon{
7 | .lava-icon-22();
8 | &.icon-unlock{
9 | color: @lava-insecure;
10 | }
11 | &.icon-star{
12 | color: @lava-insecure;
13 | }
14 | }
15 | }
16 | &.active{
17 | .sec-icon{
18 | color: #FFF !important;
19 | }
20 | }
21 | }
22 | }
23 |
24 | .contact-name{
25 | .no-top();
26 | padding: 0 !important;
27 | }
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/filesize.js:
--------------------------------------------------------------------------------
1 | module.exports = () =>
2 | size => {
3 | if (isNaN(size))
4 | size = 0;
5 |
6 | if (size < 1024)
7 | return size + ' Bytes';
8 |
9 | size /= 1024;
10 |
11 | if (size < 1024)
12 | return size.toFixed(2) + ' Kb';
13 |
14 | size /= 1024;
15 |
16 | if (size < 1024)
17 | return size.toFixed(2) + ' Mb';
18 |
19 | size /= 1024;
20 |
21 | if (size < 1024)
22 | return size.toFixed(2) + ' Gb';
23 |
24 | size /= 1024;
25 |
26 | return size.toFixed(2) + ' Tb';
27 | };
--------------------------------------------------------------------------------
/gulp/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | isProduction: false,
3 | isDebugable: true,
4 | isLogs: true,
5 | defaultApiUri: 'https://api.lavaboom.com',
6 | contribPluginsBaseUrl: 'https://github.com/lavab-plugins/',
7 | coreAppNames: ['LavaLoader', 'LavaUtils', 'LavaLogin', 'LavaMail'],
8 | defaultLanguageCode: 'en',
9 | defaultRootDomain: 'lavaboom.com',
10 | livereloadListenAddress: '0.0.0.0',
11 | livereloadListenPort: 35729,
12 | listenAddress: '0.0.0.0',
13 | listenPort: 5000,
14 | nodeVersion: '>=0.10.35'
15 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/decorators/$templateCache.js:
--------------------------------------------------------------------------------
1 | module.exports = ($delegate, $q, $http) => {
2 | $delegate.fetch = (url) => {
3 | let deferred = $q.defer();
4 |
5 | let template = $delegate.get(url);
6 | if (template)
7 | deferred.resolve(template);
8 | else {
9 | $http.get(url)
10 | .then(result => {
11 | $delegate.put(url, result.data);
12 | deferred.resolve(result.data);
13 | })
14 | .catch(err => deferred.reject(err));
15 | }
16 |
17 | return deferred.promise;
18 | };
19 | return $delegate;
20 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/decorators/dialogs.js:
--------------------------------------------------------------------------------
1 | module.exports = ($delegate, router) => {
2 | let confirm = $delegate.confirm;
3 | let create = $delegate.create;
4 |
5 | $delegate.confirm = (...args) => {
6 | console.log('dialogs confirm');
7 |
8 | let r = confirm(...args);
9 | router.registerDialog(r.result);
10 | return r;
11 | };
12 |
13 | $delegate.create = (...args) => {
14 | console.log('dialogs create');
15 | let r = create(...args);
16 | router.registerDialog(r.result);
17 | return r;
18 | };
19 |
20 | return $delegate;
21 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/lavaday.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, $translate, $filter) => {
2 |
3 |
4 | return (input) => {
5 | var yesterday = new Date((new Date()).valueOf() - 1000*60*60*24).toDateString();
6 | var today = (new Date()).toDateString();
7 | var inputString = (new Date(input)).toDateString();
8 | if(inputString == today){
9 | return 'Today';
10 | } else if (inputString == yesterday) {
11 | return 'Yesterday';
12 | } else {
13 | return $filter('date')(new Date(input), 'dd/M/yyyy');
14 | }
15 |
16 | };
17 | };
--------------------------------------------------------------------------------
/src/tests/helpers/intervalDigest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * INFO: dirty hack to over come angular-co wrapper
3 | * that doesn't give hook on generator resolve before digest
4 | */
5 |
6 | let rootScopeUpdateIntervalId;
7 | module.exports = {
8 | start: (interval = 1000)=>
9 | inject(($rootScope) =>
10 | rootScopeUpdateIntervalId = setInterval(() => {
11 | console.info('...still waiting for crypto response');
12 | $rootScope.$digest();
13 | }, interval)
14 | ),
15 | stop: () =>
16 | () =>
17 | clearInterval(rootScopeUpdateIntervalId)
18 | };
--------------------------------------------------------------------------------
/src/apps/LavaLogin/directives/maxRepeating.js:
--------------------------------------------------------------------------------
1 | module.exports = () => ({
2 | require: 'ngModel',
3 | link: (scope, elem, attrs, ngModel) => {
4 | const r = attrs.maxRepeating;
5 | ngModel.$parsers.unshift(value => {
6 | let isValid = true;
7 | value.split('').reduce((a, c) => {
8 | if (a && c == a.pc) {
9 | a.count++;
10 | if (a.count > r)
11 | isValid = false;
12 | }
13 | else a = {pc: c, count: 0};
14 |
15 | return a;
16 | }, null);
17 | ngModel.$setValidity('maxRepeating', isValid);
18 | return value;
19 | });
20 | }
21 | });
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/lavadate.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, $translate, $filter) => {
2 |
3 |
4 | return (input) => {
5 | var yesterday = new Date((new Date()).valueOf() - 1000*60*60*24).toDateString();
6 | var today = (new Date()).toDateString();
7 | var inputString = (new Date(input)).toDateString();
8 | if(inputString == today){
9 | return $filter('date')(new Date(input), 'HH:mm');
10 | } else if (inputString == yesterday) {
11 | return 'Yesterday';
12 | } else {
13 | return $filter('date')(new Date(input), 'dd/M/yyyy');
14 | }
15 |
16 | };
17 | };
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/reservedUsername.jade:
--------------------------------------------------------------------------------
1 | form.ng-cloak(ng-class="{'errors': currentErrorMessage}")
2 | h1 {{'LAVALOGIN.SECURED.TITLE' | translate}}
{{'LAVALOGIN.SECURED.SUB_TITLE' | translate}}
3 |
4 | hr.spacer
5 |
6 | p.text-center.big {{'LAVALOGIN.SECURED.LB_EMAIL_SENT' | translate}}
7 | p.big.highlight.text-center(ng-bind="email")
8 |
9 | hr.spacer.large
10 |
11 | p.text-center {{'LAVALOGIN.SECURED.LB_COMPLETE' | translate}}
12 |
13 | footer
14 | button.btn.btn-block.btn-lg.btn-primary(ui-sref="verifyInvite") {{'LAVALOGIN.SECURED.BTN_COMPLETE' | translate}}
--------------------------------------------------------------------------------
/src/less/mixins.less:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------------------------------
3 |
4 | // Utilities
5 | @import "mixins/type.less";
6 | // @import "mixins/buttons.less";
7 | // @import "mixins/icons.less";
8 | // @import "mixins/modals.less";
9 | // @import "mixins/reset-filter.less";
10 | // @import "mixins/resize.less";
11 | // @import "mixins/responsive-visibility.less";
12 | // @import "mixins/size.less";
13 | // @import "mixins/tab-focus.less";
14 | // @import "mixins/text-emphasis.less";
15 | // @import "mixins/text-overflow.less";
16 | // @import "mixins/vendor-prefixes.less";
17 |
--------------------------------------------------------------------------------
/src/less/lavaboom-icons.less:
--------------------------------------------------------------------------------
1 | @import (less) "../fonts/style.css";
2 |
3 | .lava-icon, .fa {
4 | font-family: 'lavaboom';
5 | speak: none;
6 | font-style: normal;
7 | font-weight: normal;
8 | font-variant: normal;
9 | text-transform: none;
10 | line-height: 1;
11 |
12 | /* Better Font Rendering =========== */
13 | -webkit-font-smoothing: antialiased;
14 | -moz-osx-font-smoothing: grayscale;
15 | }
16 |
17 | .lava-icon-22{
18 | font-size: 2.2rem;
19 | line-height: 2.2rem;
20 | padding: 1.1rem;
21 | }
22 |
23 | .lava-icon-44{
24 | font-size: 4.4rem;
25 | line-height: 4.4rem;
26 | padding: 1.1rem;
27 | }
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlVerify.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $state, $stateParams, co, user, signUp) => {
2 | const userName = $stateParams.userName;
3 | const inviteCode = $stateParams.inviteCode;
4 |
5 | $scope.isUsernameDefined = signUp.reserve ? true : false;
6 | $scope.form = {
7 | username: userName ? userName : (signUp.reserve ? signUp.reserve.originalUsername : ''),
8 | token: inviteCode ? inviteCode : '',
9 | isNews: true
10 | };
11 |
12 | $scope.requestVerify = () => co(function *(){
13 | yield signUp.verifyInvite($scope.form.username, $scope.form.token, $scope.form.isNews);
14 | yield $state.go('plan');
15 | });
16 | };
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esnext": true,
3 | "noyield": true,
4 | "indent": 4,
5 | "maxdepth": 5,
6 | "maxlen": 160,
7 | "maxstatements": 75,
8 | "newcap": true,
9 | "noempty": true,
10 | "nonbsp": true,
11 | "nonew": true,
12 | "quotmark": "single",
13 | "browserify": true,
14 | "globals": {
15 | "_": true,
16 | "angular": true,
17 | "openpgp": true,
18 | "saveAs": true,
19 | "Lavaboom": true,
20 | "Symbol": true,
21 | "loader": true,
22 | "checker": true,
23 |
24 | "assets": true,
25 | "primaryApplicationName": true,
26 | "console": true
27 | },
28 | "undef": true,
29 | "latedef": true,
30 | "-W002": false,
31 | "-W014": false
32 | }
--------------------------------------------------------------------------------
/src/apps/LavaMail/directives/emailContextMenu.js:
--------------------------------------------------------------------------------
1 | module.exports = ($templateCache, $compile, co, contacts, user) => ({
2 | restrict : 'E',
3 | scope: {
4 | isOpen: '=',
5 | noReply: '=',
6 | email: '='
7 | },
8 | link : (scope, el, attrs) => {
9 | co(function *(){
10 | const template = yield $templateCache.fetch('LavaMail/directives/emailContextMenu');
11 | const contact = contacts.getContactByEmail(scope.email);
12 | scope.isExistingContact = !!contact;
13 |
14 | if (scope.isExistingContact && scope.noReply)
15 | return;
16 |
17 | const compiledTemplate = $compile(template)(scope);
18 | el.prepend(compiledTemplate);
19 | });
20 | }
21 | });
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlPassword.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $state, signUp, co, crypto, user) => {
2 | if (!signUp.isPartiallyFlow && (!signUp.tokenSignup || !signUp.details))
3 | $state.go('login');
4 |
5 | $scope.isPartiallyFlow = signUp.isPartiallyFlow;
6 |
7 | $scope.form = {
8 | password: '',
9 | passwordConfirm: '',
10 | isPrivateComputer: false
11 | };
12 |
13 | $scope.updatePassword = () => co(function *(){
14 | if (signUp.isPartiallyFlow)
15 | signUp.password = $scope.form.password;
16 | else {
17 | yield signUp.setup($scope.form.password, $scope.form.isPrivateComputer);
18 | }
19 |
20 | yield $state.go('generateKeys');
21 | });
22 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/directives/openFile.js:
--------------------------------------------------------------------------------
1 | module.exports = ($compile, fileReader) => ({
2 | restrict : 'A',
3 | scope: {
4 | openFile: '&',
5 | openError: '&'
6 | },
7 | link : (scope, el, attrs) => {
8 | scope.getFile = (file) => {
9 | fileReader.readAsText(file)
10 | .then(data => {
11 | scope.openFile({data: data});
12 | })
13 | .catch(error => {
14 | if (scope.openError)
15 | scope.openError(error);
16 | });
17 | };
18 |
19 | const buttonEl = angular.element('');
20 | $compile(buttonEl)(scope);
21 |
22 | el.bind('click', () => buttonEl[0].click());
23 | }
24 | });
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/inbox/download.jade:
--------------------------------------------------------------------------------
1 | div
2 | form.lava-modal-header
3 | .filters.row.no-gutter.lava-icon-row.z1
4 | span.pull-left.modal-heading {{'LAVAMAIL.INBOX.DOWNLOAD.TITLE' | translate}}
5 | nav.navbar.navbar-inverse(role="navigation")
6 | div
7 | ul.nav.navbar-nav.navbar-right
8 | li
9 | button.btn.btn-default(ng-click="hidePopup()")
10 | span.icon-close
11 | .row.lava-modal-content
12 | div.col-xs-12.col-xs-offset-6
13 | hr.spacer
14 | .progress
15 | .progress-bar(role='progressbar', aria-valuenow='{{progress}}', aria-valuemin='0', aria-valuemax='100', style='width: {{progress}}%;')
16 | p.text-center(ng-bind="label")
17 | hr.spacer
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/misc/lsOff.jade:
--------------------------------------------------------------------------------
1 | .modal-content
2 | .modal-header.dialog-header-confirm.ng-scope
3 | button.close(type='button', ng-click='no()') ×
4 | h4.modal-title.ng-binding
5 | span.glyphicon.glyphicon-check
6 | | {{'LAVAMAIL.SETTINGS.SECURITY.TITLE_CONFIRM' | translate}}
7 | .modal-body.ng-binding.ng-scope {{'LAVAMAIL.SETTINGS.SECURITY.LB_CONFIRM_LS_DISABLE' | translate}}
8 | .modal-footer.ng-scope
9 | button.btn.btn-default.ng-binding(type='button', ng-click='yes()') {{'LAVAMAIL.SETTINGS.SECURITY.LB_CONFIRM_LS_DISABLE_YES' | translate}}
10 | button.btn.btn-primary.ng-binding(type='button', ng-click='no()') {{'LAVAMAIL.SETTINGS.SECURITY.LB_CONFIRM_LS_DISABLE_NO' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaMail/filters/members.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, $translate, contacts) => {
2 | const translations = {
3 | LB_AND_ONE_OTHER : '',
4 | LB_AND_TWO_OTHERS : '',
5 | LB_AND_OTHERS : ''
6 | };
7 |
8 | $translate.bindAsObject(translations, 'LAVAMAIL.INBOX');
9 |
10 | return (membersList) => {
11 | let membersString = membersList.slice(0, 2).join(', ');
12 | if (membersList.length == 3)
13 | membersString += ' ' + translations.LB_AND_ONE_OTHER;
14 | else if (membersList.length == 4)
15 | membersString += ' ' + translations.LB_AND_TWO_OTHERS;
16 | else if (membersList.length > 4)
17 | membersString += ' ' + translations.LB_AND_OTHERS;
18 |
19 | return membersString;
20 | };
21 | };
--------------------------------------------------------------------------------
/src/img/check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
--------------------------------------------------------------------------------
/src/img/avatar.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/decorators/$sanitize.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | module.exports = ($delegate, loader, utils) => {
4 | let rexExp = /["'](data:image\/[^"']+)["']/ig;
5 |
6 | function sanitizer (html) {
7 | let dataUris = {};
8 |
9 | function urlX(url) {
10 | if (dataUris[url])
11 | return dataUris[url];
12 | return url;
13 | }
14 |
15 | function idX(id) {
16 | return id;
17 | }
18 |
19 | html = html.replace(rexExp, function(match, url) {
20 | let key = `http://${utils.getRandomString()}/`;
21 | dataUris[key] = url;
22 | return key;
23 | });
24 |
25 | return window.html_sanitize(html, urlX, idX);
26 | }
27 |
28 | sanitizer.enableCss = () => {
29 | return loader.loadJS('/js/vendor/LavaUtils/html-css-sanitizer.js');
30 | };
31 |
32 | return sanitizer;
33 | };
34 |
--------------------------------------------------------------------------------
/gulp/browserifyNgAnnotateHelper.js:
--------------------------------------------------------------------------------
1 | var through = require('through');
2 | var jade = require('jade');
3 |
4 | module.exports = function (fileName, options) {
5 | const r1 = /module\.exports\s*=\s*\(/, r2 = /module\.exports\s*=\s*function\s*\(/i;
6 |
7 | if (!fileName.endsWith('.js') || !options.mustInclude.some(e => fileName.includes(e))) {
8 | return through();
9 | }
10 |
11 | var inputString = '';
12 | return through(
13 | function (chunk) {
14 | inputString += chunk;
15 | },
16 | function () {
17 | var self = this;
18 |
19 | let r = inputString
20 | .replace(r1, 'module.exports = /*@ngInject*/ (')
21 | .replace(r2, 'module.exports = /*@ngInject*/ function(');
22 |
23 | console.log('ngInject added to', fileName);
24 |
25 | self.queue(r);
26 | self.queue(null);
27 | }
28 | );
29 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/misc/hotkeys.jade:
--------------------------------------------------------------------------------
1 | .cfp-hotkeys(ng-class="{ mac : (navigator.userAgent.indexOf('Mac OS X') != -1)}")
2 | h4.cfp-hotkeys-title {{ 'LAVAMAIL.HOTKEY.TITLE_GLOBAL' | translate }}
3 | .key-row(ng-repeat='hotkey in globalHotkeys')
4 | .cfp-hotkeys-keys
5 | span.cfp-hotkeys-key.label.label-default(ng-repeat='key in hotkey.combo track by $index') {{ hotkey.require }} {{ key }}
6 | .cfp-hotkeys-text {{ hotkey.description }}
7 | h4.cfp-hotkeys-title {{ 'LAVAMAIL.HOTKEY.TITLE_LOCAL' | translate }}
8 | .key-row(ng-repeat='hotkey in localHotkeys')
9 | .cfp-hotkeys-keys
10 | span.cfp-hotkeys-key.label.label-default(ng-repeat='key in hotkey.combo track by $index') {{ hotkey.require }} {{ key }}
11 | .cfp-hotkeys-text {{ hotkey.description }}
12 | .cfp-hotkeys-close(ng-click='hidePopup()')
13 | span.icon-close
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlAuth.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $rootScope, $window, $interval, co, user, loader, utils) => {
2 | let signInSettings = utils.def(() => JSON.parse(localStorage['sign-in-settings']), null);
3 |
4 | $scope.form = {
5 | username: '',
6 | password: '',
7 | isPrivateComputer: signInSettings ? signInSettings.isPrivateComputer : false
8 | };
9 |
10 | $scope.$watch('form', (o, n) => {
11 | localStorage['sign-in-settings'] = JSON.stringify({
12 | isPrivateComputer: $scope.form.isPrivateComputer
13 | });
14 | }, true);
15 |
16 | $scope.logIn = () => co(function *(){
17 | yield user.signIn($scope.form.username, $scope.form.password, $scope.form.isPrivateComputer, $scope.form.isPrivateComputer);
18 |
19 | loader.resetProgress();
20 | loader.showLoader(true);
21 | loader.loadMainApplication();
22 | });
23 | };
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlDetails.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $state, co, user, signUp) => {
2 | if (!signUp.tokenSignup || !signUp.plan)
3 | $state.go('invite');
4 |
5 | $scope.form = signUp.details ? signUp.details : {
6 | firstName: '',
7 | lastName: '',
8 | displayName: ''
9 | };
10 |
11 | $scope.$watchGroup(['form.firstName', 'form.lastName'], () => {
12 | let firstName = $scope.form.firstName ? $scope.form.firstName.trim() : '';
13 | let lastName = $scope.form.lastName ? $scope.form.lastName.trim() : '';
14 | let autoDisplayName = `${firstName} ${lastName}`;
15 |
16 | $scope.form.displayName = firstName || lastName ? autoDisplayName : $scope.form.displayName;
17 | });
18 |
19 | $scope.requestDetailsUpdate = () => co(function *(){
20 | signUp.details = $scope.form;
21 |
22 | yield $state.go('choosePasswordIntro');
23 | });
24 | };
25 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/inbox/forwardedEmail.jade:
--------------------------------------------------------------------------------
1 | section.email
2 | section.body(ng-bind-html="body")
3 | footer.signature(ng-bind-html="signature")
4 | section.replied-to(ng-repeat="email in forwardEmails")
5 | blockquote {{'LAVAMAIL.INBOX.FORWARD_HEADER' | translate}}
6 | div {{'LAVAMAIL.INBOX.FORWARD_FROM' | translate}}:
7 | span.medium {{email.fromAllPretty}}
8 | div {{'LAVAMAIL.INBOX.FORWARD_DATE' | translate}}: {{email.date | date}}
9 | div {{'LAVAMAIL.INBOX.FORWARD_SUBJECT' | translate}}: {{email.subject}}
10 | div {{'LAVAMAIL.INBOX.FORWARD_TO' | translate}}: {{email.toPretty}}
11 | div(ng-if="email.cc && email.cc.length > 0")
12 | span {{'LAVAMAIL.INBOX.FORWARD_CC' | translate}}: {{email.ccPretty}}
13 | div(ng-if="email.bcc && email.bcc.length > 0")
14 | span {{'LAVAMAIL.INBOX.FORWARD_BCC' | translate}}: {{ email.bccPretty}}
15 | section(ng-bind-html="email.body.data")
--------------------------------------------------------------------------------
/src/apps/LavaUtils/decorators/$timeout.js:
--------------------------------------------------------------------------------
1 | module.exports = ($delegate, $q) => {
2 | $delegate.schedule = (timeoutVariable, action, timeout, invokeApply) => {
3 | if (timeoutVariable)
4 | $delegate.cancel(timeoutVariable);
5 | return $delegate(action, timeout, invokeApply);
6 | };
7 |
8 | $delegate.schedulePromise = (timeoutVariable, action, timeout, invokeApply) => {
9 | if (timeoutVariable)
10 | $delegate.cancel(timeoutVariable);
11 | const deferred = $q.defer();
12 |
13 | const t = $delegate(() => {
14 | try {
15 | const r = action();
16 |
17 | if (r.then) {
18 | r
19 | .then(res => deferred.resolve(res))
20 | .catch(err => deferred.reject(err));
21 | } else
22 | deferred.resolve(r);
23 | } catch (err) {
24 | deferred.reject(err);
25 | }
26 | }, timeout, invokeApply);
27 |
28 | return [t, deferred.promise];
29 | };
30 |
31 | return $delegate;
32 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/services/loader.js:
--------------------------------------------------------------------------------
1 | module.exports = function($q) {
2 | let loader = window.loader;
3 |
4 | this.loadJS = (src, isReload = false) => {
5 | return $q.when(loader.loadJS(src, isReload));
6 | };
7 |
8 | this.loadMainApplication = (opts) => {
9 | loader.loadMainApplication(opts);
10 | };
11 |
12 | this.loadLoginApplication = (opts) => {
13 | loader.loadLoginApplication(opts);
14 | };
15 |
16 | this.resetProgress = () => {
17 | loader.resetProgress();
18 | };
19 |
20 | this.setProgress = (text, progress) => {
21 | loader.setProgress(text, progress);
22 | };
23 |
24 | this.incProgress = (text, progress) => {
25 | loader.incProgress(text, progress);
26 | };
27 |
28 | this.getProgress = () => loader.getProgress();
29 |
30 | this.showLoader = (isImmediate = false) => {
31 | loader.showLoader(isImmediate);
32 | };
33 |
34 | this.isMainApplication = () => loader.isMainApplication();
35 | };
--------------------------------------------------------------------------------
/src/apps/LavaLogin/directives/dictionary.js:
--------------------------------------------------------------------------------
1 | module.exports = ($http, $q) => {
2 | const Levenshtein = window.Levenshtein;
3 | const words = {};
4 |
5 | return {
6 | require: 'ngModel',
7 | link: (scope, elem, attrs, ngModel) => {
8 | const dictionary = attrs.dictionary;
9 | const minLevenshteinDistance = attrs.minLevenshteinDistance;
10 |
11 | ngModel.$asyncValidators.dictionary = (modelValue, viewValue) => {
12 | if (!words[dictionary])
13 | words[dictionary] = $http.get(dictionary)
14 | .then(r => r.data.split('\n'));
15 |
16 | return words[dictionary]
17 | .then(words => {
18 | for (let word of words) {
19 | word = word.trim();
20 | let levenshtein = new Levenshtein(word, viewValue);
21 | if (levenshtein.distance < minLevenshteinDistance)
22 | return $q.reject(false);
23 | }
24 |
25 | return true;
26 | });
27 | };
28 | }
29 | };
30 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webclient",
3 | "version": "0.0.0",
4 | "homepage": "https://github.com/lavab/webclient",
5 | "authors": [
6 | {
7 | "name": "let4be",
8 | "url": "https://github.com/let4be"
9 | }
10 | ],
11 | "license": "MIT",
12 | "private": true,
13 | "ignore": [
14 | "**/.*",
15 | "node_modules",
16 | "bower_components",
17 | "test",
18 | "tests"
19 | ],
20 | "dependencies": {
21 | "angular-ui-select": "https://github.com/lavab/ui-select/archive/master.tar.gz",
22 | "ngInfiniteScroll": "~1.2.0",
23 | "angular-hotkeys": "chieffancypants/angular-hotkeys#~1.4.5",
24 | "angular-timeago": "~0.1.9",
25 | "angular-dialog-service": "~5.2.6",
26 | "angular-bootstrap": "~0.13.0",
27 | "textAngular": "~1.4.1",
28 | "papaparse": "~4.1.1"
29 | },
30 | "resolutions": {
31 | "angular-bootstrap": "~0.13.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/choosePasswordIntro.jade:
--------------------------------------------------------------------------------
1 | form.steps-form.ng-cloak(ng-class="{'errors': currentErrorMessage}")
2 | p.lead.pack {{'LAVALOGIN.CHOOSE_PASSWORD_INTRO.TITLE' | translate}}
3 |
4 | p.text-center.steps
5 | span.icon-unread.active
6 | span.icon-unread.active
7 | span.icon-unread.active
8 | span.icon-unread.active
9 | span.icon-unread
10 | span.icon-unread
11 | span.icon-unread
12 |
13 | div.login-alert.alert
14 | header
15 | h1 {{'LAVALOGIN.CHOOSE_PASSWORD_INTRO.LB_WARNING_TITLE' | translate}}
16 | p.big.xtra(ng-show="isPartiallyFlow") {{'LAVALOGIN.CHOOSE_PASSWORD_INTRO.LB_WARNING0' | translate}}
17 | p.big.xtra {{'LAVALOGIN.CHOOSE_PASSWORD_INTRO.LB_WARNING1' | translate}}
18 | p.big {{'LAVALOGIN.CHOOSE_PASSWORD_INTRO.LB_WARNING2' | translate}}
19 |
20 | footer
21 | button.btn.btn-block.btn-lg.btn-primary(ui-sref="choosePassword") {{'LAVALOGIN.CHOOSE_PASSWORD_INTRO.BTN_NEXT' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlSettingsSecurityKey.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $timeout, $translate, co, consts, crypto, notifications) => {
2 | console.log('CtrlSettingsSecurityKey instantiated with key', $scope.key);
3 |
4 | const translations = {
5 | LB_PRIVATE_KEY_DECRYPTED: '%'
6 | };
7 | $translate.bindAsObject(translations, 'LAVAMAIL.SETTINGS.SECURITY');
8 |
9 | $scope.$watch('key.decryptPassword', (o, n) => {
10 | if (o == n)
11 | return;
12 |
13 | if ($scope.key) {
14 | co(function *(){
15 | if (yield $scope.key.decrypt($scope.key.decryptPassword)) {
16 | notifications.unSetByKind('crypto');
17 |
18 | notifications.set('private-key-decrypted-ok', {
19 | text: translations.LB_PRIVATE_KEY_DECRYPTED({email: $scope.email}),
20 | type: 'info',
21 | timeout: 3000,
22 | namespace: 'settings',
23 | kind: 'crypto'
24 | });
25 | }
26 | });
27 | }
28 | });
29 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/settings/settingsPlan.jade:
--------------------------------------------------------------------------------
1 | .row.settings-panel.ng-cloak(ng-controller="CtrlSettingsPlan")
2 | form.settings.col-xs-22.col-xs-offset-1.col-sm-20.col-sm-offset-2.col-md-12.col-md-offset-6.col-xs-22.col-xs-offset-1
3 | .form-wrap(ng-repeat="name in planList", title="{{plans[name].hoverTitle}}")
4 | h4 {{plans[name].title}}
5 | .price-tag(ng-show="plans[name].tag")
6 | a(href="{{plans[name].tagHref}}", target="_blank", title="{{plans[name].tagTitle}}", ng-show="plans[name].tagIsLink")
7 | span.text {{plans[name].tag}}
8 | span.text(ng-show="!plans[name].tagIsLink") {{plans[name].tag}}
9 | span.caret
10 | ul.list-group.suppress-borders
11 | li.list-group-item(ng-repeat="item in plans[name].items", ng-bind-html="item")
12 | li.list-group-item(ng-show="name == currentPlanName")
13 | hr
14 | | {{'LAVAMAIL.SETTINGS.PLAN.LB_CURRENT' | translate}}
15 | span.icon.icon-tick.pull-right.icon-circle
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:trusty
2 |
3 | MAINTAINER Piotr Zduniak
4 |
5 | ENV DEBIAN_FRONTEND noninteractive
6 |
7 | RUN rm /bin/sh && ln -s /bin/bash /bin/sh
8 |
9 | RUN apt-get update && apt-get upgrade -y && apt-get install -y nginx curl
10 | RUN apt-get install -y git
11 | RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.25.4/install.sh | bash
12 | RUN . /root/.nvm/nvm.sh && nvm install 0.12
13 | RUN . /root/.nvm/nvm.sh && nvm use 0.12 && npm install gulpjs/gulp-cli#4.0 -g
14 | RUN . /root/.nvm/nvm.sh && nvm use 0.12 && npm install bower -g
15 |
16 | RUN rm /etc/nginx/sites-enabled/default
17 | COPY ./website.conf /etc/nginx/sites-enabled/default
18 |
19 | COPY . /tmp/build
20 | RUN . /root/.nvm/nvm.sh && nvm use 0.12 && cd /tmp/build && npm install && source ./prepare-env.sh && gulp production
21 | RUN rm -f /var/www && mv /tmp/build/dist /var/www
22 |
23 | CMD ["nginx", "-g", "daemon off;"]
24 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlNavigation.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, $scope, $state, $translate, co, inbox, user, hotkey) => {
2 | $scope.$state = $state;
3 | $scope.name = user.styledName;
4 |
5 | $scope.labelTranslations = {
6 | INBOX: '',
7 | SENT: '',
8 | STARRED: '',
9 | SPAM: '',
10 | TRASH: '',
11 | DRAFTS: ''
12 | };
13 |
14 | $translate.bindAsObject($scope.labelTranslations, 'LAVAMAIL.LABEL');
15 |
16 | co(function *(){
17 | $scope.labels = (yield inbox.getLabels()).list;
18 | });
19 |
20 | $scope.getThreadForLabel = labelName => inbox.selectedTidByLabelName[labelName] ? inbox.selectedTidByLabelName[labelName] : null;
21 |
22 | $scope.$on('inbox-labels', (e, labels) => {
23 | $scope.labels = labels.list;
24 | });
25 |
26 | $scope.isActive = (multiKey) => hotkey.isActive(multiKey);
27 |
28 | $scope.getMultiComboPrettified = (multiKey, name) => hotkey.getMultiComboPrettified(multiKey, name);
29 |
30 | $scope.logout = () => {
31 | user.logout();
32 | };
33 | };
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var args = process.argv.slice(2);
3 | var target = process.env.TARGET;
4 |
5 | if (target == 'run' || args.length > 0) {
6 | global.gulp = gulp;
7 | global.plg = require('gulp-load-plugins')({
8 | pattern: ['gulp-*', 'gulp.*'],
9 | replaceString: /\bgulp[\-.]/
10 | });
11 | require('toml-require').install();
12 | require('babel/register');
13 | require('./gulpfile-es6.js');
14 | }
15 | else
16 | {
17 | var nodemon = require('gulp-nodemon');
18 | var console = require('better-console');
19 | var exec = require('child_process').exec;
20 |
21 | gulp.task('default', function (){
22 | exec('which gulp', function (error, stdout, stderr) {
23 | nodemon({
24 | script: stdout.trim(),
25 | watch: ['gulpfile.js', 'gulpfile-es6.js', 'serve.js', 'gulp/*'],
26 | env: {'TARGET': 'run'}
27 | })
28 | .on('start', function() {
29 | console.clear();
30 | })
31 | .on('restart', function() {
32 | console.clear();
33 | });
34 | });
35 | });
36 | }
37 |
--------------------------------------------------------------------------------
/src/fonts/demo-files/demo.js:
--------------------------------------------------------------------------------
1 | if (!('boxShadow' in document.body.style)) {
2 | document.body.setAttribute('class', 'noBoxShadow');
3 | }
4 |
5 | document.body.addEventListener("click", function(e) {
6 | var target = e.target;
7 | if (target.tagName === "INPUT" &&
8 | target.getAttribute('class').indexOf('liga') === -1) {
9 | target.select();
10 | }
11 | });
12 |
13 | (function() {
14 | var fontSize = document.getElementById('fontSize'),
15 | testDrive = document.getElementById('testDrive'),
16 | testText = document.getElementById('testText');
17 | function updateTest() {
18 | testDrive.innerHTML = testText.value || String.fromCharCode(160);
19 | if (window.icomoonLiga) {
20 | window.icomoonLiga(testDrive);
21 | }
22 | }
23 | function updateSize() {
24 | testDrive.style.fontSize = fontSize.value + 'px';
25 | }
26 | fontSize.addEventListener('change', updateSize, false);
27 | testText.addEventListener('input', updateTest, false);
28 | testText.addEventListener('change', updateTest, false);
29 | updateSize();
30 | }());
31 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlSettingsList.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, $scope, $state, router, hotkey) => {
2 | const settingsList = ['main.settings.general', 'main.settings.profile', 'main.settings.security', 'main.settings.plan'];
3 |
4 | {
5 | const goSettings = (event, delta) => {
6 | event.preventDefault();
7 |
8 | let i = settingsList.findIndex(s => s == $state.current.name);
9 | if (i > -1) {
10 | i = i + delta;
11 | if (i < 0)
12 | i = settingsList.length + i;
13 | if (i > settingsList.length - 1)
14 | i = settingsList.length - i;
15 | $state.go(settingsList[i]);
16 | }
17 | };
18 |
19 | hotkey.registerCustomHotkeys($scope, [
20 | {
21 | combo: ['h', 'k', 'left', 'up'],
22 | description: 'LAVAMAIL.HOTKEY.MOVE_UP',
23 | callback: (event, key) => goSettings(event, -1)
24 | },
25 | {
26 | combo: ['j', 'l', 'right', 'down'],
27 | description: 'LAVAMAIL.HOTKEY.MOVE_DOWN',
28 | callback: (event, key) => goSettings(event, +1)
29 | }
30 | ], {scope: 'ctrlSettingsList', addedFromState: 'main.settings'});
31 | }
32 | };
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/generateKeys.jade:
--------------------------------------------------------------------------------
1 | form.steps-form.ng-cloak(ng-class="{'errors': currentErrorMessage}")
2 | p.lead.pack {{'LAVALOGIN.GENERATE_KEYS.TITLE' | translate}}
3 |
4 | p.text-center.steps
5 | span.icon-unread.active
6 | span.icon-unread.active
7 | span.icon-unread.active
8 | span.icon-unread.active
9 | span.icon-unread.active
10 | span.icon-unread
11 | span.icon-unread
12 |
13 | div.info
14 | span.icon-info-circle
15 | p {{'LAVALOGIN.GENERATE_KEYS.LB_WARNING1' | translate}}
16 |
17 | hr.spacer
18 |
19 | ul.list-unstyled
20 | li.table-list
21 | span.icon-lock-hallow.cell.shadow
22 | div.cell
23 | h1.pack {{'GLOBAL.LB_PUBLIC_KEY' | translate}}
24 | p {{'LAVALOGIN.GENERATE_KEYS.LB_WARNING2' | translate}}
25 | li.table-list
26 | span.icon-key-solid.cell.shadow
27 | div.cell
28 | h1.pack {{'GLOBAL.LB_PRIVATE_KEY' | translate}}
29 | p {{'LAVALOGIN.GENERATE_KEYS.LB_WARNING3' | translate}}
30 | hr.spacer
31 |
32 | footer
33 | button.btn.btn-block.btn-lg.btn-primary(ui-sref="generatingKeys") {{'LAVALOGIN.GENERATE_KEYS.BTN_NEXT' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaUtils/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webclient",
3 | "version": "0.0.0",
4 | "homepage": "https://github.com/lavab/webclient",
5 | "authors": [
6 | {
7 | "name": "let4be",
8 | "url": "https://github.com/let4be"
9 | }
10 | ],
11 | "license": "MIT",
12 | "private": true,
13 | "ignore": [
14 | "**/.*",
15 | "node_modules",
16 | "bower_components",
17 | "test",
18 | "tests"
19 | ],
20 | "dependencies": {
21 | "angular": "~1.3.8",
22 | "angular-sanitize": "~1.3.8",
23 | "sockjs-client": "~0.3.4",
24 | "lavaboom": "~0.4.2",
25 | "angular-ui-router": "~0.2.13",
26 | "angular-translate": "~2.6.1",
27 | "angular-translate-loader-static-files": "~2.6.1",
28 | "angular-messages": "~1.3.11",
29 | "angular-co": "~0.2",
30 | "file-saver": "https://github.com/eligrey/FileSaver.js/archive/master.tar.gz",
31 | "openpgp": "https://github.com/lavab/openpgpjs/archive/v1.0.1.2.tar.gz",
32 | "angular-autodisable": "~0.2.0",
33 | "jquery": "2.1.4"
34 | },
35 | "resolutions": {
36 | "jquery": "2.1.4"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/factories/label.js:
--------------------------------------------------------------------------------
1 | module.exports = () => {
2 | const classes = {
3 | 'Drafts': 'draft',
4 | 'Spam': 'ban',
5 | 'Starred': 'star-outline'
6 | };
7 |
8 | function Label (opt) {
9 | const self = this;
10 |
11 | this.id = opt.id;
12 | this.name = opt.name;
13 | this.lname = opt.name.toLowerCase();
14 | this.created = opt.date_created;
15 | this.modified = opt.date_modified;
16 | this.emailsTotal = opt.emails_total;
17 | this.isBuiltin = opt.builtin;
18 |
19 | this.iconClass = `icon-${classes[this.name] ? classes[this.name] : this.name.toLowerCase()}`;
20 |
21 | const unreadFromServer = opt.unread_threads_count;
22 | const unreadThreads = {};
23 | const readThreads = {};
24 |
25 | this.threadsUnread = unreadFromServer;
26 |
27 | this.addUnreadThreadId = (tid) => {
28 | unreadThreads[tid] = true;
29 | self.threadsUnread = unreadFromServer + Object.keys(unreadThreads).length;
30 | };
31 |
32 | this.addReadThreadId = (tid) => {
33 | delete unreadThreads[tid];
34 | self.threadsUnread = unreadFromServer + Object.keys(unreadThreads).length;
35 | };
36 | }
37 |
38 |
39 | return Label;
40 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/decorators/crypto.js:
--------------------------------------------------------------------------------
1 | module.exports = ($delegate, $rootScope, co, consts, Cache, Proxy) => {
2 | const self = $delegate;
3 |
4 | const proxy = new Proxy($delegate);
5 | const cache = new Cache('crypto cache', {
6 | ttl: consts.CRYPTO_CACHE_TTL
7 | });
8 |
9 | proxy.methodCall('decodeRaw', function *(decodeRaw, args) {
10 | const [message] = args;
11 |
12 | if (!message)
13 | return yield decodeRaw(...args);
14 |
15 | if (message.length < consts.CRYPTO_CACHE_MAX_ENTRY_SIZE) {
16 | const key = openpgp.crypto.hash.md5(message);
17 |
18 | let value = cache.get(key);
19 | if (!value) {
20 | value = yield decodeRaw(...args);
21 | cache.put(key, value);
22 | }
23 |
24 | return value;
25 | }
26 |
27 | return yield decodeRaw(...args);
28 | });
29 |
30 | $delegate.invalidateCryptoCache = () => {
31 | cache.invalidateAll();
32 | };
33 |
34 | $rootScope.whenInitialized(() => {
35 | $rootScope.$on('keyring-updated', () => {
36 | self.invalidateCryptoCache();
37 | });
38 | $rootScope.$on('logout', () => {
39 | self.invalidateCryptoCache();
40 | });
41 | });
42 |
43 | return $delegate;
44 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/directives/tagOnFocusLoss.js:
--------------------------------------------------------------------------------
1 | module.exports = ($parse, $timeout) => {
2 | return {
3 | restrict: 'A',
4 | require: 'ngModel',
5 | link: function (scope, element, attrs, ngModel) {
6 | var toggleInput = element.find('input')[0];
7 | toggleInput.addEventListener('blur', (event) => {
8 | let searchValue = $parse(attrs.tagOnFocusLoss)(scope);
9 | if (searchValue)
10 | searchValue = searchValue.trim();
11 |
12 | if (searchValue) {
13 | const tagTransform = $parse(attrs.tagging)(scope);
14 |
15 | $timeout(() => {
16 | const target = angular.element(event.explicitOriginalTarget || document.activeElement);
17 | const isTransferable = (target, lvl = 1) => {
18 | if (target.hasClass('tag-transferable'))
19 | return true;
20 |
21 | if (target.parent() && lvl < 3)
22 | return isTransferable(angular.element(target.parent()), lvl + 1);
23 |
24 | return false;
25 | };
26 |
27 | if (isTransferable(target)) {
28 | const tag = tagTransform(searchValue);
29 | ngModel.$modelValue.push(tag);
30 | }
31 | });
32 | }
33 | });
34 | }
35 | };
36 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/settings/_settingsList.jade:
--------------------------------------------------------------------------------
1 | #settings-list.list-pane.ng-cloak(ng-controller="CtrlSettingsList")
2 | .filters.row.no-gutter.lava-icon-row
3 | |
4 | .list-group-wrapper.list-group-icons
5 | ul.list-group.big-text
6 | li.list-group-item(ui-sref-active='active')
7 | a(ui-sref='main.settings.general', ng-click="toggleShowDetails('main.settings.general')")
8 | i.icon.icon-preferences
9 | span {{'LAVAMAIL.SETTINGS.MENU.GENERAL' | translate}}
10 | li.list-group-item(ui-sref-active='active')
11 | a(ui-sref='main.settings.profile', ng-click="toggleShowDetails('main.settings.profile')")
12 | i.icon.icon-profile
13 | span {{'LAVAMAIL.SETTINGS.MENU.PROFILE' | translate}}
14 | li.list-group-item(ui-sref-active='active')
15 | a(ui-sref='main.settings.security', ng-click="toggleShowDetails('main.settings.security')")
16 | i.icon.icon-key
17 | span {{'LAVAMAIL.SETTINGS.MENU.KEYS' | translate}}
18 | li.list-group-item(ui-sref-active='active')
19 | a(ui-sref='main.settings.plan', ng-click="toggleShowDetails('main.settings.plan')")
20 | i.icon.icon-lavaboom
21 | span {{'LAVAMAIL.SETTINGS.MENU.SUBSCRIPTION' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlBackup.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $state, $window, co, user, signUp, crypto, cryptoKeys, loader, saver) => {
2 | if (!user.isAuthenticated())
3 | $state.go('login');
4 |
5 | $scope.isPartiallyFlow = signUp.isPartiallyFlow;
6 |
7 | const navigateMainApplication = () => {
8 | user.update({state: 'ok'});
9 |
10 | loader.resetProgress();
11 | loader.showLoader(true);
12 | loader.loadMainApplication();
13 | };
14 |
15 | $scope.backup = () => {
16 | let keysBackup = cryptoKeys.exportKeys(user.email);
17 | saver.saveAs(keysBackup, cryptoKeys.getExportFilename(keysBackup, user.name), 'text/plain;charset=utf-8');
18 |
19 | navigateMainApplication();
20 | };
21 |
22 | $scope.skip = () => {
23 | navigateMainApplication();
24 | };
25 |
26 | $scope.sync = () => co(function *(){
27 | let keysBackup = cryptoKeys.exportKeys(user.email);
28 |
29 | yield user.update({isLavaboomSynced: true, keyring: keysBackup, state: 'backupKeys'});
30 |
31 | let keys = crypto.clearPermanentPrivateKeysForEmail(user.email);
32 | crypto.initialize({isShortMemory: true});
33 | crypto.restorePrivateKeys(...keys);
34 |
35 | navigateMainApplication();
36 | });
37 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/services/fileReader.js:
--------------------------------------------------------------------------------
1 | module.exports = function ($rootScope, $q) {
2 | const getReader = (deferred, opts) => {
3 | let reader = new FileReader();
4 |
5 | reader.onload = () => deferred.resolve(reader.result);
6 | reader.onerror = () => deferred.reject(reader.result);
7 |
8 | if (opts && opts.fileProgress)
9 | reader.onprogress = event => $rootScope.$apply(() => {
10 | opts.fileProgress({
11 | total: event.total,
12 | loaded: event.loaded
13 | });
14 | });
15 |
16 | return reader;
17 | };
18 |
19 | this.readAsDataURL = (file, opts = {}) => {
20 | let deferred = $q.defer();
21 |
22 | let reader = getReader(deferred, opts);
23 | reader.readAsDataURL(file);
24 |
25 | return deferred.promise;
26 | };
27 |
28 | this.readAsText = (file, opts = {}) => {
29 | let deferred = $q.defer();
30 |
31 | let reader = getReader(deferred, opts);
32 | reader.readAsText(file);
33 |
34 | return deferred.promise;
35 | };
36 |
37 | this.readAsArrayBuffer = (file, opts = {}) => {
38 | let deferred = $q.defer();
39 |
40 | let reader = getReader(deferred, opts);
41 | reader.readAsArrayBuffer(file);
42 |
43 | return deferred.promise;
44 | };
45 | };
--------------------------------------------------------------------------------
/src/img/Lavaboom-logo-no-shadow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ecmaFeatures": {
3 | "arrowFunctions": true,
4 | "binaryLiterals": true,
5 | "blockBindings": true,
6 | "defaultParams": true,
7 | "forOf": true,
8 | "generators": true,
9 | "objectLiteralComputedProperties": true,
10 | "objectLiteralDuplicateProperties": true,
11 | "objectLiteralShorthandMethods": true,
12 | "objectLiteralShorthandProperties": true,
13 | "octalLiterals": true,
14 | "templateStrings": true
15 | },
16 | "env": {
17 | "browser": true
18 | },
19 | "rules": {
20 | "no-console": 0,
21 | "quotes": "single",
22 | "eqeqeq": 0,
23 | "eol-last": 0,
24 | "semi": 0,
25 | "strict": 0,
26 | "curly": 0,
27 | "camelcase": 0
28 | },
29 | "globals": {
30 | "_": true,
31 | "angular": true,
32 | "openpgp": true,
33 | "saveAs": true,
34 | "Lavaboom": true,
35 | "loader": true,
36 | "checker": true,
37 | "assets": true,
38 | "process": true,
39 | "require": true,
40 | "module": true,
41 | "__dirname": true
42 | }
43 | }
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/misc/_notifications.jade:
--------------------------------------------------------------------------------
1 | nav#notifications.navbar.navbar-inverse.notifications(
2 | ng-class="{ active: (Object.keys(notificationsInfo).length + Object.keys(notificationsWarning).length) > 0 }"
3 | )
4 | div(ng-repeat="(name, notification) in notificationsInfo", ng-class="{'notification-auto-dismiss' : notification.timeout > 0}")
5 | ul.nav.navbar-nav
6 | li
7 | p.navbar-text
8 | span(title="{{notification.title}}", ng-bind-html="notification.text")
9 | ul.nav.navbar-nav.navbar-right(ng-hide="notification.timeout")
10 | li
11 | button.btn.btn-default(ng-click="unSetNotification(name, notification.namespace)")
12 | //- span.icon-trash
13 | | {{'GLOBAL.BTN_REMOVE_NOTIFICATION' | translate}}
14 |
15 | div(ng-repeat="(name, notification) in notificationsWarning", class="notifications-warning")
16 | ul.nav.navbar-nav
17 | li
18 | p.navbar-text
19 | span(title="{{notification.title}}", ng-bind-html="notification.text")
20 | ul.nav.navbar-nav.navbar-right(ng-hide="notification.timeout")
21 | li
22 | button.btn.btn-default(ng-click="unSetNotification(name, notification.namespace)")
23 | //- span.icon-trash
24 | | {{'GLOBAL.BTN_REMOVE_NOTIFICATION' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlSettingsPersonal.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $timeout, $translate, user, co, notifications) => {
2 | $scope.name = user.styledName;
3 | $scope.status = '';
4 | $scope.settings = {};
5 |
6 | const translations = {
7 | LB_PROFILE_SAVED: '',
8 | LB_PROFILE_CANNOT_BE_SAVED: ''
9 | };
10 | $translate.bindAsObject(translations, 'LAVAMAIL.SETTINGS.PROFILE');
11 |
12 | $scope.$bind('user-settings', () => {
13 | $scope.settings = user.settings;
14 | });
15 |
16 | let updateTimeout = null;
17 |
18 | $scope.$watch('settings', (o, n) => {
19 | if (o === n)
20 | return;
21 |
22 | if (Object.keys($scope.settings).length > 0) {
23 | [updateTimeout] = $timeout.schedulePromise(updateTimeout, () => co(function *(){
24 | try {
25 | yield user.update($scope.settings);
26 |
27 | notifications.set('profile-save-ok', {
28 | text: translations.LB_PROFILE_SAVED,
29 | type: 'info',
30 | timeout: 3000,
31 | namespace: 'settings'
32 | });
33 | } catch (err) {
34 | notifications.set('profile-save-fail', {
35 | text: translations.LB_PROFILE_CANNOT_BE_SAVED,
36 | namespace: 'settings'
37 | });
38 | }
39 | }), 1000);
40 | }
41 | }, true);
42 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/services/saver.js:
--------------------------------------------------------------------------------
1 | module.exports = function($translate, utils, notifications, co) {
2 | const notifications18n = {
3 | BAD_BROWSER_TITLE: '',
4 | BAD_BROWSER_TEXT: ''
5 | };
6 |
7 | $translate.bindAsObject(notifications18n, 'NOTIFICATIONS');
8 |
9 | function checkBrowser() {
10 | if (utils.getBrowser().isSafari || !window.Blob || !window.FileReader)
11 | notifications.set('bad-browser', {
12 | title: notifications18n.BAD_BROWSER_TITLE,
13 | text: notifications18n.BAD_BROWSER_TEXT,
14 | type: 'warning'
15 | });
16 |
17 | return !!window.Blob && !!window.FileReader;
18 | }
19 |
20 | this.saveAs = (data, name, type = 'application/octet-stream') => {
21 | if (!checkBrowser())
22 | return;
23 |
24 | var blob = new Blob([utils.str2Uint8Array(data)], {type: type});
25 | saveAs(blob, name);
26 | };
27 |
28 | this.openAs = (data, type) => {
29 | if (!checkBrowser())
30 | return;
31 |
32 | var reader = new FileReader();
33 | var out = new Blob([data], {type: type});
34 | reader.onload = function(e){
35 | var base64Data = btoa(reader.result);
36 | var dataUri = 'data:' + type + ';base64,' + base64Data;
37 | window.open(dataUri);
38 | };
39 | reader.readAsBinaryString(out);
40 | };
41 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/index.toml:
--------------------------------------------------------------------------------
1 | [MANIFEST]
2 | version = "1.0"
3 |
4 | [APPLICATION]
5 | name = "LavaMail"
6 | version = "1.0"
7 | description = "Lava Mail application"
8 | type = "angular"
9 |
10 | moduleName = "LavaMail"
11 |
12 | dependencies = [
13 | "LavaUtils",
14 | "lavaboom.api",
15 | "ui.router",
16 | "ui.bootstrap",
17 | "ui.select",
18 | "textAngular",
19 | "pascalprecht.translate",
20 | "infinite-scroll",
21 | "angular-co",
22 | "ngAutodisable",
23 | "cfp.hotkeys",
24 | "yaru22.angular-timeago",
25 | "dialogs.main",
26 | "ngMessages"
27 | ]
28 |
29 | productionOnlyDependencies = [
30 | ]
31 |
32 | vendorDependencies = [
33 | "bower@papaparse/papaparse.js",
34 | "npm@vcardjs/src/vcard.js",
35 | "npm@vcardjs/src/vcf.js",
36 | "bower@angular-bootstrap/ui-bootstrap.js",
37 | "bower@angular-bootstrap/ui-bootstrap-tpls.js",
38 | "bower@angular-ui-select/dist/select.js",
39 | "bower@textAngular/dist/textAngular-rangy.min.js",
40 | "bower@textAngular/dist/textAngular.min.js",
41 | "bower@ngInfiniteScroll/build/ng-infinite-scroll.js",
42 | "bower@angular-hotkeys/build/hotkeys.js",
43 | "bower@angular-timeago/src/timeAgo.js",
44 | "bower@angular-dialog-service/dist/dialogs.min.js",
45 | "npm@q-encoding/q.js",
46 | ]
--------------------------------------------------------------------------------
/src/less/hotkeys.less:
--------------------------------------------------------------------------------
1 | /*!
2 | * angular-hotkeys v1.4.5
3 | * https://chieffancypants.github.io/angular-hotkeys
4 | * Copyright (c) 2015 Wes Cruver
5 | * License: MIT
6 | */
7 | .cfp-hotkeys-container {
8 | // display: table !important;
9 | position: fixed;
10 | width: 100%;
11 | height: 100%;
12 | top: 0;
13 | left: 0;
14 | // color: #333;
15 | font-size: 1.4rem;
16 | background-color: rgba(255,255,255,0.9);
17 | }
18 |
19 | .cfp-hotkeys-container.fade {
20 | z-index: -1024;
21 | visibility: hidden;
22 |
23 | }
24 |
25 | .cfp-hotkeys-container.fade.in {
26 | z-index: 10002;
27 | visibility: visible;
28 | opacity: 1;
29 | }
30 |
31 | .cfp-hotkeys-title {
32 | font-weight: bold;
33 | text-align: center;
34 | font-size: 1.2em;
35 | }
36 |
37 | .cfp-content {
38 | // display: table-cell;
39 | // vertical-align: middle;
40 | }
41 |
42 | .cfp-hotkeys-keys {
43 | padding: .8rem;
44 | // text-align: right;
45 | .cfp-hotkeys-key{
46 | // margin-right: .5rem;
47 | }
48 | }
49 |
50 | .cfp-hotkeys-close {
51 | font-size: 2rem;
52 | position: absolute;
53 | right: 1rem;
54 | top: 1rem;
55 | }
56 |
57 |
58 |
59 |
60 | .key-row{
61 | display: table;
62 | width: 100%;
63 | > * {
64 | display: table-cell;
65 | width: 50%;
66 | }
67 | }
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/backupKey.jade:
--------------------------------------------------------------------------------
1 | form.steps-form.ng-cloak(ng-class="{'errors': currentErrorMessage}")
2 | p.lead.pack {{'LAVALOGIN.BACKUP_KEY.TITLE' | translate}}
3 |
4 | p.text-center.steps
5 | span.icon-unread.active
6 | span.icon-unread.active
7 | span.icon-unread.active
8 | span.icon-unread.active
9 | span.icon-unread.active
10 | span.icon-unread.active
11 | span.icon-unread.active
12 |
13 | div.info
14 | span.icon-info-circle
15 | div
16 | p {{'LAVALOGIN.BACKUP_KEY.INFO1' | translate}}
17 | p {{'LAVALOGIN.BACKUP_KEY.INFO2' | translate}}
18 | p {{'LAVALOGIN.BACKUP_KEY.INFO3' | translate}}
19 | p {{'LAVALOGIN.BACKUP_KEY.INFO4' | translate}}
20 | p {{'LAVALOGIN.BACKUP_KEY.INFO5' | translate}}
21 | p(ng-show="!isPartiallyFlow") {{'LAVALOGIN.BACKUP_KEY.INFO6' | translate}}
22 | hr.blank
23 |
24 | footer(ng-show="!isPartiallyFlow")
25 | button.btn.btn-block.btn-lg.btn-primary(ng-click="sync()") {{'LAVALOGIN.BACKUP_KEY.BTN_ENABLE_LB_SYNC' | translate}}
26 | button.btn.btn-block.btn-lg.btn(ng-click="backup()") {{'LAVALOGIN.BACKUP_KEY.BTN_BACKUP_NOW' | translate}}
27 | footer(ng-show="isPartiallyFlow")
28 | button.btn.btn-block.btn-lg.btn-primary(ng-click="backup()") {{'LAVALOGIN.BACKUP_KEY.BTN_BACKUP_NOW' | translate}}
29 | button.btn.btn-block.btn-lg.btn(ng-click="skip()") {{'LAVALOGIN.BACKUP_KEY.BTN_SKIP' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/auth.jade:
--------------------------------------------------------------------------------
1 | form.ng-cloak(name="__form", ng-class="{'errors': currentErrorMessage}", ng-submit="__form.$valid && logIn()", novalidate, ng-autodisable)
2 | img.login-logo(src="#{resolveAsset('/img/Lavaboom-logo-no-shadow.svg')}")
3 | p.lead {{'LAVALOGIN.SIGNUP.LOGIN' | translate}}
4 |
5 | div.alert.alert-danger(ng-show="currentErrorMessage", ng-bind="currentErrorMessage")
6 |
7 | div.form-group
8 | input.form-control.input-lg(type='text', auto-focus="true", name="username", ng-model='form.username', placeholder="{{'GLOBAL.PLC_USERNAME_OR_EMAIL' | translate}}", required)
9 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.username.$error", ng-messages-include="LavaLogin/misc/validationMessages")
10 |
11 | div.form-group
12 | input.form-control.input-lg(type='password', name="password", ng-model='form.password', placeholder="{{'GLOBAL.PLC_PASSWORD' | translate}}", required)
13 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.password.$error", ng-messages-include="LavaLogin/misc/validationMessages")
14 |
15 | .checkbox
16 | input#check2(type='checkbox', name="check2", ng-model="form.isPrivateComputer")
17 | label(for='check2') {{'LAVALOGIN.AUTH.LB_PRIVATE_PC' | translate}}
18 |
19 | footer
20 | button.btn.btn-block.btn-lg.btn-primary(type="submit") {{'LAVALOGIN.AUTH.BTN_GO_TO_MY_INBOX' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/plan.jade:
--------------------------------------------------------------------------------
1 | form.steps-form.ng-cloak(ng-class="{'errors': currentErrorMessage}")
2 | p.lead.pack {{'LAVALOGIN.PLAN.TITLE' | translate}}
3 |
4 | p.text-center.steps
5 | span.icon-unread.active
6 | span.icon-unread.active
7 | span.icon-unread
8 | span.icon-unread
9 | span.icon-unread
10 | span.icon-unread
11 | span.icon-unread
12 |
13 | div.login-alert.alert
14 | header
15 | h1 {{'LAVALOGIN.PLAN.PLAN1_TITLE' | translate}}
16 | ul.big.list-unstyled
17 | li
18 | p
19 | strong {{'LAVALOGIN.PLAN.PLAN1_OPT1_VALUE' | translate}}
20 | | {{'LAVALOGIN.PLAN.PLAN1_OPT1_KEY' | translate}}
21 | li
22 | p
23 | strong {{'LAVALOGIN.PLAN.PLAN1_OPT2_VALUE' | translate}}
24 | | {{'LAVALOGIN.PLAN.PLAN1_OPT2_KEY' | translate}}
25 | li
26 | p
27 | strong {{'LAVALOGIN.PLAN.PLAN1_OPT3_VALUE' | translate}}
28 | | {{'LAVALOGIN.PLAN.PLAN1_OPT3_KEY' | translate}}
29 | li
30 | p
31 | strong {{'LAVALOGIN.PLAN.PLAN1_OPT4_VALUE' | translate}}
32 | | {{'LAVALOGIN.PLAN.PLAN1_OPT4_KEY' | translate}}
33 | hr
34 | p.text-center.big
35 | strong {{'LAVALOGIN.PLAN.PLAN1_OPT5_VALUE' | translate}}
36 | | {{'LAVALOGIN.PLAN.PLAN1_OPT5_KEY' | translate}}
37 | hr.blank
38 | footer
39 | button.btn.btn-block.btn-lg.btn-primary(type='submit', ng-click="selectPlan()") {{'LAVALOGIN.PLAN.BTN_SELECT' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/inbox/_attachment.jade:
--------------------------------------------------------------------------------
1 | a.attachment(ui-sref=".popup.download({emailId: email.id, fileId: file.id})")
2 | svg#attachment-icon(width="66", height="88", xmlns="http://www.w3.org/2000/svg", xmlns:svg="http://www.w3.org/2000/svg")
3 | g
4 | title Layer 1
5 | g#main-file-icon(stroke="null")
6 | g#svg_3(stroke="null")
7 | g#svg_4(stroke="null")
8 | g#svg_5(stroke="null")
9 | polygon#svg_6(stroke="null", points="66,16 50,0 0,0 0,88 66,88", fill="#F5F5F5")
10 | polygon#svg_7(stroke="null", points="50,16 66,16 50,0", fill="#ECECEC")
11 | rect#loading-icon(stroke="#000000", stroke-opacity="0", fill-opacity="0", height="33.86705", width="30.11855", y="27.66474", x="17.93347", stroke-linecap="null", stroke-linejoin="null", stroke-dasharray="null", stroke-width="null", fill="#000000")
12 | path#lock(d="m40.77688,42.96763l0,-4.76647c0,-4.26474 -3.51213,-7.77688 -7.77687,-7.77688s-7.77688,3.51214 -7.77688,7.77688l0,4.76647l-1.37977,0l0,14.42485l18.18786,0l0,-14.42485l-1.25433,0l-0.00001,0zm-13.79769,-4.76647c0,-3.3867 2.75953,-6.14625 6.14624,-6.14625s6.14624,2.75954 6.14624,6.14625l0,4.76647l-12.29248,0l0,-4.76647z", fill="#00BCDF")
13 | include ./_attachmentProgress
14 |
15 | hgroup
16 | div
17 | div.file-name
18 | span(ng-bind="file.filename | fileName")
19 | div.file-ext
20 | span(ng-bind="file.filename | fileExtension")
21 | span(ng-bind="file.size | filesize")
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/contacts/import.jade:
--------------------------------------------------------------------------------
1 | div.import-contacts
2 | form
3 | .lava-modal-header
4 | .filters.row.no-gutter.lava-icon-row.z1
5 | span.pull-left.modal-heading {{'LAVAMAIL.CONTACTS.TITLE_IMPORT_CONTACTS' | translate}}
6 | nav.navbar.navbar-inverse(role="navigation")
7 | div
8 | ul.nav.navbar-nav.navbar-right
9 | li
10 | button.btn.btn-default(ng-click="hidePopup()")
11 | span.icon-close
12 | .row.lava-modal-content
13 | div.col-xs-24
14 | p(ng-show="state == 'select'") {{'LAVAMAIL.CONTACTS.LB_IMPORT_CONTACTS_DETAILS' | translate}}
15 | div(ng-show="state == 'importing' || state == 'finished'")
16 | img(src="/img/loader.svg", ng-show="state != 'finished'")
17 | p {{'LAVAMAIL.CONTACTS.LB_PROCESSED_FROM' | translate:translationData }}
18 | p {{'LAVAMAIL.CONTACTS.LB_DUPLICATES' | translate:translationData }}
19 | p {{'LAVAMAIL.CONTACTS.LB_ERRORS' | translate:translationData }}
20 | div(ng-show="state == 'select'")
21 | p
22 | button.btn.btn-block(type='button', open-file="importCSV(data)") {{'LAVAMAIL.CONTACTS.LB_IMPORT_CONTACTS_CSV' | translate}}
23 | button.btn.btn-block(type='button', open-file="importVcard(data)") {{'LAVAMAIL.CONTACTS.LB_IMPORT_CONTACTS_VCARD' | translate}}
24 | div(ng-show="state == 'finished'")
25 | button.btn.btn-default(type='button', ng-click='ok()') {{'LAVAMAIL.CONTACTS.LB_IMPORT_OK' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlLavaboomLogin.js:
--------------------------------------------------------------------------------
1 | module.exports = ($q, $rootScope, $state, $scope, $translate,
2 | LavaboomAPI, utils, tests, notifications, translate, co, crypto, loader, user, signUp) => {
3 | const translations = {
4 | LB_INITIALIZING_I18N: '',
5 | LB_INITIALIZING_OPENPGP: '',
6 | LB_INITIALIZATION_FAILED: '',
7 | LB_SUCCESS: ''
8 | };
9 |
10 |
11 | $scope.initializeApplication = (opts) => co(function *(){
12 | try {
13 | let connectionPromise = LavaboomAPI.connect();
14 |
15 | yield translate.initialize();
16 |
17 | if (!$rootScope.isInitialized) {
18 | yield $translate.bindAsObject(translations, 'LOADER');
19 | }
20 |
21 | loader.incProgress(translations.LB_INITIALIZING_OPENPGP, 5);
22 |
23 | crypto.initialize();
24 |
25 | yield connectionPromise;
26 |
27 | yield tests.initialize();
28 |
29 | tests.performCompatibilityChecks();
30 |
31 | $rootScope.isInitialized = true;
32 |
33 | console.log('opts', opts);
34 | if (opts) {
35 | signUp.isPartiallyFlow = !!opts.state;
36 | if (signUp.isPartiallyFlow) {
37 | yield user.authenticate();
38 |
39 | yield $state.go(opts.state);
40 | }
41 | } else {
42 | yield $state.go('login', {}, {reload: true});
43 | }
44 |
45 |
46 | return {lbDone: translations.LB_SUCCESS};
47 | } catch (error) {
48 | throw {message: translations.LB_INITIALIZATION_FAILED, error: error};
49 | }
50 | });
51 | };
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/secureUsername.jade:
--------------------------------------------------------------------------------
1 | form.ng-cloak(name="__form", ng-class="{'errors': currentErrorMessage}", ng-submit="__form.$valid && requestSecure()", novalidate, ng-autodisable)
2 | h1 {{'LAVALOGIN.SECURE_USERNAME.TITLE' | translate}}
3 |
4 | div.alert.alert-danger(ng-show="currentErrorMessage", ng-bind="currentErrorMessage")
5 |
6 | p.text-center
7 | a(ui-sref="verifyInvite") {{'LAVALOGIN.SECURE_USERNAME.LNK_INVITE_QUESTION' | translate}}
8 |
9 | div.form-group
10 | div.input-group.suffix.clearfix
11 | input.form-control.input-lg(type='text', name="username", ng-model='form.username', placeholder="{{'GLOBAL.USERNAME' | translate}}",
12 | focus="true", required, minlength="2", pattern="^[a-zA-Z0-9\\.]+$")
13 | span.input-group-addon @lavaboom.com
14 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.username.$error", ng-messages-include="LavaLogin/misc/validationMessages")
15 | span(ng-message="pattern") {{'VALIDATION.LB_USERNAME_PATTERN' | translate}}
16 |
17 | div.form-group.clearfix
18 | input.form-control.input-lg(type='email', name="email", ng-model='form.email', placeholder="{{'GLOBAL.CURRENT_EMAIL' | translate}}", required, minlength="4")
19 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.email.$error", ng-messages-include="LavaLogin/misc/validationMessages")
20 |
21 | footer
22 | button.btn.btn-block.btn-lg.btn-primary(type="submit") {{'LAVALOGIN.SECURE_USERNAME.BTN_SECURE' | translate}}
--------------------------------------------------------------------------------
/src/less/print.less:
--------------------------------------------------------------------------------
1 | .only-print{
2 | display: none;
3 | }
4 | @media print {
5 | #leftPanel, #mail-list, .filters, .mail .navbar-right-buttons{
6 | display: none !important;
7 | }
8 | #mail-thread, #content, .main-view{
9 | padding: 0 !important; margin: 0 !important;
10 | position: static !important;
11 | width: 100%;
12 | height: auto !important;
13 | overflow-x: hidden !important;
14 | }
15 |
16 | a[href]:after {
17 | word-wrap: break-word;
18 | }
19 |
20 | strong, .strong, b{
21 | font-weight: 400 !important;
22 | }
23 |
24 | .filters{
25 |
26 | }
27 |
28 | .logo{
29 | padding-right: 80%;
30 | display: block;
31 | height: 3.2rem;
32 | // background-image: url(../img/Lavaboom-logo-gray.svg) !important;
33 | // background-repeat: no-repeat !important;
34 | // background-size: cover;
35 | margin-bottom: 1rem;
36 | img{
37 | .img-responsive();
38 | }
39 | }
40 |
41 | .details-pane{
42 | height: auto;
43 | position: static;
44 | }
45 |
46 | .mail{
47 | border-color: transparent !important; box-shadow: none !important;
48 | border-bottom: 1px solid @border-site-wide;
49 | padding: 0 !important; margin: 0 !important;
50 | &:hover{
51 | border: 1px solid transparent !important;
52 | box-shadow: none !important;
53 | .navbar-right-buttons{
54 | display: none;
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/src/apps/LavaMail/decorators/taTools.js:
--------------------------------------------------------------------------------
1 | module.exports = ($delegate) => {
2 | $delegate.h1.iconclass = 'icon-H1';
3 | delete $delegate.h1.buttontext;
4 |
5 | $delegate.h2.iconclass = 'icon-H2';
6 | delete $delegate.h2.buttontext;
7 |
8 | $delegate.h3.iconclass = 'icon-H3';
9 | delete $delegate.h3.buttontext;
10 |
11 | $delegate.pre.iconclass = 'icon-format-code';
12 | delete $delegate.pre.buttontext;
13 |
14 | $delegate.bold.iconclass = 'icon-format-bold';
15 | $delegate.italics.iconclass = 'icon-format-italic';
16 | $delegate.underline.iconclass = 'icon-format-underline';
17 | $delegate.ul.iconclass = 'icon-format-list-bulleted';
18 | $delegate.ol.iconclass = 'icon-format-list-numbered';
19 | $delegate.undo.iconclass = 'icon-undo';
20 | $delegate.redo.iconclass = 'icon-repeat';
21 | $delegate.justifyLeft.iconclass = 'icon-format-align-left';
22 | $delegate.justifyRight.iconclass = 'icon-format-align-right';
23 | $delegate.justifyCenter.iconclass = 'icon-format-align-center';
24 | $delegate.clear.iconclass = 'icon-ban-circle';
25 | $delegate.insertImage.iconclass = 'icon-format-photo';
26 | $delegate.indent.iconclass = 'icon-format-indent';
27 | $delegate.outdent.iconclass = 'icon-format-dedent';
28 | // $delegate.unlink.iconclass = 'icon-link red';
29 | // $delegate.insertImage.iconclass = 'icon-picture';
30 | // there is no quote icon in old font-awesome so we change to text as follows
31 | // delete $delegate.quote.iconclass;
32 | $delegate.quote.iconclass = 'icon-format-quote';
33 | return $delegate;
34 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/services/composeHelpers.js:
--------------------------------------------------------------------------------
1 | module.exports = function ($rootScope, $templateCache, $compile, co, utils) {
2 | const transformNodes = (dom, level = 0) => {
3 | for(let node of dom.childNodes) {
4 | if (node.getAttribute) {
5 | let classAttr = node.getAttribute('class');
6 | if (classAttr && classAttr.length > 1) {
7 | let classes = classAttr.match(/\S+/g).filter(c => !c.startsWith('ng-'));
8 | node.setAttribute('class', classes.join(' '));
9 | }
10 | }
11 |
12 | if (node.childNodes && node.childNodes.length > 0)
13 | transformNodes(node, level + 1);
14 | }
15 | };
16 |
17 | this.cleanupOutboundEmail = (body) => {
18 | let dom = utils.getDOM(body);
19 | transformNodes(dom);
20 |
21 | console.log('cleanupOutboundEmail: ', body, 'transformed to: ', dom.innerHTML);
22 |
23 | return dom.innerHTML;
24 | };
25 |
26 | this.buildForwardedTemplate = (body, signature, forwardEmails) => co(function *(){
27 | return yield utils.fetchAndCompile('LavaMail/inbox/forwardedEmail', {
28 | body,
29 | signature,
30 | forwardEmails
31 | });
32 | });
33 |
34 | this.buildRepliedTemplate = (body, signature, replies) => co(function *(){
35 | return yield utils.fetchAndCompile('LavaMail/inbox/repliedEmail', {
36 | body,
37 | signature,
38 | replies
39 | });
40 | });
41 |
42 | this.buildDirectTemplate = (body, signature) => co(function *(){
43 | return yield utils.fetchAndCompile('LavaMail/inbox/directEmail', {
44 | body,
45 | signature
46 | });
47 | });
48 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/services/translate.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const acceptLanguageParser = require('accept-language-parser');
3 |
4 | module.exports = function($rootScope, $http, $translate, co, consts) {
5 | const self = this;
6 |
7 | this.settings = {};
8 |
9 | this.initialize = () => co(function *(){
10 | let index = process.env.translationIndexPath ? fs.readFileSync(process.env.translationIndexPath, 'utf8') : '';
11 | if (!index)
12 | return;
13 |
14 | self.settings = JSON.parse(consts.stripBOM(index));
15 | console.log('i18n index loaded', self.settings);
16 |
17 | try {
18 | let headersResponse = yield $http.get(`${consts.API_URI}/headers`);
19 | let headers = headersResponse.data;
20 | let acceptedLanguage = acceptLanguageParser.parse(headers['Accept-Language'][0]);
21 |
22 | for (let lang of acceptedLanguage) {
23 | let fullCode = lang.code + (lang.region ? '_' + lang.region : '');
24 | let translationFile = self.settings.TRANSLATIONS[fullCode];
25 | if (translationFile) {
26 | if (!localStorage.lang)
27 | localStorage.lang = fullCode;
28 | }
29 | }
30 | } catch (err) {
31 | console.error('Error during Accept-Language language determination, fallback to defaults: ', err);
32 | }
33 |
34 | self.switchLanguage(self.getCurrentLangCode());
35 | });
36 |
37 | this.getCurrentLangCode = () => localStorage.lang ? localStorage.lang : consts.DEFAULT_LANG;
38 |
39 | this.switchLanguage = (langKey) => {
40 | localStorage.lang = langKey;
41 | $translate.use(langKey);
42 | };
43 | };
--------------------------------------------------------------------------------
/karma-unit.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function (config) {
2 | config.set({
3 | basePath: '',
4 |
5 | // (!) we can’t use babel preprocessor (https://github.com/babel/karma-babel-preprocessor) here
6 | // because of https://github.com/nikku/karma-browserify#transforms
7 | // and use https://github.com/babel/babelify below
8 | frameworks: ['browserify', 'jasmine'],
9 |
10 | files: [
11 | 'dist/js/lavaUtils-vendor.js',
12 | 'dist/js/lavaUtils.js',
13 |
14 | 'dist/js/lavaMail-vendor.js',
15 | 'dist/js/lavaMail.js',
16 |
17 | 'dist/js/lavaLogin.js',
18 |
19 | 'dist/js/vendor/LavaUtils/openpgp.js',
20 |
21 | //we should have angular before mock it
22 | 'node_modules/angular-mocks/angular-mocks.js',
23 | 'node_modules/sinon/lib/sinon.js',
24 | 'node_modules/jasmine-sinon/lib/jasmine-sinon.js',
25 | 'node_modules/phantomjs-polyfill/bind-polyfill.js',
26 | 'node_modules/to-have-property/lib/index.js',
27 |
28 | 'src/tests/unit/**/*Spec.js'
29 | ],
30 |
31 | exclude: [],
32 |
33 | preprocessors: {
34 | 'src/tests/**/*Spec.js': ['browserify']
35 | },
36 |
37 | browserify: {
38 | debug: true,
39 | // put here transformation that should be done before browserify
40 | transform: ['babelify', 'envify', 'brfs']
41 | },
42 |
43 | reporters: ['progress'],
44 |
45 | port: 9876,
46 |
47 | colors: true,
48 |
49 | logLevel: config.LOG_INFO,
50 |
51 | autoWatch: true,
52 |
53 | browsers: ['PhantomJS'],
54 |
55 | singleRun: true,
56 |
57 | browserNoActivityTimeout: 60000
58 | });
59 | };
60 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/controllers/ctrlChecker.js:
--------------------------------------------------------------------------------
1 | module.exports = (co, consts, LavaboomAPI, $scope) => {
2 | const token = sessionStorage['lava-token'] ? sessionStorage['lava-token'] : localStorage['lava-token'];
3 | LavaboomAPI.setAuthToken(token);
4 |
5 | $scope.initializeApplication = (opts) => co(function *(){
6 | if (!token) {
7 | console.log('checker: no token found!');
8 | return loader.loadLoginApplication({noDelay: true});
9 | }
10 |
11 | try {
12 | yield LavaboomAPI.connect();
13 |
14 | try {
15 | const me = yield LavaboomAPI.accounts.get('me');
16 | console.log('checker: accounts.get(me) success', me);
17 |
18 | try {
19 | const res = yield LavaboomAPI.keys.get(`${me.body.user.name}@${process.env.ROOT_DOMAIN}`);
20 | console.log('checker: keys.get success', res);
21 |
22 | if (!me.body.user.settings || me.body.user.settings.state != 'ok') {
23 | console.log('checker: user haven\'t decided with keys');
24 |
25 | return loader.loadLoginApplication({state: 'backupKeys', noDelay: true});
26 | }
27 |
28 | return loader.loadMainApplication({noDelay: true});
29 | } catch(err) {
30 | console.log('checker: keys.get error', err);
31 | return loader.loadLoginApplication({state: 'generateKeys', noDelay: true});
32 | }
33 | } catch(err) {
34 | console.log('checker: accounts.get(me) error', err);
35 | return loader.loadLoginApplication({noDelay: true});
36 | }
37 | } catch (err) {
38 | console.log('checker: error, cannot connect', err);
39 | throw err;
40 | }
41 | });
42 | };
--------------------------------------------------------------------------------
/src/less/type.less:
--------------------------------------------------------------------------------
1 | body{
2 | .lava-font();
3 | -webkit-font-smoothing: antialiased;
4 | }
5 |
6 | .line-clamp(@lines){
7 | display: -webkit-box;
8 | -webkit-line-clamp: @lines;
9 | -webkit-box-orient: vertical;
10 | position: relative;
11 | height: 0em + (@line-height-base * @lines);
12 | overflow: hidden;
13 | }
14 |
15 | .lava-font{
16 | font-weight: @base-font-weight !important;
17 | .medium{
18 | .lava-font-medium();
19 | }
20 | strong, .strong{
21 | .lava-font-strong();
22 | }
23 | @media (-webkit-min-device-pixel-ratio: 2),
24 | (min-resolution: 192dpi) {
25 | font-weight: @light-font-weight !important;
26 | }
27 | }
28 |
29 | .lava-font-strong{
30 | font-weight: 500;
31 | @media (-webkit-min-device-pixel-ratio: 2),
32 | (min-resolution: 192dpi) {
33 | font-weight: 400;
34 | }
35 | }
36 |
37 | .lava-font-medium{
38 | font-weight: 400;
39 | @media (-webkit-min-device-pixel-ratio: 2),
40 | (min-resolution: 192dpi) {
41 | font-weight: 300;
42 | }
43 | }
44 |
45 | .lava-font-light{
46 | font-weight: 100 !important;
47 | // @media (-webkit-min-device-pixel-ratio: 2),
48 | // (min-resolution: 192dpi) {
49 | // font-weight: 100 !important;
50 | // }
51 | }
52 |
53 | .email-formatting{
54 | line-height: 1.46em;
55 | > * {
56 | margin-bottom: .67rem;
57 | }
58 | h1{
59 | .h4();
60 | }
61 | h2{
62 | .h5();
63 | }
64 | h3{
65 | .h6();
66 | }
67 | blockquote{
68 | border-left: 3px solid @lava;
69 | font-size: 1em;
70 | }
71 | img {
72 | .img-responsive();
73 | }
74 |
75 | pre{
76 | white-space: pre-wrap;
77 | }
78 | }
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlSettingsPlan.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $translate, $interval, consts, user, $sce) => {
2 | $scope.currentPlanName = user.accountType;
3 |
4 | $scope.planList = consts.PLAN_LIST.filter(plan => {
5 | if (user.isHiddenAccountType(plan))
6 | return plan == user.accountType;
7 | return true;
8 | });
9 |
10 | $scope.plans = { };
11 |
12 | const translations = {
13 | LB_CURRENT: '',
14 | LB_GET_ON_IGG_TEXT: '',
15 | LB_GET_ON_IGG_TITLE: ''
16 | };
17 | const translationsForPlans = {};
18 | $translate.bindAsObject(translations, 'LAVAMAIL.SETTINGS.PLAN');
19 |
20 | $scope.planList.forEach(name => {
21 | translationsForPlans[name] = {
22 | TITLE: '',
23 | LB_TAG: '',
24 | LB_ITEMS: ''
25 | };
26 | ((name) => {
27 | $translate.bindAsObject(translationsForPlans[name], 'LAVAMAIL.SETTINGS.PLAN.' + name.toUpperCase(), t => {
28 | let plan = {
29 | title: t.TITLE,
30 | tag: t.LB_TAG,
31 | items: t.LB_ITEMS.split('|').map(item => $sce.trustAsHtml(item)),
32 | hoverTitle: name == user.accountType ? translations.LB_CURRENT : ''
33 | };
34 |
35 | if (user.accountType == 'beta' && name == 'supporter') {
36 | plan.tag = translations.LB_GET_ON_IGG_TEXT;
37 | plan.tagTitle = translations.LB_GET_ON_IGG_TITLE;
38 | plan.tagIsLink = true;
39 | plan.tagHref =
40 | 'https://www.indiegogo.com/projects/lavaboom-secure-email-for-everyone/contributions/new/#/contribute?perk_amt=17&perk_id=2839034';
41 | }
42 |
43 | $scope.plans[name] = plan;
44 | return t;
45 | });
46 | })(name);
47 | });
48 | };
--------------------------------------------------------------------------------
/serve.js:
--------------------------------------------------------------------------------
1 | var path = require('path'),
2 | http = require('http'),
3 | express = require('express'),
4 | staticGzip = require('connect-gzip-static'),
5 | livereload = require('connect-livereload'),
6 | config = require('./gulp/config'),
7 | paths = require('./gulp/paths');
8 |
9 | module.exports = function () {
10 | var app = express();
11 |
12 | // default index redirect
13 | app.use(function (req, res, next) {
14 | if (req.url == '/')
15 | req.url = '/index.html';
16 |
17 | console.log('serve', req.url);
18 | res.setHeader('X-Powered-By', 'Darth Vader');
19 | next();
20 | });
21 |
22 | // inject livereload
23 | app.use(livereload({
24 | port: config.livereloadListenPort
25 | }));
26 |
27 | // serve content, search for pre-compiled gzip with gracefully fallback to plaintext
28 | ['css', 'img', 'js'].forEach(function (folder) {
29 | app.use(staticGzip(paths.output + '/' + folder));
30 | });
31 |
32 | // server index
33 | app.use(staticGzip(paths.output));
34 |
35 | // html5 support
36 | app.all('/*', function(req, res, next) {
37 | if (req.url.endsWith('.html')) {
38 | res.status(404)
39 | .send('Not found');
40 | return;
41 | }
42 |
43 | req.url = '/index.html';
44 |
45 | staticGzip(paths.output)(req, res, next);
46 | });
47 |
48 | // woa!
49 | var server = http.Server(app);
50 | server.listen(config.listenPort, config.listenAddress);
51 |
52 | console.log('Serving content from ' + path.resolve(__dirname, paths.output));
53 | console.log('LISTENING ON ' + config.listenAddress + ':' + config.listenPort);
54 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/index.toml:
--------------------------------------------------------------------------------
1 | [MANIFEST]
2 | version = "1.0"
3 |
4 | [APPLICATION]
5 | name = "LavaUtils"
6 | version = "1.0"
7 | description = "Lava utils core application with generic logic shared across plugins/applications"
8 | type = "angular"
9 |
10 | moduleName = "LavaUtils"
11 |
12 | dependencies = [
13 | "ngSanitize",
14 | "lavaboom.api",
15 | "ui.router",
16 | "pascalprecht.translate",
17 | "angular-co",
18 | "ngAutodisable"
19 | ]
20 |
21 | productionOnlyDependencies = [
22 | ]
23 |
24 | vendorDependencies = [
25 | "vendor@blob.js",
26 | "vendor@html-sanitizer.min.js",
27 |
28 | "bower@jquery/dist/jquery.js",
29 | "bower@angular/angular.js",
30 | "bower@angular-sanitize/angular-sanitize.js",
31 |
32 | "bower@sockjs-client/dist/sockjs.js",
33 | "bower@lavaboom/dist/lavaboom-api.js",
34 | "bower@lavaboom/dist/lavaboom-angular.js",
35 |
36 | "bower@angular-ui-router/release/angular-ui-router.js",
37 | "bower@angular-translate/angular-translate.js",
38 | "bower@angular-translate-loader-static-files/angular-translate-loader-static-files.js",
39 | "bower@angular-messages/angular-messages.js",
40 | "bower@angular-autodisable/angular-autodisable.js",
41 |
42 | "bower@angular-co/dist/co.js",
43 | "bower@file-saver/FileSaver.js",
44 |
45 | "npm@babel-core/browser-polyfill.js",
46 | "npm@babel-core/external-helpers.js",
47 | "npm@setimmediate/setImmediate.js",
48 | "npm@levenshtein/lib/levenshtein.js"
49 | ]
50 |
51 | vendorExternalDependencies = [
52 | "bower@openpgp/dist/openpgp.js",
53 | "bower@openpgp/dist/openpgp.worker.js",
54 | "vendor@html-css-sanitizer.min.js"
55 | ]
--------------------------------------------------------------------------------
/src/apps/LavaMail/services/textAngularHelpers.js:
--------------------------------------------------------------------------------
1 | module.exports = function (utils, taSelection) {
2 | const self = this;
3 | const newLineRegex = /(\r\n|\r|\n)/g;
4 | const tagRegex = /<[a-z][\s\S]*>/gi;
5 |
6 | this.ctrlEnterCallback = null;
7 |
8 | function htmlToText (dom, level = 0) {
9 | let curText = '';
10 |
11 | for(let node of dom.childNodes) {
12 | if (node.nodeName == '#text')
13 | curText += node.data;
14 | if (node.nodeName == 'BR')
15 | curText += '\n';
16 |
17 | if (node.childNodes)
18 | curText += htmlToText(node, level + 1);
19 | }
20 |
21 | return level === 0 ? `${curText}` : curText;
22 | }
23 |
24 | function textToHtml (text) {
25 | const replace = text => {
26 | let r = text
27 | .replace(newLineRegex, '
');
28 |
29 | return r;
30 | };
31 |
32 | let i = 0;
33 | let dom = utils.getDOM(`${text}
`);
34 | let parent = dom.childNodes[0];
35 |
36 | for (let node of parent.childNodes) {
37 | if (node.nodeName == '#text') {
38 | let newDom = utils.getDOM(replace(node.data));
39 | parent.insertBefore(newDom, node);
40 | parent.removeChild(node);
41 | }
42 | }
43 |
44 | return dom.innerHTML;
45 | }
46 |
47 | this.formatPaste = (paste) => {
48 | let selection = taSelection.getSelection();
49 |
50 | let formatted = '';
51 | if (selection.start.element.nodeName == 'PRE' && selection.end.element.nodeName == 'PRE') {
52 | let dom = utils.getDOM(paste);
53 | formatted = htmlToText(dom);
54 | } else {
55 | if (!tagRegex.test(paste))
56 | formatted = textToHtml(paste);
57 | }
58 |
59 | return formatted;
60 | };
61 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/factories/proxy.js:
--------------------------------------------------------------------------------
1 | module.exports = (co) => {
2 | function Proxy ($delegate) {
3 | const self = this;
4 |
5 | this.unbindedMethodCall = (call, proxy, isCache = false) => {
6 | const cache = {};
7 |
8 | const original = $delegate[call];
9 |
10 | if (isCache) {
11 | // we don't want to have multiple simultaneous calls to the same resource(call + arguments)
12 | // so basically if we have one pending we always wait for it to return instead of making a new one
13 | const originalCachingWrapper = (...args) => co(function *() {
14 | const key = call + '.' + args.join(':');
15 | console.log('calling original, cache key', key);
16 |
17 | if (!cache[key])
18 | cache[key] = original(...args);
19 |
20 | const promise = cache[key];
21 | try {
22 | return yield promise;
23 | } finally {
24 | delete cache[key];
25 | }
26 | });
27 |
28 | return (...args) => co(function *() {
29 | return yield co(proxy(originalCachingWrapper, args));
30 | });
31 | }
32 |
33 | return (...args) => co(function *() {
34 | return yield co(proxy(original, args));
35 | });
36 | };
37 |
38 | this.methodCall = (call, proxy, isCache = false) => {
39 | $delegate[call] = self.unbindedMethodCall(call, proxy, isCache);
40 | };
41 |
42 | this.unbindedMethodSyncCall = (call, proxy) => {
43 | const original = $delegate[call];
44 |
45 | return (...args) => function () {
46 | return proxy(original, args);
47 | };
48 | };
49 |
50 | this.methodSyncCall = (call, proxy) => {
51 | $delegate[call] = self.unbindedMethodCall(call, proxy);
52 | };
53 | }
54 |
55 | return Proxy;
56 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/factories/attachment.js:
--------------------------------------------------------------------------------
1 | module.exports = (co, user, crypto, utils, fileReader, Email) => {
2 | function Attachment(file) {
3 | const self = this;
4 |
5 | angular.extend(this, {
6 | id: utils.getRandomString(16),
7 | type: file.type,
8 | name: file.name,
9 | dateModified: new Date(file.lastModifiedDate),
10 | body: '',
11 | size: 0
12 | });
13 |
14 | this.getBodyAsBinaryString = () => utils.Uint8Array2str(self.body);
15 |
16 | this.read = () => co(function* (){
17 | self.body = new Uint8Array(yield fileReader.readAsArrayBuffer(file));
18 | self.size = self.body ? self.body.length : 0;
19 | });
20 | }
21 |
22 | Attachment.toEnvelope = (attachment, keys) => co(function *() {
23 | const isSecured = Email.isSecuredKeys(keys);
24 |
25 | if (isSecured)
26 | keys[user.email] = user.key.armor();
27 | const publicKeys = isSecured ? Email.keysMapToList(keys) : [];
28 |
29 | const envelope = yield crypto.encodeEnvelopeWithKeys({
30 | data: attachment.body
31 | }, publicKeys, 'data');
32 | envelope.name = isSecured ? attachment.id + '.pgp' : attachment.name;
33 |
34 | console.log('attachment envelope', envelope, isSecured, keys);
35 |
36 | return envelope;
37 | });
38 |
39 | Attachment.fromEnvelope = (envelope) => co(function *() {
40 | const data = yield crypto.decodeEnvelope(envelope, 'data');
41 |
42 | switch (data.majorVersion) {
43 | default:
44 | return new Attachment(angular.extend({}, {
45 | id: envelope.id,
46 | name: envelope.name,
47 | dateCreated: envelope.date_created,
48 | dateModified: envelope.date_modified
49 | }, data.data));
50 | }
51 | });
52 |
53 | return Attachment;
54 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlDownload.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, $scope, $stateParams, $interval, $timeout, $translate, consts, co, inbox, router, saver) => {
2 | let [emailId, fileId] = [$stateParams.emailId, $stateParams.fileId];
3 |
4 | let timePassed = 0;
5 | // TODO: implement proper estimation
6 | let estimatedTime = 1000 * 3;
7 |
8 | $scope.progress = 0;
9 | $scope.label = '';
10 |
11 | const translations = {
12 | LB_ACQUIRING : '',
13 | LB_DOWNLOADING : '',
14 | LB_DECRYPTING : '',
15 | LB_TAKES_MORE : '',
16 | LB_COMPLETED : ''
17 | };
18 |
19 | $translate.bindAsObject(translations, 'LAVAMAIL.INBOX.DOWNLOAD', null, () => {
20 | $scope.label = translations.LB_ACQUIRING;
21 | });
22 |
23 | console.log('downloading file. Email id', emailId, 'file id', fileId);
24 |
25 | let progressBarInterval = $interval(() => {
26 | $scope.progress = Math.floor(++timePassed / estimatedTime);
27 | if ($scope.progress >= 100) {
28 | $scope.label = translations.LB_TAKES_MORE;
29 |
30 | $interval.cancel(progressBarInterval);
31 | }
32 | }, 1000);
33 |
34 | co(function *(){
35 | let email = yield inbox.getEmailById(emailId);
36 | console.log('downloading file from email', 'email', email);
37 |
38 | $scope.label = translations.LB_DOWNLOADING;
39 | let fileData = yield inbox.downloadAttachment(emailId, fileId);
40 |
41 | let manifestFile = email.manifest.getFileById(fileId);
42 | saver.saveAs(fileData, manifestFile.filename);
43 |
44 | $scope.progress = 100;
45 | $scope.label = translations.LB_COMPLETED;
46 | $interval.cancel(progressBarInterval);
47 |
48 | $timeout(() => {
49 | router.hidePopup();
50 | }, consts.POPUP_AUTO_HIDE_DELAY);
51 | });
52 | };
--------------------------------------------------------------------------------
/src/apps/LavaLoader/js/host.js:
--------------------------------------------------------------------------------
1 | function Host() {
2 | let plugins = {};
3 |
4 | this.register = (name, options) => {
5 | if (!name)
6 | throw new Error(`Cannot register plugin, name required`);
7 | if (plugins[name])
8 | throw new Error(`Cannot register plugin "${name}" - already registered`);
9 | if (!options.initialize)
10 | throw new Error(`Cannot register plugin "${name}" - "initialize" callback required`);
11 | if (!options.messageHandler)
12 | throw new Error(`Cannot register plugin "${name}" - "messageHandler" callback required`);
13 | if (!options.version)
14 | throw new Error(`Cannot register plugin "${name}" - "version" required`);
15 | if (!options.description)
16 | throw new Error(`Cannot register plugin "${name}" - "description" required`);
17 |
18 | plugins[name] = {
19 | name: name,
20 | isInitialized: false,
21 | options: options
22 | };
23 | };
24 |
25 | this.isLoaded = (name) => !!plugins[name];
26 |
27 | this.isInitialized = (name) => plugins[name].isInitialized;
28 |
29 | this.sendMessage = (toPlugin, msg) => {
30 | if (!toPlugin)
31 | throw new Error(`Cannot send message, plugin recipient required`);
32 | if (!msg)
33 | throw new Error(`Cannot send message to the plugin "${toPlugin}", should not be empty`);
34 | if (!plugins[name])
35 | throw new Error(`Cannot send message, plugin with name "${toPlugin}" should exist`);
36 |
37 | plugins[name].options.messageHandler(msg);
38 | };
39 |
40 | this.broadcastMessage = (msg) => {
41 | if (!msg)
42 | throw new Error(`Cannot broadcast empty message`);
43 |
44 | for(let pluginName of Object.keys(plugins))
45 | plugins[pluginName].messageHandler(msg);
46 | };
47 | }
48 |
49 | module.exports = new Host();
--------------------------------------------------------------------------------
/karma-integration.conf.js:
--------------------------------------------------------------------------------
1 | module.exports = function (config) {
2 | config.set({
3 | basePath: '',
4 |
5 | // (!) we can’t use babel preprocessor (https://github.com/babel/karma-babel-preprocessor) here
6 | // because of https://github.com/nikku/karma-browserify#transforms
7 | // and use https://github.com/babel/babelify below
8 | frameworks: ['browserify', 'jasmine'],
9 |
10 | files: [
11 | 'dist/js/lavaUtils-vendor.js',
12 | 'dist/js/lavaUtils.js',
13 |
14 | 'dist/js/lavaMail-vendor.js',
15 | 'dist/js/lavaMail.js',
16 |
17 | 'dist/js/lavaLogin.js',
18 |
19 | 'dist/js/vendor/LavaUtils/openpgp.js',
20 | {pattern: 'dist/js/vendor/LavaUtils/openpgp.worker.js', included: false},
21 |
22 | //we should have angular before mock it
23 | 'node_modules/angular-mocks/angular-mocks.js',
24 | 'node_modules/sinon/lib/sinon.js',
25 | 'node_modules/jasmine-sinon/lib/jasmine-sinon.js',
26 | 'node_modules/phantomjs-polyfill/bind-polyfill.js',
27 | 'node_modules/to-have-property/lib/index.js',
28 |
29 | 'src/tests/integration/**/*Spec.js'
30 | ],
31 |
32 | proxies: {
33 | '/js/vendor': 'http://localhost:9876/base/dist/js/vendor'
34 | },
35 |
36 | exclude: [],
37 |
38 | preprocessors: {
39 | 'src/tests/**/*Spec.js': ['browserify']
40 | },
41 |
42 | browserify: {
43 | debug: true,
44 | // put here transformation that should be done before browserify
45 | transform: ['babelify', 'envify', 'brfs']
46 | },
47 |
48 | reporters: ['progress'],
49 |
50 | port: 9876,
51 |
52 | colors: true,
53 |
54 | logLevel: config.LOG_INFO,
55 |
56 | autoWatch: true,
57 |
58 | browsers: ['PhantomJS'],
59 |
60 | singleRun: true,
61 |
62 | browserNoActivityTimeout: 60000
63 | });
64 | };
65 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/services/router.js:
--------------------------------------------------------------------------------
1 | module.exports = function ($rootScope, $state, $modal, $timeout) {
2 | const self = this;
3 |
4 | let isInitialized = false;
5 | let delayedPopup = null;
6 |
7 | $rootScope.whenInitialized(() => {
8 | isInitialized = true;
9 | if (delayedPopup) {
10 | delayedPopup.windowClass = 'no-animation-modal';
11 | self.createPopup(delayedPopup);
12 | delayedPopup = null;
13 | }
14 | });
15 |
16 | this.currentModal = null;
17 | let openedDialogs = 0;
18 |
19 | this.registerDialog = (dialogPromise) => {
20 | openedDialogs++;
21 | dialogPromise.finally(() => openedDialogs--);
22 | };
23 |
24 | this.isOpenedDialog = () => openedDialogs > 0;
25 |
26 | this.createPopup = (opts) => {
27 | if (isInitialized) {
28 | self.currentModal = $modal.open(opts);
29 | self.currentModal.result
30 | .then(() => {
31 | self.hidePopup();
32 | })
33 | .catch(() => {
34 | self.hidePopup();
35 | });
36 | } else delayedPopup = opts;
37 | };
38 |
39 | this.showPopup = (stateName, params) => {
40 | if (self.currentModal) {
41 | self.currentModal.close();
42 | self.currentModal = null;
43 | }
44 |
45 | stateName = `.popup.${stateName}`;
46 | if (!params)
47 | params = {};
48 |
49 | $timeout(() => {
50 | $state.go(self.getPrimaryStateName($state.current.name) + stateName, params);
51 | });
52 | };
53 |
54 | this.hidePopup = () => {
55 | if (self.currentModal) {
56 | self.currentModal.close();
57 | self.currentModal = null;
58 | }
59 |
60 | $timeout(() => {
61 | $state.go(self.getPrimaryStateName($state.current.name));
62 | });
63 | };
64 |
65 | this.isPopupState = (name) => name.includes('.popup.');
66 |
67 | this.getPrimaryStateName = (name) => name.replace(/\.popup\..+/, '');
68 | };
--------------------------------------------------------------------------------
/gulp/paths.js:
--------------------------------------------------------------------------------
1 | const config = require('./config');
2 | const output = 'dist/';
3 | const review = 'review/';
4 |
5 | module.exports = {
6 | input: 'src/**/*',
7 | cache: 'cache/',
8 | output: output,
9 | plugins: 'plugins/',
10 | scripts: {
11 | cacheOutput: './cache/',
12 | input: 'src/js/*.js',
13 | inputFolders: ['src/js/', 'src/apps/'],
14 | inputAll: 'src/js/**/*.js',
15 | inputApplication: './src/apps/app.js',
16 | inputDeps: 'src/apps/**/*.toml',
17 | inputAppsFolder: 'src/apps/',
18 | output: output + 'js/'
19 | },
20 | styles: {
21 | input: 'src/less/lavaboom.less',
22 | inputAll: ['src/less/**/*.less', 'src/fonts/**/*'],
23 | output: output + 'css/'
24 | },
25 | svgs: {
26 | input: 'src/svg/*',
27 | output: output + 'svg/'
28 | },
29 | img: {
30 | input: 'src/img/**/*',
31 | output: output + 'img/'
32 | },
33 | fonts: {
34 | input: 'src/fonts/fonts/*',
35 | output: output + 'css/fonts/'
36 | },
37 | markup: {
38 | input: 'src/*.jade',
39 | output: output
40 | },
41 | partials: {
42 | input: 'src/blocks/**/*.jade',
43 | output: output + 'partials/'
44 | },
45 | vendor: {
46 | input: ['src/vendor/*', 'src/apps/LavaUtils/bower_components/openpgp/dist/*'],
47 | output: output + 'vendor/'
48 | },
49 | translations : {
50 | inputEn: 'src/translations/en.toml',
51 | input: 'src/translations/*.toml',
52 | output: `${output}/translations/`,
53 | outputForPlugin: (pluginName) => `${output}/translations/${pluginName}/`
54 | },
55 | tests: {
56 | unit: {
57 | input: 'src/tests/*.js',
58 | output: 'src/tests/out/'
59 | }
60 | },
61 | docs: {
62 | input: 'src/docs/*.{html,md,markdown}',
63 | output: 'docs/',
64 | templates: 'src/docs/_templates/',
65 | assets: 'src/docs/assets/**'
66 | }
67 | };
--------------------------------------------------------------------------------
/gulp/utils.js:
--------------------------------------------------------------------------------
1 | const co = require('co');
2 | const chan = require('chan');
3 |
4 | const source = require('vinyl-source-stream');
5 | const merge = require('merge-stream');
6 |
7 | const fs = require('fs');
8 | const crypto = require('crypto');
9 | const childProcess = require('child_process');
10 | const spawn = childProcess.spawn;
11 |
12 | function Utils() {
13 | const self = this;
14 |
15 | this.calcHash = function (fileName) {
16 | return co(function *() {
17 | let content = yield fs.readFileAsync(fileName, 'utf8');
18 | let sha = crypto.createHash('sha256');
19 | sha.update(content, 'utf8');
20 | return sha.digest().toString('hex');
21 | });
22 | };
23 |
24 | this.createEmptyTask = function () {
25 | return cb => cb();
26 | };
27 |
28 | this.createFile = function (name, content) {
29 | var stream = source(name);
30 | stream.write(content);
31 | process.nextTick(() => stream.end());
32 |
33 | return stream;
34 | };
35 |
36 | this.execute = function (cmd, args, opts) {
37 | return co(function *(){
38 | let p = spawn(cmd, args, opts);
39 | let ch = chan();
40 |
41 | p.on('exit', function (code) {
42 | ch(code);
43 | });
44 |
45 | return yield ch;
46 | });
47 | };
48 |
49 | this.lowerise = function (str) {
50 | return str[0].toLowerCase() + str.substr(1);
51 | };
52 |
53 | this.createFiles = function (list) {
54 | return merge(list.map(e => self.createFile(e.name, e.content)));
55 | };
56 |
57 | this.logGulpError = function (prefix, path, err) {
58 | plg.util.log(
59 | plg.util.colors.red(prefix),
60 | err.message,
61 | '\n\t',
62 | plg.util.colors.cyan('in file'),
63 | path
64 | );
65 | };
66 |
67 | this.def = (func, def) => {
68 | try {
69 | return func();
70 | } catch (err) {
71 | return def;
72 | }
73 | };
74 | }
75 |
76 | module.exports = new Utils();
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/details.jade:
--------------------------------------------------------------------------------
1 | form.steps-form.ng-cloak(name="__form", ng-class="{'errors': currentErrorMessage}", ng-submit="__form.$valid && requestDetailsUpdate()", novalidate, ng-autodisable)
2 | p.lead.pack {{'LAVALOGIN.DETAILS.TITLE' | translate}}
3 |
4 | p.text-center.steps
5 | span.icon-unread.active
6 | span.icon-unread.active
7 | span.icon-unread.active
8 | span.icon-unread
9 | span.icon-unread
10 | span.icon-unread
11 | span.icon-unread
12 |
13 | div.alert.alert-danger(ng-show="currentErrorMessage", ng-bind="currentErrorMessage")
14 | div.info
15 | span.icon-info-circle
16 | p {{'LAVALOGIN.DETAILS.SUB_TITLE' | translate}}
17 |
18 | div.form-group
19 | input.form-control.input-lg(type='text', name="firstName", ng-model='form.firstName', placeholder="{{'GLOBAL.PLC_FIRST_NAME' | translate}}",
20 | focus="true", requared, minlength=2)
21 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.firstName.$error", ng-messages-include="LavaLogin/misc/validationMessages")
22 | div.form-group
23 | input.form-control.input-lg(type='text', name="lastName", ng-model='form.lastName', placeholder="{{'GLOBAL.PLC_LAST_NAME' | translate}}", requared, minlength=2)
24 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.lastName.$error", ng-messages-include="LavaLogin/misc/validationMessages")
25 | br
26 | div.form-group
27 | input.form-control.input-lg(type='text', name="displayName", ng-model='form.displayName', placeholder="{{'GLOBAL.PLC_DISPLAY_NAME' | translate}}", required, minlength=4)
28 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.displayName.$error", ng-messages-include="LavaLogin/misc/validationMessages")
29 |
30 | p.text-center {{'GLOBAL.LB_DISPLAY_NAME' | translate}}
31 |
32 | hr.spacer
33 |
34 | footer
35 | button.btn.btn-block.btn-lg.btn-primary(type='submit') {{'GLOBAL.NEXT_STEP' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/verifyInvite.jade:
--------------------------------------------------------------------------------
1 | form.steps-form.ng-cloak(name="__form", ng-class="{'errors': currentErrorMessage}", ng-submit="__form.$valid && requestVerify()", novalidate, ng-autodisable)
2 | p.lead.pack {{'LAVALOGIN.VERIFY_INVITE.TITLE' | translate}}
3 |
4 | p.text-center.steps.large
5 | span.icon-unread.active
6 | span.icon-unread
7 | span.icon-unread
8 | span.icon-unread
9 | span.icon-unread
10 | span.icon-unread
11 | span.icon-unread
12 |
13 | div.alert.alert-danger(ng-show="currentErrorMessage", ng-bind="currentErrorMessage")
14 |
15 | div.form-group
16 | input.form-control.input-lg(type='text', name="username", ng-model='form.username', placeholder="{{'GLOBAL.USERNAME' | translate}}",
17 | focus="!isUsernameDefined", minlength=2, required)
18 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.username.$error", ng-messages-include="LavaLogin/misc/validationMessages")
19 | div.form-group
20 | input.form-control.input-lg(type='text', name="token", ng-model='form.token', placeholder="{{'LAVALOGIN.VERIFY_INVITE.PLC_INVITE_CODE' | translate}}",
21 | focus="isUsernameDefined", minlength=8, required)
22 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.token.$error", ng-messages-include="LavaLogin/misc/validationMessages")
23 |
24 | hr.spacer
25 |
26 | .checkbox
27 | input#check1(type='checkbox', name="check1", ng-model='form.isNews')
28 | label(for='check1') {{'LAVALOGIN.VERIFY_INVITE.LB_NEWS' | translate}}
29 |
30 | p.text-center
31 | span {{'LAVALOGIN.VERIFY_INVITE.LB_TOS' | translate}}
32 | a(href="https://lavaboom.com/terms", target="_blank") {{'LAVALOGIN.VERIFY_INVITE.LNK_TOS' | translate}}
33 | | {{'LAVALOGIN.VERIFY_INVITE.AND_THE' | translate}}
34 | a(href="https://lavaboom.com/privacy", target="_blank") {{'LAVALOGIN.VERIFY_INVITE.LINK_PRIVACY' | translate}}
35 |
36 | footer
37 | button.btn.btn-block.btn-lg.btn-primary(type='submit') {{'GLOBAL.NEXT_STEP' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaUtils/constants/consts.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | API_URI: process.env.API_URI,
3 | ROOT_DOMAIN: process.env.ROOT_DOMAIN,
4 | ROOT_DOMAIN_LIST: ['lavaboom.com', 'lavaboom.io', 'lavaboom.co'],
5 | IMAGES_PROXY_URI: 'https://rr.lavaboom.com',
6 | DEFAULT_LANG: 'en_US',
7 | DEFAULT_KEY_LENGTH: 4096,
8 | ESTIMATED_KEY_GENERATION_TIME_SECONDS: 24,
9 | INBOX_REDIRECT_DELAY: 1000,
10 | LAVABOOM_SYNC_REDIRECT_DELAY: 1000,
11 | BACKUP_KEYS_REDIRECT_DELAY: 1000,
12 | ENVELOPE_DEFAULT_MAJOR_VERSION: 1,
13 | ENVELOPE_DEFAULT_MINOR_VERSION: 0,
14 | AUTO_SAVE_TIMEOUT: 1000,
15 | LOADER_SHOW_DELAY: 150,
16 | FAST_ACTIONS_TIMEOUT: 250,
17 | MUMBLE_SHOW_DELAY: 1000,
18 | CRYPTO_CACHE_MAX_ENTRY_SIZE: 1024 * 512,
19 | CRYPTO_CACHE_TTL: 60 * 60 * 1000,
20 |
21 | HOTKEY_MULTI_TIMEOUT: 3000,
22 |
23 | MAX_API_CONCURENCY_FOR_BULK: 5,
24 |
25 | CRYPTO_PERFORMANCE_TEST_COUNT: 3,
26 | CRYPTO_PERFORMANCE_TEST_KEY_LENGTH: 512,
27 | CRYPTO_PERFORMANCE_TEST_REF_TIME: 140 * 3,
28 |
29 | // we set this to one year as there is no reason to expire those entries from-memory only cache
30 | // inbox.js algos relies on constant in-memory presence of once loaded threads
31 | // we can manually invalidate the whole cache of threads when we need to do so(sorting of threads for example, whole because we store in chunks of 15)
32 | INBOX_THREADS_CACHE_TTL: 60 * 60 * 24 * 365 * 1000,
33 | INBOX_LABELS_CACHE_TTL: 60 * 60 * 24 * 365 * 1000,
34 |
35 | INBOX_EMAILS_CACHE_TTL: 60 * 10 * 1000,
36 | SET_READ_AFTER_TIMEOUT: 3000,
37 | KEYS_BACKUP_README: 'https://lavab.groovehq.com/knowledge_base/topics/backing-up-your-keyring',
38 | POPUP_AUTO_HIDE_DELAY: 500,
39 | ORDERED_LABELS: ['Inbox', 'Drafts', 'Sent', 'Starred', 'Spam', 'Trash'],
40 | PLAN_LIST: ['free', 'beta', 'supporter', 'premium'],
41 | CRYPTO_DEFAULT_THREAD_POOL_SIZE: 4,
42 | KEY_EXPIRY_DAYS: 365 * 30,
43 | KEY_EXPIRY_DAYS_WARNING: 10,
44 |
45 | // what, why? because reasons ^^
46 | stripBOM: (str) => str.replace(/^\ufeff/g, '')
47 | };
--------------------------------------------------------------------------------
/src/fonts/variables.scss:
--------------------------------------------------------------------------------
1 | $icon-format-list-bulleted: "\e602";
2 | $icon-align-justify: "\e619";
3 | $icon-align-left: "\e61a";
4 | $icon-align-right: "\e61b";
5 | $icon-download: "\e61c";
6 | $icon-add-contact: "\e601";
7 | $icon-archive: "\e607";
8 | $icon-contacts: "\e608";
9 | $icon-later: "\e609";
10 | $icon-empty-trash: "\e60a";
11 | $icon-put-back: "\e60b";
12 | $icon-public-key-download: "\e60c";
13 | $icon-public-key-send: "\e60d";
14 | $icon-public-key-upload: "\e60e";
15 | $icon-plus-circle: "\e60f";
16 | $icon-cancel: "\e610";
17 | $icon-delete: "\e611";
18 | $icon-edit: "\e612";
19 | $icon-edit-circle: "\e613";
20 | $icon-key-pair-download: "\e614";
21 | $icon-key-pair: "\e615";
22 | $icon-provide: "\e616";
23 | $icon-save: "\e617";
24 | $icon-yes: "\e618";
25 | $icon-format-bold: "\f032";
26 | $icon-format-italic: "\f033";
27 | $icon-format-align-center: "\f037";
28 | $icon-format-list: "\f03a";
29 | $icon-format-dedent: "\f03b";
30 | $icon-format-indent: "\f03c";
31 | $icon-format-list-numbered: "\e603";
32 | $icon-format-quote: "\e604";
33 | $icon-format-underline: "\e605";
34 | $icon-format-photo: "\e606";
35 | $icon-plus: "\2b";
36 | $icon-lock-hallow: "\e600";
37 | $icon-key-solid: "\38";
38 | $icon-info-circle: "\39";
39 | $icon-compose: "\61";
40 | $icon-inbox: "\62";
41 | $icon-draft: "\63";
42 | $icon-outbox: "\64";
43 | $icon-sent: "\65";
44 | $icon-star-outline: "\66";
45 | $icon-star: "\67";
46 | $icon-ban: "\68";
47 | $icon-trash: "\6a";
48 | $icon-head: "\6b";
49 | $icon-cog: "\6c";
50 | $icon-power: "\6d";
51 | $icon-paper-clip: "\6e";
52 | $icon-search: "\6f";
53 | $icon-lock: "\70";
54 | $icon-unlock: "\71";
55 | $icon-unread: "\72";
56 | $icon-close: "\73";
57 | $icon-chevron-down: "\76";
58 | $icon-arrow-right: "\79";
59 | $icon-reply: "\7a";
60 | $icon-reply-all: "\e4";
61 | $icon-arrow-up: "\f6";
62 | $icon-arrow-down: "\fc";
63 | $icon-tag: "\df";
64 | $icon-read: "\32";
65 | $icon-preferences: "\33";
66 | $icon-profile: "\34";
67 | $icon-key: "\35";
68 | $icon-lavaboom: "\36";
69 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/runs/rootScopeHelpers.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | window.coJS = require('co');
3 |
4 | module.exports = ($rootScope, $translate, $injector, translate, consts) => {
5 | $rootScope.$bind = (bindName, bindHandler) => {
6 | let r = $rootScope.$on(bindName, bindHandler);
7 |
8 | bindHandler();
9 |
10 | return r;
11 | };
12 |
13 | $rootScope.switchLanguage = (langKey) => {
14 | translate.switchLanguage(langKey);
15 | };
16 |
17 | $rootScope.isInitialized = false;
18 | $rootScope.isShown = false;
19 | $rootScope.currentErrorMessage = '';
20 |
21 | $rootScope.$on('$stateChangeSuccess', () => {
22 | $rootScope.currentErrorMessage = '';
23 | });
24 |
25 | $rootScope.shownApplication = () => {
26 | $rootScope.isShown = true;
27 | };
28 |
29 | $rootScope.notificationsInfo = $rootScope.notificationsWarning = {};
30 | $rootScope.manifest = JSON.parse(consts.stripBOM(
31 | fs.readFileSync(__dirname + '/../../../../manifest.json', 'utf8')
32 | ));
33 | $rootScope.servedBy = {
34 | text: '',
35 | title: ''
36 | };
37 |
38 | const initializeNotifications = () => {
39 | const notifications = $injector.get('notifications');
40 |
41 | $rootScope.servedBy = notifications.get('info', 'status')['powered-by']
42 | || notifications.get('warning', 'status')['powered-by'];
43 |
44 | $rootScope.$bind('notifications', () => {
45 | $rootScope.notificationsInfo = notifications.get('info');
46 | $rootScope.notificationsWarning = notifications.get('warning');
47 | });
48 |
49 | $rootScope.unSetNotification = (nid, namespace) => {
50 | notifications.unSet(nid, namespace);
51 | };
52 |
53 | $rootScope.getNotificationsLength = (...notifications) =>
54 | notifications.reduce((a, c) => a + (c ? Object.keys(c).length : 0), 0);
55 | };
56 |
57 | $rootScope.whenInitialized = (initializer) => {
58 |
59 | if ($rootScope.isInitialized) {
60 | initializeNotifications();
61 | initializer();
62 | } else
63 | $rootScope.$on('initialization-completed', () => {
64 | initializeNotifications();
65 | initializer();
66 | });
67 | };
68 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/contacts/_contactsList.jade:
--------------------------------------------------------------------------------
1 | #contacts-list.list-pane.ng-cloak(ng-controller="CtrlContactList")
2 | .filters.row.no-gutter.lava-icon-row
3 | nav.navbar.navbar-inverse(role="navigation")
4 | div
5 | ul.nav.navbar-nav.navbar-right.boogie
6 | li
7 | button.btn.btn-default(ng-click="showPopup('importContacts')", ng-autodisable, tooltip = "{{'LAVAMAIL.CONTACTS.LB_IMPORT_CONTACTS' | translate}}", tooltip-placement="bottom", tooltip-append-to-body="true")
8 | span.icon-add-contact
9 | li
10 | button.btn.btn-default(ng-click="newContact()", ng-autodisable, tooltip = "{{'LAVAMAIL.CONTACTS.LB_NEW_CONTACT' | translate}}", tooltip-placement="bottom", tooltip-append-to-body="true")
11 | span.icon-add-contact
12 | form.navbar-form-alt
13 | .form-group
14 | .input-group
15 | label.search-icon.input-group-addon(for="top-search")
16 | span.icon-search
17 | input#top-search.search.form-control(placeholder="{{'LAVAMAIL.CONTACTS.PLC_SEARCH_CONTACTS' | translate}}", ng-model="searchText")
18 | .list-group-wrapper.scrollable.z1
19 | ul.list-group
20 | section.pane-status(ng-show="letters.length < 1") {{'LAVAMAIL.CONTACTS.LB_NOTHING_FOUND' | translate}}
21 | section(ng-repeat="alpha in letters")
22 | li.separator.list-group-item(ng-bind="alpha")
23 | li.list-group-item(ng-repeat="contact in people[alpha] | filter: searchText", ng-class="{active: selectedContactId == contact.id}")
24 | a(ng-click="toggleShowDetails('main.contacts.profile', {contactId: contact.id})", ng-class="'sec-' + contact.sec", ui-sref="main.contacts.profile({contactId: contact.id})")
25 | span(ng-show="contact.isNew", ng-bind="contact.getFullName() | defaultValue:('LAVAMAIL.CONTACTS.LB_NEW_CONTACT' | translate)")
26 | span(ng-show="!contact.isNew", ng-bind="contact.getFullName() | defaultValue:('LAVAMAIL.CONTACTS.LB_CONTACT_NAME' | translate)")
27 | span.sec-icon.icon-lock(ng-show="contact.isSecured()")
28 | span.sec-icon.icon-unlock(ng-show="!contact.isSecured()")
29 | span.sec-icon.icon-star(ng-show="contact.isStar()")
--------------------------------------------------------------------------------
/src/apps/LavaLogin/blocks/login/choosePassword.jade:
--------------------------------------------------------------------------------
1 | form.steps-form.ng-cloak(name="__form", ng-class="{'errors': currentErrorMessage}", ng-submit="__form.$valid && updatePassword()", novalidate, ng-autodisable)
2 | p.lead.pack {{'LAVALOGIN.CHOOSE_PASSWORD.TITLE' | translate}}
3 |
4 | p.text-center.steps
5 | span.icon-unread.active
6 | span.icon-unread.active
7 | span.icon-unread.active
8 | span.icon-unread.active
9 | span.icon-unread
10 | span.icon-unread
11 | span.icon-unread
12 |
13 | div.alert.alert-danger(ng-show="currentErrorMessage", ng-bind="currentErrorMessage")
14 |
15 | div.info
16 | span.icon-info-circle
17 | p {{'LAVALOGIN.CHOOSE_PASSWORD.LB_WARNING1' | translate}}
18 |
19 | div.form-group
20 | input.form-control.input-lg(type="password", name="password", ng-model="form.password", placeholder="{{'GLOBAL.PLC_CHOOSE_PASSWORD' | translate}}", focus="true", required, minlength="8", max-repeating="2", dictionary="/vendor/pw.dict", min-levenshtein-distance="3")
21 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.password.$error", ng-messages-include="LavaLogin/misc/validationMessages")
22 | span(ng-message="dictionary") {{'VALIDATION.LB_TOO_SIMPLE' | translate}}
23 | span(ng-message="maxRepeating") {{'VALIDATION.LB_TOO_MANY_REPEATING_CHARACTERS' | translate}}
24 |
25 | div.form-group
26 | input.form-control.input-lg(type="password", name="passwordConfirm", ng-model="form.passwordConfirm", placeholder="{{'GLOBAL.PLC_RETYPE_PASSWORD' | translate}}", required, match="form.password")
27 | span.alert.alert-danger(ng-show="__form.$submitted", ng-messages="__form.passwordConfirm.$error", ng-messages-include="LavaLogin/misc/validationMessages")
28 |
29 | .checkbox
30 | input#check1(type='checkbox', name="check1", ng-model="form.isPrivateComputer")
31 | label(for='check1') {{'LAVALOGIN.CHOOSE_PASSWORD.LB_PRIVATE_PC' | translate}}
32 |
33 | hr.spacer
34 |
35 | div.info
36 | span.icon-info-circle
37 | p {{'LAVALOGIN.CHOOSE_PASSWORD.LB_WARNING2' | translate}}
38 | footer
39 | button.btn.btn-block.btn-lg.btn-primary(type="submit") {{'LAVALOGIN.CHOOSE_PASSWORD.BTN_NEXT' | translate}}
--------------------------------------------------------------------------------
/src/apps/LavaUtils/decorators/$translate.js:
--------------------------------------------------------------------------------
1 | module.exports = ($delegate, $q, $rootScope, utils) => {
2 | const translationTablesCache = {};
3 |
4 | $delegate.instantWithPrefix = (name, prefix = '') => {
5 | if (angular.isObject(name)) {
6 | let translationTable = name;
7 | let originalTranslationTable = translationTable;
8 |
9 | if (!translationTable.__UID) {
10 | translationTable.__UID = utils.getRandomString(16);
11 | translationTablesCache[translationTable.__UID] = angular.copy(translationTable);
12 | } else
13 | originalTranslationTable = translationTablesCache[translationTable.__UID];
14 |
15 | return Object.keys(originalTranslationTable).reduce((a, translationKey) => {
16 | if (translationKey.startsWith('__'))
17 | return a;
18 |
19 | let value = '';
20 | let isParam = false;
21 |
22 | if (originalTranslationTable[translationKey].startsWith('%')) {
23 | value = originalTranslationTable[translationKey].substr(1);
24 | isParam = true;
25 | } else value = originalTranslationTable[translationKey];
26 |
27 | const resolvedTranslationKey = prefix && !value
28 | ? prefix + '.' + translationKey
29 | : value + '.' + translationKey;
30 |
31 | a[translationKey] = isParam
32 | ? (argsObject) => $delegate.instant(resolvedTranslationKey, argsObject)
33 | : $delegate.instant(resolvedTranslationKey);
34 | return a;
35 | }, {});
36 | }
37 |
38 | return $delegate.instant(name);
39 | };
40 |
41 | $delegate.bindAsObject = (translations, prefix = '', map = null, postProcess = null) => {
42 | let deferred = $q.defer();
43 |
44 | $rootScope.$bind('$translateChangeSuccess', () => {
45 | try {
46 | const currentTranslations = $delegate.instantWithPrefix(translations, prefix);
47 | const mappedTranslations = map ? map(currentTranslations) : currentTranslations;
48 | for(let k of Object.keys(mappedTranslations))
49 | translations[k] = mappedTranslations[k];
50 |
51 | if (postProcess)
52 | postProcess();
53 |
54 | deferred.resolve(translations);
55 | } catch (err) {
56 | deferred.reject(err);
57 | }
58 | });
59 |
60 | return deferred.promise;
61 | };
62 |
63 | return $delegate;
64 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/settings/settingsProfile.jade:
--------------------------------------------------------------------------------
1 | .row.settings-panel.ng-cloak(ng-controller="CtrlSettingsPersonal")
2 | form.settings.col-xs-22.col-xs-offset-1.col-sm-20.col-sm-offset-2.col-md-12.col-md-offset-6.col-xs-22.col-xs-offset-1
3 | //- h4 Picture
4 | //- .row.settings-row
5 | //- .col-xs-7
6 | //- img.img-responsive(src='img/avatar.svg')
7 | //- .col-xs-17
8 | //- p.profile-description.text-muted
9 | //- | Your picture is only shown in mails to recipents in your contacts. It‘s only purpose is to make your messages more personal. It is not used for anything else.
10 | h4 {{'LAVAMAIL.SETTINGS.PROFILE.TITLE_NAME' | translate}}
11 | ul.list-group
12 | li.list-group-item
13 | .text-control
14 | label {{'LAVAMAIL.SETTINGS.PROFILE.LB_USERNAME' | translate}}
15 | input.form-control(disabled='', ng-model="name")
16 | span
17 | li.list-group-item
18 | .text-control
19 | label {{'LAVAMAIL.SETTINGS.PROFILE.LB_FIRST_NAME' | translate}}
20 | input.form-control(ng-model="settings.firstName")
21 | li.list-group-item
22 | .text-control
23 | label {{'LAVAMAIL.SETTINGS.PROFILE.LB_LAST_NAME' | translate}}
24 | input.form-control(ng-model="settings.lastName")
25 | li.list-group-item
26 | .text-control
27 | label {{'LAVAMAIL.SETTINGS.PROFILE.LB_DISPLAY_NAME' | translate}}
28 | input.form-control(ng-model="settings.displayName")
29 | h4 {{'LAVAMAIL.SETTINGS.PROFILE.TITLE_SIGNATURE' | translate}}
30 | ul.list-group
31 | li.list-group-item
32 | label {{'LAVAMAIL.SETTINGS.PROFILE.LB_SIGNATURE_ENABLED' | translate}}
33 | .control
34 | label.switch
35 | input(type='checkbox', ng-model="settings.isSignatureEnabled")
36 | i
37 | li.list-group-item(collapse="!settings.isSignatureEnabled")
38 | .text-control
39 | .signature
40 | div(text-angular="", ta-toolbar="[]", ng-model="settings.signatureHtml")
41 |
42 | //- h4 {{'LAVAMAIL.SETTINGS.PROFILE.TITLE_NEWSLETTER' | translate}}
43 | //- ul.list-group
44 | li.list-group-item
45 | label {{'LAVAMAIL.SETTINGS.PROFILE.LB_SEND_ME_NEWS' | translate}}
46 | .control
47 | label.switch
48 | input(type='checkbox', ng-model="settings.isSubscribedToNews")
49 | i
--------------------------------------------------------------------------------
/src/apps/LavaUtils/services/notifications.js:
--------------------------------------------------------------------------------
1 | module.exports = function($rootScope, $translate, $timeout) {
2 | const self = this;
3 |
4 | let notifications = {};
5 |
6 | this.clear = () => {
7 | notifications = {};
8 | $rootScope.$broadcast('notifications');
9 | };
10 |
11 | this.set = (name, {text, title, type, timeout, namespace, kind, onRemove}) => {
12 | if (!type)
13 | type = 'warning';
14 | if (!namespace)
15 | namespace = 'root';
16 | if (!kind)
17 | kind = 'unspecified';
18 | if (!title)
19 | title = '';
20 | if (!timeout)
21 | timeout = 0;
22 | if (!onRemove)
23 | onRemove = null;
24 |
25 | let cssClass = '';
26 | if (type == 'warning')
27 | cssClass = 'icon-info-circle';
28 |
29 | const notification = {
30 | text,
31 | title,
32 | type,
33 | timeout,
34 | namespace,
35 | kind,
36 | cssClass,
37 | onRemove
38 | };
39 | notifications[namespace + '.' + name] = notification;
40 |
41 | if (timeout > 0)
42 | $timeout(() => {
43 | self.unSet(name, namespace);
44 | }, timeout);
45 |
46 | $rootScope.$broadcast('notifications');
47 | };
48 |
49 | this.unSet = (name, namespace = 'root') => {
50 | name = namespace + '.' + name;
51 | if (name in notifications) {
52 | if (notifications[name].onRemove)
53 | notifications[name].onRemove();
54 | delete notifications[name];
55 | $rootScope.$broadcast('notifications');
56 | }
57 | };
58 |
59 | this.unSetByKind = (kind) => {
60 | for(let cName of Object.keys(notifications))
61 | if (notifications[cName].kind == kind) {
62 | if (notifications[cName].onRemove)
63 | notifications[cName].onRemove();
64 | delete notifications[cName];
65 | }
66 |
67 | $rootScope.$broadcast('notifications');
68 | };
69 |
70 | this.get = (type, namespace = 'root') => {
71 | const notificationsSet = angular.copy(notifications);
72 | let r = Object.keys(notificationsSet).reduce((a, name) => {
73 | if (!name.startsWith(namespace + '.'))
74 | return a;
75 |
76 | if (notificationsSet[name].type == type)
77 | a[name.replace(namespace + '.', '')] = notificationsSet[name];
78 |
79 | return a;
80 | }, {});
81 |
82 | if (namespace != 'root')
83 | angular.extend(r, self.get(type));
84 |
85 | return r;
86 | };
87 | };
--------------------------------------------------------------------------------
/fabfile.py:
--------------------------------------------------------------------------------
1 | import os, random, string
2 | from fabric.api import run, env, cd, settings, put, local, shell_env
3 |
4 | # load ~/.ssh/id_rsa
5 | env.key_filename = os.getenv('HOME', '/root') + '/.ssh/id_rsa'
6 |
7 | # determine hosts
8 | branch = os.getenv('DRONE_BRANCH', '')
9 | if branch == "master":
10 | env.hosts = ["marge.lavaboom.io:36104"]
11 | api_uri = "https://api.lavaboom.com"
12 | root_domain = "lavaboom.com"
13 | elif branch == "staging":
14 | env.hosts = ["lisa.lavaboom.io:36412"]
15 | api_uri = "https://api.lavaboom.io"
16 | root_domain = "lavaboom.io"
17 | elif branch == "develop":
18 | env.hosts = ["bart.lavaboom.io:36467"]
19 | api_uri = "https://api.lavaboom.co"
20 | root_domain = "lavaboom.co"
21 |
22 | # build
23 | def build():
24 | # we install required npm packages with increased number of retries and if it fails we use backup mirror
25 | local("npm install --fetch-retries 3 -g gulp || npm install --fetch-retries 3 --registry http://registry.npmjs.eu -g gulp")
26 | local("npm install --fetch-retries 3 || npm install --fetch-retries 3 --registry http://registry.npmjs.eu")
27 | if branch == "master" or branch == "staging":
28 | with shell_env(API_URI=api_uri, ROOT_DOMAIN=root_domain):
29 | local("gulp production")
30 | elif branch == "develop":
31 | with shell_env(API_URI=api_uri, ROOT_DOMAIN=root_domain):
32 | local("gulp develop")
33 | else:
34 | local("gulp develop")
35 |
36 | def deploy():
37 | branch = os.getenv('DRONE_BRANCH', 'master')
38 | commit = os.getenv('DRONE_COMMIT', 'master')
39 | tmp_dir = '/tmp/' + ''.join(random.choice(string.lowercase) for i in xrange(10))
40 |
41 | local('tar cvfz dist.tgz dist/')
42 | run('mkdir ' + tmp_dir)
43 | with cd(tmp_dir):
44 | run('mkdir -p ' + tmp_dir + '/web/dist')
45 | put('dist.tgz', tmp_dir + '/web/dist.tgz')
46 | put('Dockerfile', tmp_dir + '/web/Dockerfile')
47 | put('website.conf', tmp_dir + '/web/website.conf')
48 |
49 | with cd('web'):
50 | run('tar -xzvf dist.tgz')
51 | run('docker build -t registry.lavaboom.io/lavaboom/web-' + branch + ' .')
52 |
53 | run('git clone git@github.com:lavab/docker.git')
54 | with settings(warn_only=True):
55 | run('docker rm -f web-' + branch)
56 | with cd('docker/runners'):
57 | run('./web-' + branch + '.sh')
58 |
59 | run('rm -r ' + tmp_dir)
60 |
61 | def integrate():
62 | build()
63 |
64 | if branch == "master" or branch == "staging" or branch == "develop":
65 | deploy()
--------------------------------------------------------------------------------
/src/less/notifications.less:
--------------------------------------------------------------------------------
1 | #notifications.notifications.navbar-inverse > div:not(.notification-auto-dismiss){
2 | background: @lava;
3 | color: #FFF;
4 | padding: 0 1.5rem;
5 | .clearfix();
6 |
7 | .btn{
8 | background: @lava-button-color;
9 | padding-left: 2.2rem;
10 | padding-right: 2.2rem;
11 | span{
12 | display: inline-block;
13 | padding-right: 1.1rem;
14 | padding-left: 0;
15 | }
16 | }
17 |
18 | a {
19 | color: white;
20 | }
21 |
22 | .navbar-text, .btn-default{
23 | color: #FFF !important;
24 | }
25 | }
26 |
27 | #notifications.notifications.navbar-inverse > div.notification-auto-dismiss{
28 | .text-center();
29 | .navbar-nav{
30 | float: none;
31 | width: auto;
32 | margin: 1.1rem auto;
33 | // line-height: 2.2rem;
34 | border-radius: @border-radius-base;
35 | background: @lava;
36 | display: inline-block;
37 | .navbar-text{
38 | color: #FFF;
39 | .lava-font-strong();
40 | margin: .6rem;
41 | }
42 | }
43 | }
44 |
45 | .notifications-0 .notifications{
46 | display: none;
47 | }
48 |
49 | .notifications-1 + div {
50 | // top: @filter-height * 2 !important;
51 | }
52 |
53 | .notifications-2 + div {
54 | // top: @filter-height * 3 !important;
55 | }
56 |
57 | .dialogs-default{
58 | .modal-lg{
59 | width: 35rem;
60 | margin: 11.0rem auto;
61 | }
62 |
63 | .modal-content{
64 | background: @lava;
65 | color: #FFF;
66 | border: 0 !important;
67 | overflow: hidden;
68 | .lava-font-medium();
69 | .text-center();
70 | }
71 |
72 | .modal-header{
73 | border: 0;
74 | .text-center();
75 | padding: 1.1rem;
76 | line-height: 2.2rem;
77 | height: 4.4rem;
78 | .close{
79 | display: none;
80 | }
81 | }
82 | .modal-body{
83 | line-height: 2.2rem;
84 | padding: 1.1rem;
85 | margin-bottom: 1.1rem;
86 | font-size: 1.8rem;
87 | }
88 | .modal-footer{
89 | padding: 0;
90 | display: flex;
91 | border: 0 !important;
92 | margin: 0;
93 | background-color: @lava-light;
94 | > *{
95 | flex-grow: 1;
96 | margin: 0 !important;
97 | padding-top: 0; padding-bottom: 0;
98 | line-height: @filter-height;
99 | background-color: transparent !important;
100 | color: #FFF;
101 | font-size: 1.8rem;
102 | &:hover{
103 | background-color: @lava-lighter !important;
104 | }
105 | &:not(:first-child){
106 | border-left: 1px solid @lava-lighter;
107 | }
108 | }
109 | }
110 | }
--------------------------------------------------------------------------------
/gulp/draft.js:
--------------------------------------------------------------------------------
1 | // Run unit tests
2 | gulp.task('test:scripts', function() {
3 | return gulp.src([paths.test.input].concat([paths.test.spec]))
4 | .pipe(plg.plumber())
5 | .pipe(plg.karma({ configFile: paths.test.karma }))
6 | .on('error', function(err) { throw err; });
7 | });
8 |
9 | // Generate documentation
10 | gulp.task('build:docs', ['compile', 'clean:docs'], function() {
11 | return gulp.src(paths.docs.input)
12 | .pipe(plg.plumber())
13 | .pipe(plg.fileInclude({
14 | prefix: '@@',
15 | basepath: '@file'
16 | }))
17 | .pipe(plg.tap(function (file, t) {
18 | if ( /\.md|\.markdown/.test(file.path) ) {
19 | return t.through(plg.markdown);
20 | }
21 | }))
22 | .pipe(plg.header(fs.readFileSync(paths.docs.templates + '/_header.html', 'utf8')))
23 | .pipe(plg.footer(fs.readFileSync(paths.docs.templates + '/_footer.html', 'utf8')))
24 | .pipe(gulp.dest(paths.docs.output));
25 | });
26 |
27 | // Copy distribution files to docs
28 | gulp.task('copy:dist', ['compile', 'clean:docs'], function() {
29 | return gulp.src(paths.output + '/**')
30 | .pipe(plg.plumber())
31 | .pipe(gulp.dest(paths.docs.output + '/dist'));
32 | });
33 |
34 | // Copy documentation assets to docs
35 | gulp.task('copy:assets', ['clean:docs'], function() {
36 | return gulp.src(paths.docs.assets)
37 | .pipe(plg.plumber())
38 | .pipe(gulp.dest(paths.docs.output + '/assets'));
39 | });
40 |
41 | // Remove pre-existing content from docs folder
42 | gulp.task('clean:docs', function () {
43 | return del.sync(paths.docs.output);
44 | });
45 |
46 | // Generate SVG sprites
47 | gulp.task('build:svgs', ['clean:dist'], function () {
48 | return gulp.src(paths.svgs.input)
49 | .pipe(plg.plumber())
50 | .pipe(plg.tap(function (file, t) {
51 | if ( file.isDirectory() ) {
52 | var name = file.relative + '.svg';
53 | return gulp.src(file.path + '/*.svg')
54 | .pipe(plg.svgmin())
55 | .pipe(plg.svgstore({
56 | fileName: name,
57 | prefix: 'icon-',
58 | inlineSvg: true
59 | }))
60 | .pipe(gulp.dest(paths.svgs.output));
61 | }
62 | }))
63 | .pipe(plg.svgmin())
64 | .pipe(plg.svgstore({
65 | fileName: 'icons.svg',
66 | prefix: 'icon-',
67 | inlineSvg: true
68 | }))
69 | .pipe(gulp.dest(paths.svgs.output));
70 | });
71 |
72 | // Generate documentation
73 | gulp.task('docs', [
74 | 'clean:docs',
75 | 'build:docs',
76 | 'copy:dist',
77 | 'copy:assets'
78 | ]);
79 |
80 | // Generate documentation
81 | gulp.task('tests', [
82 | 'test:scripts'
83 | ]);
--------------------------------------------------------------------------------
/src/apps/LavaUtils/decorators/LavaboomAPI.js:
--------------------------------------------------------------------------------
1 | module.exports = ($delegate, $rootScope, $translate, co, utils) => {
2 | let propsList = ['info', 'accounts', 'files', 'contacts', 'emails', 'labels', 'keys', 'threads', 'tokens'];
3 |
4 | const formatError = (callName, error) => {
5 | callName = callName.toUpperCase();
6 |
7 | const translate = (name) => {
8 | let r = $translate.instant(name);
9 | if (r == name || !r)
10 | throw new Error(`Translation '${name}' not found!`);
11 | return r;
12 | };
13 |
14 | try {
15 | if (error.body && error.body.message == 'Unexpected end of input')
16 | return translate(`LAVABOOM.API.ERROR.DOWN`);
17 |
18 | return translate(`LAVABOOM.API.ERROR.${callName}.${error.status}`);
19 | } catch (err) {
20 | const def = utils.def(() => translate(`LAVABOOM.API.ERROR.${callName}.DEFAULT`), '');
21 |
22 | try {
23 | let genericReason = translate(`LAVABOOM.API.ERROR.${error.status}`);
24 | return def ? `${def} (${genericReason})` : genericReason;
25 | } catch (err) {
26 | if (error.body && error.body.message)
27 | return error.body.message;
28 |
29 | // huh? wtf!
30 | try {
31 | return translate(`LAVABOOM.API.ERROR.UNKNOWN`);
32 | } catch (err) {
33 | // srsly? wtf!
34 | return 'unknown error';
35 | }
36 | }
37 | }
38 | };
39 |
40 | let patchApiMethod = (obj, k, callName) => {
41 | let originalFunction = obj[k];
42 |
43 | obj[k] = (...args) => {
44 | console.log(`Calling ${callName}`, args ? args : '[no args]', '...');
45 | return co(function *() {
46 | try {
47 | let res = yield originalFunction(...args);
48 |
49 | console.log(`${callName}: `, res);
50 |
51 | return res;
52 | } catch (err) {
53 | let formattedError = formatError(callName, err);
54 | $rootScope.currentErrorMessage = formattedError;
55 |
56 | console.error(`${callName} error: `, err);
57 |
58 | let error = new Error(formattedError);
59 | error.original = err;
60 | throw error;
61 | }
62 | });
63 | };
64 | };
65 |
66 | function wrapApiCall (path, obj, k) {
67 | if (angular.isFunction(obj[k])) {
68 | let callName = [...path, k].join('.');
69 |
70 | //console.log('patching LavaboomAPI', callName);
71 | patchApiMethod(obj, k, callName);
72 | } else
73 | wrapApiObject([...path, k], obj[k]);
74 | }
75 |
76 | function wrapApiObject (path, obj) {
77 | for(let k in obj)
78 | if (obj.hasOwnProperty(k))
79 | wrapApiCall(path, obj, k);
80 | }
81 |
82 | for (let prop of propsList) {
83 | wrapApiObject([prop], $delegate[prop]);
84 | }
85 |
86 | return $delegate;
87 | };
--------------------------------------------------------------------------------
/src/tests/integration/utils/services/cryptoSpec.js:
--------------------------------------------------------------------------------
1 | const integralDigest = require('../../../helpers/intervalDigest.js'),
2 | gen = require('jasmine-es6-generator'),
3 | numBits = 512;
4 |
5 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 1000;
6 |
7 | describe('Crypto Service', () => {
8 | let service,
9 | $rootScope,
10 | email = 'some@email.com',
11 | nameAndEmail = `John Doh <${email}>`,
12 | pwd = 'password';
13 |
14 | beforeEach(angular.mock.module('LavaUtils'));
15 |
16 | beforeEach(inject((crypto, _$rootScope_) => {
17 | service = crypto;
18 | $rootScope = _$rootScope_;
19 | }));
20 |
21 | //mock user email. because it inject directly to service
22 | beforeEach(inject((user) => {
23 | user.email = email;
24 | }));
25 |
26 | beforeEach(inject(($httpBackend) => {
27 | $httpBackend.whenGET('/translations/LavaUtils/en.json').respond();
28 | $httpBackend.whenGET('/partials/inbox/defaultSignature.html').respond();
29 | }));
30 |
31 | beforeEach(()=>
32 | service.initialize()
33 | );
34 |
35 | describe('keys generating', () => {
36 | beforeEach(integralDigest.start());
37 |
38 | it('should return public and private keys', (done) =>
39 | service.generateKeys('', '', numBits).then((keys) => {
40 | expect(keys).toHaveProperty('pub');
41 | expect(keys).toHaveProperty('prv');
42 | done();
43 | }, done.fail)
44 | );
45 |
46 | afterEach(integralDigest.stop());
47 | });
48 |
49 | describe('encrypting-decrypting chain', () => {
50 | let publicKeys;
51 |
52 | beforeEach(integralDigest.start());
53 |
54 | beforeEach(gen(function*() {
55 | let keys = yield service.generateKeys(nameAndEmail, pwd, numBits);
56 | service.importPublicKey(keys.pub);
57 | service.importPrivateKey(keys.prv);
58 | service.storeKeyring();
59 | service.authenticateByEmail(email, pwd);
60 | publicKeys = [keys.pub.armor()];
61 | }));
62 |
63 | it('should encrypt', gen(function*() {
64 | const orgMessage = 'original message',
65 | env = yield service.encodeWithKeys(orgMessage, publicKeys);
66 |
67 | expect(env).toHaveProperty('pgpData');
68 | expect(env.pgpData).not.toEqual(orgMessage);
69 | }));
70 |
71 | it('should decrypt encrypted', gen(function*() {
72 | const orgMessage = 'original message';
73 |
74 | let encodedMail = yield service.encodeWithKeys(orgMessage, publicKeys),
75 | decodedMail = yield service.decodeRaw(encodedMail.pgpData);
76 |
77 | expect(decodedMail).toEqual(orgMessage);
78 | }));
79 |
80 | afterEach(integralDigest.stop());
81 | });
82 |
83 | afterEach(() => {
84 | service.removeAllKeys();
85 | service.removeSensitiveKeys();
86 | });
87 | });
88 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/controllers/ctrlContactProfileEmail.js:
--------------------------------------------------------------------------------
1 | module.exports = ($scope, $stateParams, $translate, $timeout, co, consts, crypto, saver, notifications, router, user, Key) => {
2 | $scope.selectedContactId = $stateParams.contactId;
3 | $scope.isNotFound = false;
4 | $scope.emails = [];
5 |
6 | const translations = {
7 | LB_EMAIL_NOT_FOUND: '',
8 | LB_CANNOT_IMPORT_PUBLIC_KEY: '',
9 | LB_PUBLIC_KEY_IMPORTED: '%'
10 | };
11 | $translate.bindAsObject(translations, 'LAVAMAIL.CONTACTS');
12 |
13 | let updateTimeout = null;
14 | $scope.starEmail = () => {
15 | $scope.currentEmail.isStar = !$scope.currentEmail.isStar;
16 |
17 | [updateTimeout] = $timeout.schedulePromise(updateTimeout, $scope.saveThisContact, consts.AUTO_SAVE_TIMEOUT);
18 | };
19 |
20 | $scope.requestPublicKey = () => {
21 | router.showPopup('compose', {to: $scope.currentEmail.email, publicKey: user.key.fingerprint});
22 | };
23 |
24 | $scope.uploadPublicKey = (data) => {
25 | try {
26 | const key = crypto.readKey(data);
27 |
28 | if (!key)
29 | throw new Error('not_found');
30 | const primaryKey = key.primaryKey;
31 | if (!primaryKey)
32 | throw new Error('not_found');
33 |
34 | $scope.currentEmail.key = new Key(key);
35 |
36 | notifications.set('public-key-import-ok' + $scope.currentEmail.email, {
37 | type: 'info',
38 | text: translations.LB_PUBLIC_KEY_IMPORTED({
39 | email: $scope.currentEmail.email,
40 | algos: $scope.currentEmail.key.algos,
41 | length: $scope.currentEmail.key.length
42 | }),
43 | namespace: 'contact.profile',
44 | timeout: 3000,
45 | kind: 'crypto'
46 | });
47 | } catch (err) {
48 | notifications.set('public-key-import-fail' + $scope.currentEmail.email, {
49 | text: translations.LB_CANNOT_IMPORT_PUBLIC_KEY,
50 | namespace: 'contact.profile',
51 | kind: 'crypto'
52 | });
53 | }
54 | };
55 |
56 | $scope.downloadPublicKey = () => {
57 | console.log($scope.currentEmail.key);
58 | saver.saveAs($scope.currentEmail.key.armor(), `${$scope.currentEmail.email}.asc`, 'text/plain;charset=utf-8');
59 | };
60 |
61 | $scope.remove = () => {
62 | console.log('remove from', $scope.details[$scope.emails], $scope.currentEmail.$$hashKey);
63 | $scope.details[$scope.emails] = $scope.details[$scope.emails].filter(e => e.$$hashKey != $scope.currentEmail.$$hashKey);
64 | };
65 |
66 | $scope.$watch('currentEmail.name', (o, n) => {
67 | if (o == n)
68 | return;
69 |
70 | let email = $scope.currentEmail;
71 | if (!email || !email.name)
72 | return;
73 |
74 | email.email = email.name.includes('@') ? email.name : `${email.name}@${consts.ROOT_DOMAIN}`;
75 | email.loadKey(true);
76 | });
77 | };
78 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/compose/_composeEmail.jade:
--------------------------------------------------------------------------------
1 | .cell.label
2 | label #{defs.label}:
3 |
4 | .cell.fields(ng-controller="CtrlComposeEmail")
5 | ui-select.tag-transferable(multiple, theme="bootstrap", style="width: 100%;", title="{{'LAVAMAIL.COMPOSE.TITLE_PERSON' | translate}}",
6 | tagging="tagTransform", tagging-tokens="{{taggingTokens}}", reset-search-input="true", ng-model="#{defs.model}",
7 | tag-on-focus-loss="$select.search")
8 | ui-select-match(placeholder="{{'LAVAMAIL.COMPOSE.PLC_SELECT_PERSON' | translate}}", ng-class="$item.name")
9 | email-context-menu(email="$item.email", is-open="status.isDropdownOpened", no-reply="true")
10 | span.icon-star(ng-show="$item.isStar")
11 | span(ng-click="switchContextMenu(); tagClicked($select, $item, #{defs.model});")
12 | span {{$item.getDisplayName()}}
13 | span(ng-if="$item.getDisplayName() != $item.email") <{{$item.email}}>
14 | span(ng-show="$item.isError && $item.isNotFoundError") ({{'LAVAMAIL.COMPOSE.LB_ERROR_NOT_FOUND' | translate}})
15 | span(ng-show="$item.isError && !$item.isNotFoundError") ({{'LAVAMAIL.COMPOSE.LB_ERROR_UNKNOWN' | translate}})
16 | //- span(ng-show="!$item.isError") {{$item.getLabel()}}
17 | img.inverted.loading(src="/img/loader.svg", ng-show="$item.isLoadingKey()")
18 | span(class="icon-lock {{$item.getSecureClass()}}", ng-show="!$item.isLoadingKey()")
19 |
20 | ui-select-choices(repeat="personEmail in people | filter: personFilter($select.search)")
21 | span(ng-show="personEmail && personEmail.isError && personEmail.isNotFoundError") <{{personEmail.email}}> {{'LAVAMAIL.COMPOSE.LB_ERROR_NOT_FOUND' | translate}}
22 | span(ng-show="personEmail && personEmail.isError && !personEmail.isNotFoundError") <{{personEmail.email}}> {{'LAVAMAIL.COMPOSE.LB_ERROR_UNKNOWN' | translate}}
23 |
24 | div.item(ng-if="personEmail && !personEmail.isError && personEmail.isTag")
25 | span(ng-show="!personEmail.isHidden()") {{personEmail.getDisplayName()}}
26 | span.icon-star(ng-show="personEmail.isStar")
27 | span {{personEmail.email}}
28 | img.inverted.loading(src="/img/loader.svg", ng-show="personEmail.isLoadingKey()")
29 | span(class="icon-lock {{personEmail.getSecureClass()}}", ng-show="!personEmail.isLoadingKey()")
30 |
31 | div.item(ng-if="personEmail && !personEmail.isError && !personEmail.isTag")
32 | span(ng-show="!personEmail.isHidden()", ng-bind-html="personEmail.getDisplayName() | highlight: $select.search")
33 | span.icon-star(ng-show="personEmail.isStar")
34 | span.muted(ng-bind-html="personEmail.email | highlight: $select.search")
35 | span(class="icon-lock {{personEmail.getSecureClass()}}")
36 | button.btn-unstyled.pull-right(ng-click="#{defs.click}")
37 | span.icon-close
--------------------------------------------------------------------------------
/src/fonts/demo-files/demo.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | margin: 0;
4 | font-family: sans-serif;
5 | font-size: 1em;
6 | line-height: 1.5;
7 | color: #555;
8 | background: #fff;
9 | }
10 | h1 {
11 | font-size: 1.5em;
12 | font-weight: normal;
13 | }
14 | small {
15 | font-size: .66666667em;
16 | }
17 | a {
18 | color: #e74c3c;
19 | text-decoration: none;
20 | }
21 | a:hover, a:focus {
22 | box-shadow: 0 1px #e74c3c;
23 | }
24 | .bshadow0, input {
25 | box-shadow: inset 0 -2px #e7e7e7;
26 | }
27 | input:hover {
28 | box-shadow: inset 0 -2px #ccc;
29 | }
30 | input, fieldset {
31 | font-size: 1em;
32 | margin: 0;
33 | padding: 0;
34 | border: 0;
35 | }
36 | input {
37 | color: inherit;
38 | line-height: 1.5;
39 | height: 1.5em;
40 | padding: .25em 0;
41 | }
42 | input:focus {
43 | outline: none;
44 | box-shadow: inset 0 -2px #449fdb;
45 | }
46 | .glyph {
47 | font-size: 16px;
48 | width: 15em;
49 | padding-bottom: 1em;
50 | margin-right: 4em;
51 | margin-bottom: 1em;
52 | float: left;
53 | overflow: hidden;
54 | }
55 | .liga {
56 | width: 80%;
57 | width: calc(100% - 2.5em);
58 | }
59 | .talign-right {
60 | text-align: right;
61 | }
62 | .talign-center {
63 | text-align: center;
64 | }
65 | .bgc1 {
66 | background: #f1f1f1;
67 | }
68 | .fgc1 {
69 | color: #999;
70 | }
71 | .fgc0 {
72 | color: #000;
73 | }
74 | p {
75 | margin-top: 1em;
76 | margin-bottom: 1em;
77 | }
78 | .mvm {
79 | margin-top: .75em;
80 | margin-bottom: .75em;
81 | }
82 | .mtn {
83 | margin-top: 0;
84 | }
85 | .mtl, .mal {
86 | margin-top: 1.5em;
87 | }
88 | .mbl, .mal {
89 | margin-bottom: 1.5em;
90 | }
91 | .mal, .mhl {
92 | margin-left: 1.5em;
93 | margin-right: 1.5em;
94 | }
95 | .mhmm {
96 | margin-left: 1em;
97 | margin-right: 1em;
98 | }
99 | .mls {
100 | margin-left: .25em;
101 | }
102 | .ptl {
103 | padding-top: 1.5em;
104 | }
105 | .pbs, .pvs {
106 | padding-bottom: .25em;
107 | }
108 | .pvs, .pts {
109 | padding-top: .25em;
110 | }
111 | .unit {
112 | float: left;
113 | }
114 | .unitRight {
115 | float: right;
116 | }
117 | .size1of2 {
118 | width: 50%;
119 | }
120 | .size1of1 {
121 | width: 100%;
122 | }
123 | .clearfix:before, .clearfix:after {
124 | content: " ";
125 | display: table;
126 | }
127 | .clearfix:after {
128 | clear: both;
129 | }
130 | .hidden-true {
131 | display: none;
132 | }
133 | .textbox0 {
134 | width: 3em;
135 | background: #f1f1f1;
136 | padding: .25em .5em;
137 | line-height: 1.5;
138 | height: 1.5em;
139 | }
140 | #testDrive {
141 | display: block;
142 | padding-top: 24px;
143 | line-height: 1.5;
144 | }
145 | .fs0 {
146 | font-size: 16px;
147 | }
148 | .fs1 {
149 | font-size: 24px;
150 | }
151 |
--------------------------------------------------------------------------------
/src/apps/LavaUtils/factories/key.js:
--------------------------------------------------------------------------------
1 | module.exports = ($injector, $translate, $timeout, co, crypto, utils, consts, dateFilter) => {
2 | const translations = {
3 | TP_KEY_IS_ENCRYPTED: '',
4 | TP_KEY_IS_DECRYPTED: '',
5 | LB_EXPIRED: '',
6 | LB_EXPIRING_SOON: ''
7 | };
8 | $translate.bindAsObject(translations, 'LAVAMAIL.SETTINGS.SECURITY');
9 |
10 | const statuses = {};
11 |
12 | function Key(key) {
13 | const self = this;
14 |
15 | const daysToMsec = 24 * 60 * 60 * 1000;
16 | const now = () => new Date();
17 |
18 | this.keyId = utils.hexify(key.primaryKey.keyid.bytes);
19 | this.fingerprint = key.primaryKey.fingerprint;
20 | this.fingerprintPretty = key.primaryKey.fingerprint.match(/.{1,4}/g).join(' ');
21 |
22 | this.created = new Date(Date.parse(key.primaryKey.created));
23 | this.expiredAt = new Date(Date.parse(key.primaryKey.created) + consts.KEY_EXPIRY_DAYS * daysToMsec);
24 | this.algos = key.primaryKey.algorithm.split('_')[0].toUpperCase();
25 | this.length = key.primaryKey.getBitSize();
26 | this.user = key.users[0].userId.userid;
27 | this.email = utils.getEmailFromAddressString(key.users[0].userId.userid);
28 |
29 | if (!statuses[self.fingerprint])
30 | statuses[self.fingerprint] = {
31 | isCollapsed: true
32 | };
33 |
34 | let isCollapsed = statuses[self.fingerprint].isCollapsed;
35 | let decodeTimeout = null;
36 | let decryptTime = 0;
37 |
38 | this.getTitle = () =>
39 | (self.isExpiringSoon ? `(${translations.LB_EXPIRING_SOON}) ` : '') +
40 | (self.isExpired ? `(${translations.LB_EXPIRED}) ` : '') +
41 | dateFilter(self.created);
42 |
43 | this.isExpired = now() > self.expiredAt;
44 | this.isExpiringSoon = !self.isExpired && (now() > self.expiredAt.getTime() - consts.KEY_EXPIRY_DAYS_WARNING * daysToMsec);
45 |
46 | this.getDecryptTime = () => decryptTime;
47 | this.isDecrypted = () => key && key.primaryKey.isDecrypted;
48 | this.isCollapsed = () => isCollapsed;
49 | this.switchCollapse = () => {
50 | isCollapsed = !isCollapsed;
51 | statuses[self.fingerprint].isCollapsed = isCollapsed;
52 | };
53 |
54 | this.getEncryptionStatusTooltip = () => this.isDecrypted()
55 | ? translations.TP_KEY_IS_DECRYPTED
56 | : translations.TP_KEY_IS_ENCRYPTED;
57 |
58 | this.decrypt = (password) => co(function *(){
59 | decodeTimeout = $timeout.schedule(decodeTimeout, () => {
60 | let r = false;
61 | if (key && !key.primaryKey.isDecrypted) {
62 | r = crypto.authenticate(key, password);
63 | return !!r;
64 | }
65 |
66 | decryptTime = new Date();
67 | }, consts.AUTO_SAVE_TIMEOUT);
68 |
69 | utils.def(yield decodeTimeout);
70 |
71 | return key.primaryKey.isDecrypted;
72 | });
73 |
74 | this.armor = () => key.armor();
75 | }
76 |
77 | return Key;
78 | };
--------------------------------------------------------------------------------
/src/less/actions.less:
--------------------------------------------------------------------------------
1 | // padding-top: @filter-height + 2;
2 | .filters, .actions{
3 | background-color: @lava-gray;
4 | position: absolute;
5 | right: 0;
6 | left: 0;
7 | height: @filter-height;
8 | color: @lava-gray-light;
9 |
10 |
11 | .no-gutter();
12 |
13 | .search{
14 | padding-left: 0;
15 | }
16 |
17 | .search-icon {
18 | // width: @mail-list-block-buttons-width;
19 | height: @filter-height;
20 | .lava-icon-22();
21 | padding: 0 .4rem 0 .9rem;
22 | .icon-search{
23 | // padding-left: 1.2rem;
24 | // float: left;
25 | // font-size: 14px;
26 | // padding-top: 3px;
27 | }
28 | }
29 | label.search-icon .icon-search{
30 | line-height: 4.4rem;
31 | padding: 0 0.53rem 0 .3rem;
32 | }
33 |
34 |
35 | > *{
36 | display: block;
37 | }
38 |
39 |
40 | button, label{
41 | color: @lava-gray-light;
42 | background: transparent;
43 | border: 0;
44 | height: @filter-height;
45 |
46 | &:hover{
47 | color: @lava-gray-light;
48 | }
49 | &:active{
50 | outline: 0;
51 | box-shadow: 0;
52 | }
53 | }
54 | input{
55 | background-color: transparent;
56 | border: 0 none;
57 | height: @filter-height;
58 | color: @lava-gray-light;
59 | border-radius: 0;
60 | outline: none;
61 |
62 | &:active, &:focus{
63 | box-shadow: none !important;
64 | outline: none;
65 | border: 0;
66 | }
67 | }
68 |
69 |
70 | button{
71 | background: transparent none;
72 | border: 0;
73 | outline: 0;
74 |
75 | &:active, &:hover{
76 | background: transparent none;
77 | border: 0;
78 | outline: 0;
79 | }
80 | }
81 |
82 | .btn{
83 |
84 | }
85 |
86 |
87 | .navbar{
88 | border: 0;
89 | }
90 |
91 | .navbar-text{
92 | margin-top: 1.2rem;
93 | margin-bottom: 1.2rem;
94 | }
95 |
96 | .btn.btn-default{
97 | background: transparent none;
98 | border: 0;
99 | outline: 0;
100 | .lava-icon-22();
101 | color: @lava-gray-light;
102 | transition: .2s color;
103 | &:active, &:hover, &:focus, &.active, &.open{
104 | background: transparent none;
105 | border: 0;
106 | outline: 0;
107 | box-shadow: none;
108 | color: @lava-gray-lightest;
109 | }
110 |
111 | .transition(.2s);
112 |
113 | &:active{
114 | transform: scale(.9);
115 | }
116 | }
117 |
118 | .btn-group.open .dropdown-toggle{
119 | background: transparent none;
120 | border: 0;
121 | outline: 0;
122 | // padding: 0 inherit;
123 | box-shadow: none;
124 | color: @lava-gray-lighter;
125 | }
126 |
127 | .form-group{
128 | margin: 0;
129 | }
130 |
131 | li.spacer{
132 | padding-left: 2rem;
133 | }
134 | }
--------------------------------------------------------------------------------
/src/apps/LavaLogin/services/signUp.js:
--------------------------------------------------------------------------------
1 | module.exports = function (LavaboomAPI, co, user, consts, $http) {
2 | const self = this;
3 |
4 | this.reserve = null;
5 | this.plan = null;
6 | this.tokenSignup = null;
7 | this.details = null;
8 | this.password = null;
9 | this.isPartiallyFlow = false;
10 |
11 | let lastReservedUsername = localStorage['lava-reserved-username'];
12 | if (lastReservedUsername)
13 | self.reserve = {
14 | originalUsername: lastReservedUsername
15 | };
16 |
17 | this.register = (username, altEmail) => {
18 | localStorage['lava-reserved-username'] = username;
19 | self.reserve = {
20 | originalUsername: username,
21 | username: username,
22 | altEmail: altEmail
23 | };
24 |
25 | return co(function * (){
26 | let res = yield LavaboomAPI.accounts.create.register({
27 | username: username,
28 | alt_email: altEmail
29 | });
30 |
31 | return res.body;
32 | });
33 | };
34 |
35 | this.verifyInvite = (username, token, isNews) => {
36 | self.tokenSignup = {
37 | username: username,
38 | token: token,
39 | isNews: isNews
40 | };
41 |
42 | if (isNews) {
43 | const email = `${username}@${consts.ROOT_DOMAIN}`;
44 | co(function *() {
45 | const res = yield $http({
46 | method: 'POST',
47 | url: 'https://technical.lavaboom.com/subscribe',
48 | headers: {
49 | 'Content-Type': 'application/x-www-form-urlencoded'
50 | },
51 | transformRequest: function (obj) {
52 | var str = [];
53 | for (var p in obj)
54 | str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p]));
55 | return str.join('&');
56 | },
57 | data: {
58 | email: email,
59 | list: 'YAUEUIgnZs1u6nPTYqjcDw'
60 | }
61 | });
62 |
63 | console.log('subscribe result', res);
64 | });
65 | }
66 |
67 | return co(function * (){
68 | let res = yield LavaboomAPI.accounts.create.verify({
69 | username: self.tokenSignup.username,
70 | invite_code: self.tokenSignup.token
71 | });
72 |
73 | return res.body;
74 | });
75 | };
76 |
77 | this.setup = (password, isRemember) => {
78 | self.password = password;
79 | return co(function * (){
80 | yield LavaboomAPI.accounts.create.setup({
81 | username: self.tokenSignup.username,
82 | invite_code: self.tokenSignup.token,
83 | password: user.calculateHash(password)
84 | });
85 |
86 | yield user.signIn(self.tokenSignup.username, password, isRemember, isRemember);
87 |
88 | let settings = angular.extend({},
89 | self.details,
90 | user.defaultSettings, {
91 | isSubscribedToNews: self.tokenSignup.isNews,
92 | state: 'incomplete'
93 | });
94 |
95 | yield user.update(settings);
96 |
97 | yield user.startOnboarding();
98 | });
99 | };
100 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/navigation/navigation.jade:
--------------------------------------------------------------------------------
1 | div.ng-cloak
2 | nav#navigation
3 | div#brand
4 | a(ui-sref="main.inbox.label({labelName: 'Inbox', threadId: null})")
5 | img(src="#{resolveAsset('/img/Lavaboom-logo-no-shadow.svg')}")
6 | img(src="#{resolveAsset('/img/Lavaboom-logo-wordmark-min.svg')}")
7 | ul.list-unstyled
8 | li.compose
9 | a(ng-click="showPopup('compose'); toggleLeftPanel()", tooltip-placement="right", tooltip="{{'LAVAMAIL.NAV.LB_COMPOSE' | translate}}")
10 | span.icon-compose
11 | span#compose-action.action {{'LAVAMAIL.NAV.LB_COMPOSE' | translate}}
12 | li(ng-class="{ active: $state.includes('main.inbox.label', {labelName: label.lname}) }", ng-repeat="label in labels track by label.id")
13 | a(ng-click="toggleLeftPanel('main.inbox.label', {labelName: label.lname, threadId: getThreadForLabel(label.name)})" ui-sref="main.inbox.label({labelName: label.name, threadId: getThreadForLabel(label.name)})", tooltip="{{label.name}}", tooltip-placement="right")
14 | span(ng-class="label.iconClass")
15 | span.action {{labelTranslations[label.name.toUpperCase()]}}
16 | span.badge {{label.threadsUnread | unread}}
17 | span.combo(ng-show="isActive('g')") ({{getMultiComboPrettified('g', label.name)}})
18 | li.separator
19 | li(ui-sref-active="active")
20 | a(ng-click="toggleLeftPanel('main.contacts')", ui-sref="main.contacts", tooltip-placement="right", tooltip = "{{'LAVAMAIL.NAV.LB_CONTACTS' | translate}}")
21 | span.icon-contacts
22 | span.action {{'LAVAMAIL.NAV.LB_CONTACTS' | translate}}
23 | span.combo(ng-show="isActive('g')") ({{getMultiComboPrettified('g', 'Contacts')}})
24 | li(ng-class="{ active: $state.includes('main.settings') }", class="hidden-xs hidden-sm")
25 | a(ng-click="toggleLeftPanel('main.settings.general')", ui-sref="main.settings.general", tooltip-placement="right", tooltip = "{{'LAVAMAIL.NAV.LB_SETTINGS' | translate}}")
26 | span.icon-cog
27 | span.action {{'LAVAMAIL.NAV.LB_SETTINGS' | translate}}
28 | span.combo(ng-show="isActive('g')") ({{getMultiComboPrettified('g', 'Settings')}})
29 | li(ng-class="{ active: $state.includes('main.settings') }", class="hidden-md hidden-lg")
30 | a(ng-click="toggleLeftPanel('main.settings')", ui-sref="main.settings", tooltip-placement="right", tooltip = "{{'LAVAMAIL.NAV.LB_SETTINGS' | translate}}")
31 | span.icon-cog
32 | span.action {{'LAVAMAIL.NAV.LB_SETTINGS' | translate}}
33 | span.combo(ng-show="isActive('g')") ({{getMultiComboPrettified('g', 'Settings')}})
34 | li.logout
35 | small {{manifest.name}} 2015
36 | small(title="{{servedBy.title}}")
37 | a(href="https://github.com/lavab/web/blob/master/CHANGELOG.md", target="_blank") {{manifest.version}}
38 | | {{servedBy.text}}
39 | button(ng-click="logout()", tooltip-placement="right", tooltip = "{{'LAVAMAIL.NAV.LB_LOGOUT' | translate}}")
40 | span.icon-power
41 | span.action {{'LAVAMAIL.NAV.LB_LOGOUT' | translate}} ({{name}})
--------------------------------------------------------------------------------
/src/less/lavaboom-login.less:
--------------------------------------------------------------------------------
1 | @bootstrap-folder: '../bower_components/bootstrap/less/';
2 |
3 | @import (less) "../bower_components/angular-ui-select/dist/select.css";
4 | @import (less) "../bower_components/angular/angular-csp.css";
5 | // @import (less) "../bower_components/angular-hotkeys/build/hotkeys.css";
6 |
7 | // Core variables and mixins
8 | @import "@{bootstrap-folder}variables.less";
9 | @import "lavaboom-variables.less";
10 | @import "@{bootstrap-folder}mixins.less";
11 |
12 | // Reset and dependencies
13 | @import "@{bootstrap-folder}normalize.less";
14 | @import "@{bootstrap-folder}print.less";
15 |
16 |
17 | // Core CSS
18 | @import "@{bootstrap-folder}scaffolding.less";
19 | @import "@{bootstrap-folder}type.less";
20 | // @import "@{bootstrap-folder}code.less";
21 | @import "@{bootstrap-folder}grid.less";
22 | // @import "@{bootstrap-folder}tables.less";
23 | @import "@{bootstrap-folder}forms.less";
24 | @import "@{bootstrap-folder}buttons.less";
25 |
26 | // Components
27 | // @import "@{bootstrap-folder}component-animations.less";
28 | // @import "@{bootstrap-folder}dropdowns.less";
29 | @import "@{bootstrap-folder}button-groups.less";
30 | @import "@{bootstrap-folder}input-groups.less";
31 | // @import "@{bootstrap-folder}navs.less";
32 | // @import "@{bootstrap-folder}navbar.less";
33 | // @import "@{bootstrap-folder}breadcrumbs.less";
34 | // @import "@{bootstrap-folder}pagination.less";
35 | // @import "@{bootstrap-folder}pager.less";
36 | // @import "@{bootstrap-folder}labels.less";
37 | // @import "@{bootstrap-folder}badges.less";
38 | // @import "@{bootstrap-folder}jumbotron.less";
39 | // @import "@{bootstrap-folder}thumbnails.less";
40 | // @import "@{bootstrap-folder}alerts.less";
41 | @import "@{bootstrap-folder}progress-bars.less";
42 | // @import "@{bootstrap-folder}media.less";
43 | // @import "@{bootstrap-folder}list-group.less";
44 | // @import "@{bootstrap-folder}panels.less";
45 | // @import "@{bootstrap-folder}responsive-embed.less";
46 | // @import "@{bootstrap-folder}wells.less";
47 | // @import "@{bootstrap-folder}close.less";
48 |
49 | // Components w/ JavaScript
50 | // @import "@{bootstrap-folder}modals.less";
51 | // @import "@{bootstrap-folder}tooltip.less";
52 | // @import "@{bootstrap-folder}popovers.less";
53 | // @import "@{bootstrap-folder}carousel.less";
54 |
55 | // Utility classes
56 | @import "@{bootstrap-folder}utilities.less";
57 | @import "@{bootstrap-folder}responsive-utilities.less";
58 |
59 | //LAVABOOM STUFF
60 | @import "login.less";
61 | @import "main.less";
62 | // @import "left-panel.less";
63 | // @import "notifications.less";
64 | // @import "mail-list.less";
65 | // @import "mail-thread.less";
66 | // @import "actions.less";
67 | @import "forms.less";
68 | // @import "dropdowns.less";
69 | // @import "contacts.less";
70 | @import "lavaboom-icons.less";
71 | // @import "selectize.less";
72 | // @import "compose.less";
73 | // @import "quirks.less";
74 | // @import "hotkeys.less";
75 | // @import "remixins.less";
76 | // @import "print.less";
--------------------------------------------------------------------------------
/src/apps/LavaLogin/configs/cfgLoginNavigation.js:
--------------------------------------------------------------------------------
1 | module.exports = ($stateProvider, $urlRouterProvider, $locationProvider) => {
2 | $locationProvider.html5Mode(true);
3 |
4 | // small hack - both routers(login && main app) work at the same time, so we need to troubleshot this
5 | $urlRouterProvider.otherwise(($injector, $location) => {
6 | console.log('login router otherwise: window.loader.isMainApplication()', window.loader.isMainApplication(), $location);
7 | if (window.loader.isMainApplication())
8 | return undefined;
9 | return '/';
10 | });
11 |
12 | $stateProvider
13 | .state('login', {
14 | url: '/',
15 | templateUrl: 'LavaLogin/login/loginOrSignup'
16 | })
17 |
18 | .state('decrypting', {
19 | url: '/decrypting',
20 | templateUrl: 'LavaLogin/login/decrypting'
21 | })
22 |
23 | .state('auth', {
24 | url: '/auth',
25 | templateUrl: 'LavaLogin/login/auth',
26 | controller:'CtrlAuth'
27 | })
28 |
29 | .state('invite', {
30 | url: '/invite',
31 | templateUrl: 'LavaLogin/login/invite'
32 | })
33 |
34 | .state('secureUsername', {
35 | url: '/secure',
36 | templateUrl: 'LavaLogin/login/secureUsername',
37 | controller:'CtrlSecureUsername'
38 | })
39 |
40 | .state('reservedUsername', {
41 | url: '/reserved',
42 | templateUrl: 'LavaLogin/login/reservedUsername',
43 | controller:'CtrlReservedUsername'
44 | })
45 |
46 | .state('verifyInvite', {
47 | url: '/verify',
48 | templateUrl: 'LavaLogin/login/verifyInvite',
49 | controller:'CtrlVerify'
50 | })
51 |
52 | .state('verifyInviteConfigured', {
53 | url: '/verify/{userName}/{inviteCode}',
54 | templateUrl: 'LavaLogin/login/verifyInvite',
55 | controller:'CtrlVerify'
56 | })
57 |
58 | .state('plan', {
59 | url: '/plan',
60 | templateUrl: 'LavaLogin/login/plan',
61 | controller:'CtrlSelectPlan'
62 | })
63 |
64 | .state('details', {
65 | url: '/details',
66 | templateUrl: 'LavaLogin/login/details',
67 | controller:'CtrlDetails'
68 | })
69 |
70 | .state('choosePassword', {
71 | url: '/password',
72 | templateUrl: 'LavaLogin/login/choosePassword',
73 | controller:'CtrlPassword'
74 | })
75 |
76 | .state('choosePasswordIntro', {
77 | url: '/password/intro',
78 | templateUrl: 'LavaLogin/login/choosePasswordIntro',
79 | controller:'CtrlPassword'
80 | })
81 |
82 | .state('generateKeys', {
83 | url: '/keys/intro',
84 | templateUrl: 'LavaLogin/login/generateKeys',
85 | controller: 'CtrlGenerateKeys'
86 | })
87 |
88 | .state('generatingKeys', {
89 | url: '/keys',
90 | templateUrl: 'LavaLogin/login/generatingKeys',
91 | controller: 'CtrlGeneratingKeys'
92 | })
93 |
94 | .state('backupKeys', {
95 | url: '/keys/backup',
96 | templateUrl: 'LavaLogin/login/backupKey',
97 | controller: 'CtrlBackup'
98 | })
99 |
100 | .state('importKeys', {
101 | url: '/keys/import',
102 | templateUrl: 'LavaLogin/login/importKey'
103 | });
104 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/inbox/threads.jade:
--------------------------------------------------------------------------------
1 | .filters.row.no-gutter
2 | nav.navbar.navbar-inverse(role="navigation")
3 | div
4 | ul.nav.navbar-nav.navbar-right
5 | li
6 | button.btn.btn-default(ng-hide="searchText", ng-click="refresh()", ng-autodisable, tooltip = "{{'LAVAMAIL.INBOX.TITLE_REFRESH_INBOX' | translate}}", tooltip-placement="bottom", tooltip-append-to-body="true")
7 | span.icon-refresh
8 | button.btn.btn-default(ng-show="searchText", ng-click="clearSearch()", ng-autodisable, tooltip = "{{'LAVAMAIL.INBOX.CLEAR_SEARCH' | translate}}", tooltip-placement="bottom", tooltip-append-to-body="true")
9 | span.icon-cancel
10 | form.navbar-form-alt
11 | .form-group
12 | .input-group
13 | label.search-icon.input-group-addon(for="top-search")
14 | span.icon-search
15 | input#top-search.search.form-control(type="text", placeholder="{{'LAVAMAIL.INBOX.PLC_SEARCH' | translate}}", ng-model="searchText")
16 | .mail-list-group
17 | div.pane-status(ng-show="isLoadingSign && threadsList.length < 1")
18 | img(src="/img/loader.svg")
19 | div.pane-status(ng-show="!isLoading && threadsList.length < 1") {{'LAVAMAIL.INBOX.LB_NOTHING_FOUND' | translate}}
20 | div(infinite-scroll="scroll()", infinite-scroll-disabled="isLoading || isDisabledScroll", infinite-scroll-parent="true")
21 | div.item-wrapper(
22 | ng-repeat="thread in threadsList | filter: searchFilter as filteredThreadsList track by thread.id",
23 | ng-class="{active: selectedTid == thread.id}",
24 | ng-click="selectThread(thread.id); toggleShowDetails();",
25 | ng-show="thread.isLoaded()")
26 | .mail-list-item
27 | .mail-list-wrapper
28 | .buttons.text-center
29 | div.icon-holder.read
30 | span.icon-star(ng-show="thread.isLabel('Starred') && thread.isRead", ng-class="{unread : !thread.isRead}")
31 | //- span.icon-star-outline(ng-show="!thread.isLabel('Starred') && thread.isRead")
32 | span.icon-unread(ng-show="!thread.isRead && !thread.isLabel('Starred')")
33 | div.icon-holder.reply(ng-show="thread.isReplied")
34 | span.icon-reply
35 | div.icon-holder.forward(ng-show="thread.isForwarded")
36 | span.icon-arrow-right
37 | .details
38 | p.strong.pack.title(ng-bind="thread.membersPretty | members")
39 | p.medium.subject(ng-bind="thread.subject")
40 | //- p.summary(ng-bind-html="thread.headerEmail.preview ? thread.headerEmail.preview.data.text : 'LAVAMAIL.INBOX.NO_PREVIEW_AVAILABLE' | translate")
41 | .when
42 | span.date(ng-bind="thread.created | lavadate", title = "{{thread.created | timeAgo}}")
43 | .lava-icon-row
44 | span(ng-show="thread.attachmentsCount > 0")
45 | span.icon-paper-clip
46 | span(ng-show="thread.secure == 'all'")
47 | span.icon-lock
48 | span(ng-show="thread.secure == 'none'|| thread.secure == 'some'")
49 | span.icon-unlock
50 | div.item-wrapper
51 | .mail-list-item(ng-show="isLoadingSign && threadsList.length > 0")
52 | .mail-list-wrapper
53 | div.pane-status
54 | img(src="/img/loader.svg")
55 |
--------------------------------------------------------------------------------
/src/less/lavaboom.less:
--------------------------------------------------------------------------------
1 | @bootstrap-folder: '../apps/LavaMail/bower_components/bootstrap/less/';
2 |
3 | @import (less) "../apps/LavaMail/bower_components/angular-ui-select/dist/select.css";
4 | @import (less) "../apps/LavaUtils/bower_components/angular/angular-csp.css";
5 | // @import (less) "../bower_components/angular-hotkeys/build/hotkeys.css";
6 |
7 | // Core variables and mixins
8 | @import "@{bootstrap-folder}variables.less";
9 | @import "lavaboom-variables.less";
10 | @import "@{bootstrap-folder}mixins.less";
11 |
12 | // Reset and dependencies
13 | @import "@{bootstrap-folder}normalize.less";
14 | @import "@{bootstrap-folder}print.less";
15 |
16 | // Core CSS
17 | @import "@{bootstrap-folder}scaffolding.less";
18 | @import "@{bootstrap-folder}type.less";
19 | @import "@{bootstrap-folder}code.less";
20 | @import "@{bootstrap-folder}grid.less";
21 | @import "@{bootstrap-folder}tables.less";
22 | @import "@{bootstrap-folder}forms.less";
23 | @import "@{bootstrap-folder}buttons.less";
24 |
25 | // Components
26 | @import "@{bootstrap-folder}component-animations.less";
27 | @import "@{bootstrap-folder}dropdowns.less";
28 | @import "@{bootstrap-folder}button-groups.less";
29 | @import "@{bootstrap-folder}input-groups.less";
30 | @import "@{bootstrap-folder}navs.less";
31 | @import "@{bootstrap-folder}navbar.less";
32 | @import "@{bootstrap-folder}breadcrumbs.less";
33 | @import "@{bootstrap-folder}pagination.less";
34 | @import "@{bootstrap-folder}pager.less";
35 | @import "@{bootstrap-folder}labels.less";
36 | @import "@{bootstrap-folder}badges.less";
37 | @import "@{bootstrap-folder}jumbotron.less";
38 | @import "@{bootstrap-folder}thumbnails.less";
39 | @import "@{bootstrap-folder}alerts.less";
40 | @import "@{bootstrap-folder}progress-bars.less";
41 | @import "@{bootstrap-folder}media.less";
42 | @import "@{bootstrap-folder}list-group.less";
43 | @import "@{bootstrap-folder}panels.less";
44 | @import "@{bootstrap-folder}responsive-embed.less";
45 | @import "@{bootstrap-folder}wells.less";
46 | @import "@{bootstrap-folder}close.less";
47 |
48 | // Components w/ JavaScript
49 | @import "@{bootstrap-folder}modals.less";
50 | @import "@{bootstrap-folder}tooltip.less";
51 | @import "@{bootstrap-folder}popovers.less";
52 | @import "@{bootstrap-folder}carousel.less";
53 |
54 | // Utility classes
55 | @import "@{bootstrap-folder}utilities.less";
56 | @import "@{bootstrap-folder}responsive-utilities.less";
57 |
58 | //LAVABOOM STUFF
59 | @import "type.less";
60 | @import "login.less";
61 | @import "main.less";
62 | @import "left-panel.less";
63 | @import "notifications.less";
64 | @import "mail-list.less";
65 | @import "mail-thread.less";
66 | @import "actions.less";
67 | @import "forms.less";
68 | @import "dropdowns.less";
69 | @import "contacts.less";
70 | @import "lavaboom-icons.less";
71 | @import "selectize.less";
72 | @import "compose.less";
73 | @import "quirks.less";
74 | @import "hotkeys.less";
75 | @import "remixins.less";
76 | @import "radial-progress.less";
77 | @import "responsive.less";
78 | @import "print.less";
--------------------------------------------------------------------------------
/src/less/radial-progress.less:
--------------------------------------------------------------------------------
1 | .radial-progress {
2 | @circle-size: 44px;
3 | @circle-background: @lava-gray-lighter;
4 | @circle-color: @lava;
5 | @inset-size: 34px;
6 | @inset-color: #F5F5F5;
7 | @transition-length: 1s;
8 | @shadow: none;
9 | @percentage-color: #97a71d;
10 | @percentage-font-size: 22px;
11 | @percentage-text-width: 57px;
12 |
13 | margin: 0px;
14 | width: @circle-size;
15 | height: @circle-size;
16 |
17 | background-color: @circle-background;
18 | border-radius: 50%;
19 | .circle {
20 | .mask, .fill, .shadow {
21 | width: @circle-size;
22 | height: @circle-size;
23 | position: absolute;
24 | border-radius: 50%;
25 | }
26 | .shadow {
27 | box-shadow: @shadow inset;
28 | }
29 | .mask, .fill {
30 | -webkit-backface-visibility: hidden;
31 | transition: -webkit-transform @transition-length;
32 | transition: -ms-transform @transition-length;
33 | transition: transform @transition-length;
34 | border-radius: 50%;
35 | }
36 | .mask {
37 | clip: rect(0px, @circle-size, @circle-size, @circle-size/2);
38 | .fill {
39 | clip: rect(0px, @circle-size/2, @circle-size, 0px);
40 | background-color: @circle-color;
41 | }
42 | }
43 | }
44 | .inset {
45 |
46 | width: @inset-size;
47 | height: @inset-size;
48 | position: absolute;
49 | margin-left: (@circle-size - @inset-size)/2;
50 | margin-top: (@circle-size - @inset-size)/2;
51 |
52 | background-color: @inset-color;
53 | border-radius: 50%;
54 | // box-shadow: @shadow;
55 | .percentage {
56 | display: none;
57 | height: @percentage-font-size;
58 | width: @percentage-text-width;
59 | overflow: hidden;
60 |
61 | position: absolute;
62 | top: (@inset-size - @percentage-font-size) / 2;
63 | left: (@inset-size - @percentage-text-width) / 2;
64 |
65 | line-height: 1;
66 | .numbers {
67 | margin-top: -@percentage-font-size;
68 | transition: width @transition-length;
69 | span {
70 | width: @percentage-text-width;
71 | display: inline-block;
72 | vertical-align: top;
73 | text-align: center;
74 | font-weight: 800;
75 | font-size: @percentage-font-size;
76 | color: @percentage-color;
77 | }
78 | }
79 | }
80 | }
81 |
82 | @i: 0;
83 | @increment: 180deg / 100;
84 | .loop (@i) when (@i <= 100) {
85 | &[data-progress="@{i}"] {
86 | .circle {
87 | .mask.full, .fill {
88 | -webkit-transform: rotate(@increment * @i);
89 | -ms-transform: rotate(@increment * @i);
90 | transform: rotate(@increment * @i);
91 | }
92 | .fill.fix {
93 | -webkit-transform: rotate(@increment * @i * 2);
94 | -ms-transform: rotate(@increment * @i * 2);
95 | transform: rotate(@increment * @i * 2);
96 | }
97 | }
98 | .inset .percentage .numbers {
99 | width: @i * @percentage-text-width + @percentage-text-width;
100 | }
101 | }
102 | .loop(@i + 1);
103 | }
104 | .loop(@i);
105 | }
--------------------------------------------------------------------------------
/src/apps/LavaUtils/services/tests.js:
--------------------------------------------------------------------------------
1 | module.exports = function($rootScope, $timeout, $state, $translate, $http,
2 | co, notifications, crypto, user, utils) {
3 | const self = this;
4 |
5 | const notifications18n = {
6 | WEB_CRYPTO_IS_NOT_AVAILABLE_TITLE: '',
7 | WEB_CRYPTO_IS_NOT_AVAILABLE_TEXT: '',
8 | WEB_WORKERS_IS_NOT_AVAILABLE_TITLE: '',
9 | WEB_WORKERS_IS_NOT_AVAILABLE_TEXT: '',
10 | SERVED_BY_TITLE: '%',
11 | SERVED_BY_TEXT: '%',
12 | SERVED_BY_UNKNOWN_TITLE: '',
13 | SERVED_BY_UNKNOWN_TEXT: '',
14 | NO_KEY_TITLE: '',
15 | NO_KEY_TEXT: '',
16 | MOBILE_BROWSER_TITLE: '',
17 | MOBILE_BROWSER_TEXT: ''
18 | };
19 |
20 | let poweredBy = '';
21 |
22 | this.initialize = () => co(function *(){
23 | yield $translate.bindAsObject(notifications18n, 'NOTIFICATIONS');
24 | });
25 |
26 | this.performCompatibilityChecks = () => co(function *(){
27 | let browser = utils.getBrowser();
28 | if (browser.isMobile) {
29 | notifications.set('mobile-platform', {
30 | title: notifications18n.MOBILE_BROWSER_TITLE,
31 | text: notifications18n.MOBILE_BROWSER_TEXT,
32 | type: 'warning'
33 | });
34 | }
35 |
36 | if (!self.isWebWorkers())
37 | notifications.set('web-workers', {
38 | title: notifications18n.WEB_WORKERS_IS_NOT_AVAILABLE_TITLE,
39 | text: notifications18n.WEB_WORKERS_IS_NOT_AVAILABLE_TEXT
40 | });
41 |
42 | if (!self.isWebCrypto())
43 | notifications.set('web-crypto', {
44 | title: notifications18n.WEB_CRYPTO_IS_NOT_AVAILABLE_TITLE,
45 | text: notifications18n.WEB_CRYPTO_IS_NOT_AVAILABLE_TEXT
46 | });
47 |
48 | let headRes = yield $http.head('/');
49 | poweredBy = headRes.headers('X-Powered-By');
50 |
51 | if (poweredBy) {
52 | notifications.set('powered-by', {
53 | title: notifications18n.SERVED_BY_TITLE({servedBy: poweredBy}),
54 | text: notifications18n.SERVED_BY_TEXT({servedBy: poweredBy}),
55 | namespace: 'status',
56 | type: 'info'
57 | });
58 | } else {
59 | notifications.set('powered-by', {
60 | title: notifications18n.SERVED_BY_UNKNOWN_TITLE,
61 | text: notifications18n.SERVED_BY_UNKNOWN_TEXT,
62 | namespace: 'status',
63 | type: 'warning'
64 | });
65 | notifications.set('powered-by', {
66 | title: notifications18n.SERVED_BY_UNKNOWN_TITLE,
67 | text: notifications18n.SERVED_BY_UNKNOWN_TEXT,
68 | type: 'warning'
69 | });
70 | }
71 |
72 | let list = crypto.getAvailablePrivateKeysForEmail(user.email);
73 | if (list.length < 1 || list.every(k => !k.primaryKey.isDecrypted)) {
74 | notifications.set('no-key', {
75 | title: notifications18n.NO_KEY_TITLE,
76 | text: notifications18n.NO_KEY_TEXT,
77 | type: 'warning',
78 | namespace: 'mailbox',
79 | kind: 'crypto',
80 | onRemove: () => {
81 | $state.go('main.settings.security');
82 | }
83 | });
84 | }
85 | });
86 |
87 | this.isWebCrypto = () => {
88 | return window.crypto || window.msCrypto;
89 | };
90 |
91 | this.isWebWorkers = () => co(function *(){
92 | return !!window.Worker;
93 | });
94 |
95 | this.getPoweredBy = () => poweredBy;
96 | };
--------------------------------------------------------------------------------
/src/apps/LavaMail/factories/thread.js:
--------------------------------------------------------------------------------
1 | const utf8 = require('utf8');
2 |
3 | module.exports = ($injector, $translate, co, utils, crypto, user, Email, Manifest) => {
4 | const translations = {
5 | LB_EMAIL_TO_YOURSELF: ''
6 | };
7 | $translate.bindAsObject(translations, 'LAVAMAIL.INBOX');
8 |
9 | function Thread(opt, manifest, labels) {
10 | const self = this;
11 | let isLoaded = false;
12 |
13 | const prettify = (a) => {
14 | let r = a
15 | .map(e => e.address == user.email ? '' : e.prettyName)
16 | .filter(e => !!e);
17 |
18 | if (r.length < 1) {
19 | r = [translations.LB_EMAIL_TO_YOURSELF];
20 | self.isToYourself = true;
21 | }
22 |
23 | return r;
24 | };
25 |
26 | const filterMembers = (members) => {
27 | let myself = null;
28 | members = members.
29 | map(e => {
30 | if (e.address == user.email || user.aliases.includes(e.address)){
31 | myself = e;
32 | return null;
33 | }
34 | return e;
35 | })
36 | .filter(e => !!e);
37 |
38 | if (members.length < 1 && myself)
39 | members = [myself];
40 |
41 | return members;
42 | };
43 |
44 | self.id = opt.id;
45 | self.created = opt.date_created;
46 | self.modified = opt.date_modified;
47 | self.isToYourself = false;
48 |
49 | self.members = opt.members && opt.members.length > 0 ? filterMembers(Manifest.parseAddresses(opt.members)) : [];
50 | self.membersPretty = prettify(self.members);
51 | self.labels = opt.labels && opt.labels.length > 0 ? opt.labels : [];
52 | self.isRead = !!opt.is_read;
53 | self.secure = opt.secure;
54 | self.isReplied = opt.emails.length > 1;
55 | self.isForwarded = Email.getSubjectWithoutRe(self.subject) != self.subject;
56 |
57 | this.setupManifest = (manifest, setIsLoaded = false) => {
58 | isLoaded = setIsLoaded;
59 |
60 | self.to = manifest ? manifest.to : [];
61 |
62 | self.subject = manifest && manifest.subject ? manifest.subject : opt.subject;
63 | if (!self.subject)
64 | self.subject = '';
65 |
66 | self.attachmentsCount = manifest && manifest.files ? manifest.files.length : 0;
67 | };
68 |
69 | this.isLoaded = () => isLoaded;
70 | this.isLabel = (labelName) => self.labels.some(lid => labels.byId[lid] && labels.byId[lid].name == labelName);
71 | this.addLabel = (labelName) => utils.uniq(self.labels.concat(labels.byName[labelName].id));
72 | this.removeLabel = (labelName) => self.labels.filter(x => x != labels.byName[labelName].id);
73 |
74 | self.setupManifest(manifest);
75 | }
76 |
77 | Thread.fromEnvelope = (envelope) => co(function *() {
78 | let inbox = $injector.get('inbox');
79 | let labels = yield inbox.getLabels();
80 |
81 | let thread = new Thread(envelope, null, labels);
82 |
83 | co(function *(){
84 | let manifestRaw;
85 | try {
86 | manifestRaw = yield co.def(crypto.decodeRaw(envelope.manifest), null);
87 | console.log('thread manifest', manifestRaw);
88 | } catch (err) {
89 | thread.setupManifest(null, true);
90 | throw err;
91 | }
92 | thread.setupManifest(manifestRaw ? Manifest.createFromJson(manifestRaw) : null, true);
93 | });
94 |
95 | return thread;
96 | });
97 |
98 | return Thread;
99 | };
--------------------------------------------------------------------------------
/src/apps/LavaUtils/factories/cache.js:
--------------------------------------------------------------------------------
1 | module.exports = (co) => {
2 | function Cache (name, opts = {}) {
3 | let cacheByKey = {};
4 | let cacheById = {};
5 | const self = this;
6 |
7 | console.log('construct Cache instance name: ', name, 'options:', opts);
8 |
9 | if (!opts.unfold)
10 | opts.unfold = null;
11 | if (!opts.list)
12 | opts.list = value => value;
13 |
14 | this.get = (key) => {
15 | const r = cacheByKey[key];
16 | if (!r)
17 | return null;
18 |
19 | const now = new Date();
20 |
21 | if (now - r.time > opts.ttl) {
22 | console.log('cache(1) ', name, 'invalidate:', now, r.time);
23 | self.invalidate(key);
24 |
25 | return null;
26 | }
27 |
28 | return r.value;
29 | };
30 |
31 | this.getById = (id) => {
32 | const r = cacheById[id];
33 | if (!r)
34 | return null;
35 |
36 | const rList = cacheByKey[r.key];
37 |
38 | const now = new Date();
39 |
40 | if (now - rList.time > opts.ttl) {
41 | self.invalidate(r.key);
42 |
43 | return null;
44 | }
45 |
46 | return r.value;
47 | };
48 |
49 | this.removeById = (id) => {
50 | for(let key of Object.keys(cacheByKey)) {
51 | const list = opts.list(cacheByKey[key].value);
52 | let index = list.findIndex(item => opts.unfold(item) == id);
53 | if (index > -1)
54 | list.splice(index, 1);
55 | }
56 | delete cacheById[id];
57 | };
58 |
59 | this.exposeIds = () =>
60 | Object.keys(cacheById).reduce((a, id) => {
61 | a[id] = cacheById[id].value;
62 | return a;
63 | }, {});
64 |
65 | this.exposeKeys = (key) => cacheByKey[key] ? cacheByKey[key].value : null;
66 |
67 | this.put = (key, value) => {
68 | const date = new Date();
69 |
70 | cacheByKey[key] = {
71 | value: value,
72 | time: date
73 | };
74 |
75 | if (opts.unfold)
76 | opts.list(value).forEach(item => {
77 | const id = opts.unfold(item);
78 | cacheById[id] = {
79 | value: item,
80 | key: key
81 | };
82 | });
83 | };
84 |
85 | this.unshiftOnlyIfKeyIsPreset = (key, item) => {
86 | const r = cacheByKey[key];
87 | if (!r)
88 | return false;
89 |
90 | const now = new Date();
91 |
92 | if (now - r.time > opts.ttl) {
93 | console.log('cache(3) ', name, 'invalidate:', now, r.time);
94 | self.invalidate(key);
95 |
96 | return null;
97 | }
98 |
99 | opts.list(r.value).unshift(item);
100 |
101 | if (opts.unfold) {
102 | const id = opts.unfold(item);
103 | cacheById[id] = {
104 | value: item,
105 | key: key
106 | };
107 | }
108 |
109 | return true;
110 | };
111 |
112 | this.invalidate = (key) => {
113 | if (cacheByKey[key]) {
114 | const value = cacheByKey[key].value;
115 | if (opts.unfold)
116 | opts.list(value).forEach(item => {
117 | const id = opts.unfold(item);
118 | delete cacheById[id];
119 | });
120 | delete cacheByKey[key];
121 | }
122 | };
123 |
124 | this.invalidateAll = () => {
125 | cacheByKey = {};
126 | cacheById = {};
127 | };
128 | }
129 |
130 | return Cache;
131 | };
--------------------------------------------------------------------------------
/src/img/loader.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/apps/LavaMail/blocks/contacts/_contactsProfileEmail.jade:
--------------------------------------------------------------------------------
1 | li.list-group-item(ng-class="{ 'collapse-active': !currentEmail.isCollapsed() }")
2 | div.input-group
3 | button.btn.btn-remove(type="button", ng-click="remove()", ng-show="isEditMode")
4 | span.icon-delete
5 | input.form-control(type="text", ng-disabled="!isEditMode", focus="{{currentEmail.isJustAdded()}}", name="email-{{$index}}", ng-model="currentEmail.name", required,
6 | placeholder="{{'LAVAMAIL.CONTACTS.PLC_EMAIL' | translate}}", tooltip="{{currentEmail.getTooltip()}}", tooltip-trigger="focus", tooltip-placement="right")
7 | div.input-group-btn
8 | button.btn.tggl(type="button", ng-click="starEmail()")
9 | span.icon-star(ng-show="currentEmail.isStar")
10 | span.icon-star-outline(ng-show="!currentEmail.isStar")
11 | span.dropdown(dropdown="", ng-show="!currentEmail.isSecured()")
12 | button.btn.btn-default.dropdown-toggle(type="button", dropdown-toggle="")
13 | span.icon-unlock(ng-show="!currentEmail.isSecured()")
14 | ul.dropdown-menu.pull-right(role="menu")
15 | li
16 | p {{'LAVAMAIL.CONTACTS.PUBLIC_KEY_PROMPT1' | translate}}
17 | p {{'LAVAMAIL.CONTACTS.PUBLIC_KEY_PROMPT2' | translate}}
18 | li.yesno.table-list
19 | a.btn.cell(type="button", ng-click="requestPublicKey()")
20 | span.icon-public-key-send
21 | span {{'LAVAMAIL.CONTACTS.PUBLIC_KEY_PROMPT_REQUEST' | translate}}
22 | a.btn.cell(type="button", open-file="uploadPublicKey(data)")
23 | span.icon-public-key-upload
24 | span {{'LAVAMAIL.CONTACTS.PUBLIC_KEY_PROMPT_UPLOAD' | translate}}
25 | button.btn.btn-default(type="button", ng-show="currentEmail.isSecured()", ng-click="currentEmail.switchCollapse()")
26 | span.icon-lock
27 | li.list-group-item.pack(ng-show="currentEmail.isSecured()", collapse="currentEmail.isCollapsed()")
28 | ul.child-item
29 | li.list-group-item
30 | .text-control
31 | label {{'LAVAMAIL.CONTACTS.LB_KEY_FINGERPRINT' | translate}}
32 | div.form-control.disable-styles(ng-bind="currentEmail.key.fingerprintPretty")
33 | li.list-group-item
34 | .text-control
35 | label {{'LAVAMAIL.CONTACTS.LB_LENGTH' | translate}}
36 | input.field-icon.form-control(disabled, ng-model="currentEmail.key.length")
37 | li.list-group-item
38 | .text-control
39 | label {{'LAVAMAIL.CONTACTS.LB_ALGORITHMS' | translate}}
40 | input.field-icon.form-control(disabled, ng-model="currentEmail.key.algos")
41 | li.list-group-item
42 | .button-holder
43 | button.cell.btn.btn-default(type="button", ng-click="downloadPublicKey()")
44 | span.icon-arrow-down
45 | | {{'LAVAMAIL.CONTACTS.LB_DOWNLOAD_PUBLIC_KEY' | translate}}
46 | button.cell.btn.btn-default(type="button", ng-click="remove()", ng-show="isEditMode")
47 | span.icon-trash
48 | | {{'LAVAMAIL.CONTACTS.LB_REMOVE' | translate}}
49 | //- div
50 | //- div.input-group.with-label
51 | //- label {{'LAVAMAIL.CONTACTS.LB_DOWNLOAD_PUBLIC_KEY' | translate}}
52 | //- div.input-group-btn
53 | //- button.btn.btn-default(type="button", ng-click="downloadPublicKey()")
54 | //- span.icon-arrow-down
55 | //- div
56 | //- div.input-group.with-label
57 | //- label {{'LAVAMAIL.CONTACTS.LB_REMOVE' | translate}}
58 | //- div.input-group-btn
59 | //- button.btn.btn-default(type="button", ng-click="remove()")
60 | //- span.icon-trash
--------------------------------------------------------------------------------
/src/apps/LavaLogin/controllers/ctrlGeneratingKeys.js:
--------------------------------------------------------------------------------
1 | module.exports = ($rootScope, $scope, $state, $interval, $timeout, $translate, consts, crypto, user, co, signUp) => {
2 | if (!user.isAuthenticated())
3 | $state.go('login');
4 |
5 | let timePassed = 0;
6 | $scope.progress = 0;
7 | $scope.label = '';
8 | $scope.subLabel = '';
9 |
10 | const translations = {
11 | LB_GENERATING : '',
12 | LB_READY : '',
13 | LB_REACHED : '',
14 | LB_ERROR : '',
15 | LB_UPLOADING : '',
16 | LB_PERFORMANCE_TEST: '',
17 | LB_DO_NOT_CLOSE_REF: '',
18 | LB_DO_NOT_CLOSE_HAMSTER: '',
19 | LB_DO_NOT_CLOSE_TURTLE: ''
20 | };
21 |
22 | $translate.bindAsObject(translations, 'LAVALOGIN.GENERATING_KEYS');
23 |
24 | let progressBarInterval;
25 | progressBarInterval = $interval(() => {
26 | $scope.progress = Math.floor(++timePassed / consts.ESTIMATED_KEY_GENERATION_TIME_SECONDS * 95);
27 | if ($scope.progress >= 95) {
28 | $scope.label = translations.LB_REACHED;
29 |
30 | $interval.cancel(progressBarInterval);
31 | progressBarInterval = null;
32 | }
33 | }, 1000);
34 |
35 | function performanceTest(count, keyLength) {
36 | return co(function *(){
37 | let start = new Date().getTime();
38 | for(let i = 0; i < count; i++)
39 | yield crypto.generateKeys('test ', 'test', keyLength);
40 | let end = new Date().getTime();
41 |
42 | return end - start;
43 | });
44 | }
45 |
46 | co(function *() {
47 | try {
48 | $scope.label = translations.LB_PERFORMANCE_TEST;
49 | $scope.subLabel = translations.LB_DO_NOT_CLOSE_REF;
50 |
51 | //CRYPTO_PERFORMANCE_TEST_REF_TIME
52 | let tookMs = yield performanceTest(consts.CRYPTO_PERFORMANCE_TEST_COUNT, consts.CRYPTO_PERFORMANCE_TEST_KEY_LENGTH);
53 | console.log('performance test: ', tookMs);
54 |
55 | if (tookMs > crypto.CRYPTO_PERFORMANCE_TEST_REF_TIME * 10)
56 | $scope.subLabel = translations.LB_DO_NOT_CLOSE_TURTLE;
57 | else
58 | if (tookMs > crypto.CRYPTO_PERFORMANCE_TEST_REF_TIME * 5)
59 | $scope.subLabel = translations.LB_DO_NOT_CLOSE_HAMSTER;
60 |
61 | $scope.label = translations.LB_GENERATING;
62 |
63 | if (!user.nameEmail)
64 | throw new Error('wups');
65 |
66 | let res = yield crypto.generateKeys(user.nameEmail, signUp.password, consts.DEFAULT_KEY_LENGTH);
67 | console.log('login app: keys generated', res);
68 |
69 | crypto.importPublicKey(res.pub);
70 | crypto.importPrivateKey(res.prv);
71 | console.log('login app: keys imported');
72 |
73 | if (progressBarInterval) {
74 | $interval.cancel(progressBarInterval);
75 | progressBarInterval = null;
76 | }
77 | $scope.label = translations.LB_UPLOADING;
78 |
79 | yield user.syncKeys();
80 | try {
81 | yield user.updateKey(res.prv.primaryKey.fingerprint);
82 | } catch (err) {
83 | console.error(err);
84 | }
85 |
86 | crypto.authenticateByEmail(user.email, signUp.password);
87 | crypto.storeKeyring();
88 |
89 | $scope.progress = 100;
90 | $scope.label = translations.LB_READY;
91 |
92 | $timeout(() => {
93 | $state.go('backupKeys');
94 | }, consts.LAVABOOM_SYNC_REDIRECT_DELAY);
95 | } catch (err) {
96 | console.log('login app: keys generation error', err);
97 |
98 | if (progressBarInterval) {
99 | $interval.cancel(progressBarInterval);
100 | progressBarInterval = null;
101 | }
102 | $scope.label = translations.LB_ERROR;
103 | }
104 | });
105 | };
106 |
--------------------------------------------------------------------------------
/src/img/Lavaboom-logo-full-min.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------