├── .drone.yml ├── .eslintrc ├── .gitignore ├── .jshintrc ├── .travis.yml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── fabfile.py ├── gulp ├── browserifyNgAnnotateHelper.js ├── config.js ├── draft.js ├── paths.js ├── pipelines.js ├── plugins.js └── utils.js ├── gulpfile-es6.js ├── gulpfile.js ├── karma-integration.conf.js ├── karma-unit.conf.js ├── manifest.json ├── package.json ├── prepare-env.sh ├── serve.js ├── src ├── apps │ ├── LavaLoader │ │ ├── .bowerrc │ │ ├── index.toml │ │ └── js │ │ │ ├── host.js │ │ │ ├── index.js │ │ │ └── loader.js │ ├── LavaLogin │ │ ├── .bowerrc │ │ ├── blocks │ │ │ ├── login │ │ │ │ ├── auth.jade │ │ │ │ ├── backupKey.jade │ │ │ │ ├── choosePassword.jade │ │ │ │ ├── choosePasswordIntro.jade │ │ │ │ ├── details.jade │ │ │ │ ├── generateKeys.jade │ │ │ │ ├── generatingKeys.jade │ │ │ │ ├── importKey.jade │ │ │ │ ├── invite.jade │ │ │ │ ├── loginOrSignup.jade │ │ │ │ ├── plan.jade │ │ │ │ ├── reservedUsername.jade │ │ │ │ ├── secureUsername.jade │ │ │ │ └── verifyInvite.jade │ │ │ └── misc │ │ │ │ └── validationMessages.jade │ │ ├── configs │ │ │ ├── .gitkeep │ │ │ └── cfgLoginNavigation.js │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ ├── ctrlAuth.js │ │ │ ├── ctrlBackup.js │ │ │ ├── ctrlDetails.js │ │ │ ├── ctrlGenerateKeys.js │ │ │ ├── ctrlGeneratingKeys.js │ │ │ ├── ctrlLavaboomLogin.js │ │ │ ├── ctrlPassword.js │ │ │ ├── ctrlReservedUsername.js │ │ │ ├── ctrlSecureUsername.js │ │ │ ├── ctrlSelectPlan.js │ │ │ └── ctrlVerify.js │ │ ├── directives │ │ │ ├── .gitkeep │ │ │ ├── dictionary.js │ │ │ └── maxRepeating.js │ │ ├── index.toml │ │ ├── package.json │ │ ├── services │ │ │ ├── .gitkeep │ │ │ └── signUp.js │ │ └── vendor │ │ │ └── .gitkeep │ ├── LavaMail │ │ ├── .bowerrc │ │ ├── blocks │ │ │ ├── compose │ │ │ │ ├── _composeEmail.jade │ │ │ │ └── compose.jade │ │ │ ├── contacts │ │ │ │ ├── _contactsList.jade │ │ │ │ ├── _contactsProfileEmail.jade │ │ │ │ ├── contacts.jade │ │ │ │ ├── contactsProfile.jade │ │ │ │ └── import.jade │ │ │ ├── directives │ │ │ │ ├── emailContextMenu.jade │ │ │ │ ├── loading.jade │ │ │ │ ├── noImage.jade │ │ │ │ └── snap.jade │ │ │ ├── inbox │ │ │ │ ├── _attachment.jade │ │ │ │ ├── _attachmentProgress.jade │ │ │ │ ├── directEmail.jade │ │ │ │ ├── download.jade │ │ │ │ ├── emails.jade │ │ │ │ ├── forwardedEmail.jade │ │ │ │ ├── inbox.jade │ │ │ │ ├── repliedEmail.jade │ │ │ │ └── threads.jade │ │ │ ├── misc │ │ │ │ ├── _notifications.jade │ │ │ │ ├── hotkeys.jade │ │ │ │ └── lsOff.jade │ │ │ ├── navigation │ │ │ │ └── navigation.jade │ │ │ └── settings │ │ │ │ ├── _settingsList.jade │ │ │ │ ├── settings.jade │ │ │ │ ├── settingsGeneral.jade │ │ │ │ ├── settingsPlan.jade │ │ │ │ ├── settingsProfile.jade │ │ │ │ └── settingsSecurity.jade │ │ ├── bower.json │ │ ├── configs │ │ │ ├── .gitkeep │ │ │ ├── cfgHotkeys.js │ │ │ └── cfgNavigation.js │ │ ├── controllers │ │ │ ├── .gitkeep │ │ │ ├── CtrlThreadList.js │ │ │ ├── ctrlCompose.js │ │ │ ├── ctrlComposeEmail.js │ │ │ ├── ctrlContactList.js │ │ │ ├── ctrlContactProfile.js │ │ │ ├── ctrlContactProfileEmail.js │ │ │ ├── ctrlDownload.js │ │ │ ├── ctrlEmailList.js │ │ │ ├── ctrlHotkeys.js │ │ │ ├── ctrlImportContacts.js │ │ │ ├── ctrlLavaboom.js │ │ │ ├── ctrlLsOff.js │ │ │ ├── ctrlNavigation.js │ │ │ ├── ctrlSettings.js │ │ │ ├── ctrlSettingsGeneral.js │ │ │ ├── ctrlSettingsList.js │ │ │ ├── ctrlSettingsPersonal.js │ │ │ ├── ctrlSettingsPlan.js │ │ │ ├── ctrlSettingsSecurity.js │ │ │ └── ctrlSettingsSecurityKey.js │ │ ├── decorators │ │ │ ├── .gitkeep │ │ │ ├── contacts.js │ │ │ ├── dialogs.js │ │ │ ├── inbox.js │ │ │ ├── taOptions.js │ │ │ └── taTools.js │ │ ├── directives │ │ │ ├── .gitkeep │ │ │ ├── customDropZone.js │ │ │ ├── emailBody.js │ │ │ ├── emailContextMenu.js │ │ │ ├── ngRightClick.js │ │ │ ├── openFile.js │ │ │ ├── openRawFile.js │ │ │ ├── tagOnFocusLoss.js │ │ │ └── textAngularFocus.js │ │ ├── factories │ │ │ ├── .gitkeep │ │ │ ├── attachment.js │ │ │ ├── contact.js │ │ │ ├── contactEmail.js │ │ │ ├── email.js │ │ │ ├── label.js │ │ │ ├── manifest.js │ │ │ └── thread.js │ │ ├── filters │ │ │ ├── .gitkeep │ │ │ ├── defaultValue.js │ │ │ ├── fileExtension.js │ │ │ ├── fileName.js │ │ │ ├── filesize.js │ │ │ ├── lavadate.js │ │ │ ├── lavaday.js │ │ │ ├── lavatime.js │ │ │ ├── members.js │ │ │ └── unread.js │ │ ├── index.toml │ │ ├── package.json │ │ ├── runs │ │ │ ├── .gitkeep │ │ │ └── rootScopeHelpers.js │ │ ├── services │ │ │ ├── .gitkeep │ │ │ ├── composeHelpers.js │ │ │ ├── contacts.js │ │ │ ├── hotkey.js │ │ │ ├── inbox.js │ │ │ ├── router.js │ │ │ └── textAngularHelpers.js │ │ ├── translations │ │ │ └── en.toml │ │ └── vendor │ │ │ └── .gitkeep │ ├── LavaUtils │ │ ├── .bowerrc │ │ ├── blocks │ │ │ ├── .gitkeep │ │ │ └── misc │ │ │ │ └── defaultSignature.jade │ │ ├── bower.json │ │ ├── configs │ │ │ ├── .gitkeep │ │ │ └── cfg.js │ │ ├── constants │ │ │ ├── .gitkeep │ │ │ └── consts.js │ │ ├── controllers │ │ │ └── ctrlChecker.js │ │ ├── decorators │ │ │ ├── $sanitize.js │ │ │ ├── $templateCache.js │ │ │ ├── $timeout.js │ │ │ ├── $translate.js │ │ │ ├── .gitkeep │ │ │ ├── LavaboomAPI.js │ │ │ └── crypto.js │ │ ├── directives │ │ │ ├── .gitkeep │ │ │ ├── a.js │ │ │ ├── fileSelect.js │ │ │ ├── focus.js │ │ │ └── match.js │ │ ├── factories │ │ │ ├── .gitkeep │ │ │ ├── cache.js │ │ │ ├── cryptoKeysStorage.js │ │ │ ├── key.js │ │ │ └── proxy.js │ │ ├── index.toml │ │ ├── package.json │ │ ├── runs │ │ │ ├── .gitkeep │ │ │ └── rootScopeHelpers.js │ │ ├── services │ │ │ ├── .gitkeep │ │ │ ├── crypto.js │ │ │ ├── cryptoKeys.js │ │ │ ├── fileReader.js │ │ │ ├── loader.js │ │ │ ├── notifications.js │ │ │ ├── saver.js │ │ │ ├── tests.js │ │ │ ├── translate.js │ │ │ ├── user.js │ │ │ └── utils.js │ │ ├── translations │ │ │ ├── .gitkeep │ │ │ ├── en.json │ │ │ └── index.json │ │ └── vendor │ │ │ ├── .gitkeep │ │ │ ├── blob.js │ │ │ ├── html-css-sanitizer.min.js │ │ │ └── html-sanitizer.min.js │ └── app.js ├── css │ ├── _lavaboom.css │ └── feather.css ├── fonts │ ├── Read Me.txt │ ├── demo-files │ │ ├── demo.css │ │ └── demo.js │ ├── demo.html │ ├── fonts │ │ ├── lavaboom.eot │ │ ├── lavaboom.svg │ │ ├── lavaboom.ttf │ │ └── lavaboom.woff │ ├── selection.json │ ├── style.css │ └── variables.scss ├── img │ ├── Lavaboom-logo-full-max.svg │ ├── Lavaboom-logo-full-min.svg │ ├── Lavaboom-logo-full.svg │ ├── Lavaboom-logo-gray.svg │ ├── Lavaboom-logo-no-shadow.svg │ ├── Lavaboom-logo-wordmark-min.svg │ ├── Lavaboom-logo.svg │ ├── assets │ │ ├── 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-57x57.png │ │ ├── apple-touch-icon-60x60.png │ │ ├── apple-touch-icon-72x72.png │ │ ├── apple-touch-icon-76x76.png │ │ ├── apple-touch-icon-precomposed.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-160x160.png │ │ ├── favicon-16x16.png │ │ ├── favicon-192x192.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── mstile-144x144.png │ │ ├── mstile-150x150.png │ │ ├── mstile-310x150.png │ │ ├── mstile-310x310.png │ │ └── mstile-70x70.png │ ├── avatar.svg │ ├── check.svg │ ├── loader.svg │ └── no-image.png ├── index.jade ├── js │ └── helpers │ │ ├── .gitkeep │ │ ├── angularApplication.js │ │ └── promise-polyfill.js ├── less │ ├── actions.less │ ├── compose.less │ ├── contacts.less │ ├── dropdowns.less │ ├── forms.less │ ├── hotkeys.less │ ├── lavaboom-icons.less │ ├── lavaboom-login.less │ ├── lavaboom-variables.less │ ├── lavaboom.less │ ├── left-panel.less │ ├── login.less │ ├── mail-list.less │ ├── mail-thread.less │ ├── main.less │ ├── mixins.less │ ├── mixins │ │ └── type.less │ ├── notifications.less │ ├── print.less │ ├── quirks.less │ ├── radial-progress.less │ ├── remixins.less │ ├── responsive.less │ ├── selectize.less │ └── type.less ├── tests │ ├── helpers │ │ └── intervalDigest.js │ ├── integration │ │ └── utils │ │ │ └── services │ │ │ └── cryptoSpec.js │ └── unit │ │ └── utils │ │ └── services │ │ └── cryptoSpec.js └── vendor │ ├── html-css-sanitizer.min.js │ ├── html-sanitizer.min.js │ └── pw.dict └── website.conf /.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 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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() -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /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 | ]); -------------------------------------------------------------------------------- /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(); -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "© Lavaboom", 3 | "version": "0.4.4" 4 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/LavaLoader/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "./bower_components", 3 | "interactive": false 4 | } -------------------------------------------------------------------------------- /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/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(); -------------------------------------------------------------------------------- /src/apps/LavaLoader/js/index.js: -------------------------------------------------------------------------------- 1 | let loader = require('./loader'); 2 | loader(window.assets); -------------------------------------------------------------------------------- /src/apps/LavaLogin/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "./bower_components", 3 | "interactive": false 4 | } -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/configs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaLogin/configs/.gitkeep -------------------------------------------------------------------------------- /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/LavaLogin/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaLogin/controllers/.gitkeep -------------------------------------------------------------------------------- /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/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/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/LavaLogin/controllers/ctrlGenerateKeys.js: -------------------------------------------------------------------------------- 1 | module.exports = ($scope, $state, user, signUp, crypto) => { 2 | if (!user.isAuthenticated()) 3 | $state.go('login'); 4 | }; -------------------------------------------------------------------------------- /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/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/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/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/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/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/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 | }; -------------------------------------------------------------------------------- /src/apps/LavaLogin/directives/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaLogin/directives/.gitkeep -------------------------------------------------------------------------------- /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/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/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/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/LavaLogin/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaLogin/services/.gitkeep -------------------------------------------------------------------------------- /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/LavaLogin/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaLogin/vendor/.gitkeep -------------------------------------------------------------------------------- /src/apps/LavaMail/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "./bower_components", 3 | "interactive": false 4 | } -------------------------------------------------------------------------------- /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/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/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/LavaMail/blocks/contacts/contacts.jade: -------------------------------------------------------------------------------- 1 | include ./_contactsList 2 | .details-pane 3 | .main-contacts-view(ui-view='') -------------------------------------------------------------------------------- /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/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/LavaMail/blocks/directives/loading.jade: -------------------------------------------------------------------------------- 1 | .pane-status(ng-show="isLoading") 2 | img(src="/img/loader.svg") -------------------------------------------------------------------------------- /src/apps/LavaMail/blocks/directives/noImage.jade: -------------------------------------------------------------------------------- 1 | div.no-image 2 | span {{'LAVAMAIL.INBOX.LB_NO_IMAGE' | translate}} -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 -------------------------------------------------------------------------------- /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/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/LavaMail/configs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaMail/configs/.gitkeep -------------------------------------------------------------------------------- /src/apps/LavaMail/configs/cfgHotkeys.js: -------------------------------------------------------------------------------- 1 | module.exports = (hotkeysProvider) => { 2 | hotkeysProvider.includeCheatSheet = false; 3 | }; -------------------------------------------------------------------------------- /src/apps/LavaMail/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaMail/controllers/.gitkeep -------------------------------------------------------------------------------- /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/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/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/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/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/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 | }; -------------------------------------------------------------------------------- /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/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/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/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 | }; -------------------------------------------------------------------------------- /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/decorators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaMail/decorators/.gitkeep -------------------------------------------------------------------------------- /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/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/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/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/directives/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaMail/directives/.gitkeep -------------------------------------------------------------------------------- /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/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/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 | }; -------------------------------------------------------------------------------- /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/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/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/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/factories/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaMail/factories/.gitkeep -------------------------------------------------------------------------------- /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/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/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/LavaMail/filters/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaMail/filters/.gitkeep -------------------------------------------------------------------------------- /src/apps/LavaMail/filters/defaultValue.js: -------------------------------------------------------------------------------- 1 | module.exports = () => 2 | (v, defaultValue) => v ? v : defaultValue; -------------------------------------------------------------------------------- /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/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/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 | }; -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/LavaMail/runs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaMail/runs/.gitkeep -------------------------------------------------------------------------------- /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/LavaMail/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaMail/services/.gitkeep -------------------------------------------------------------------------------- /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/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 | }; -------------------------------------------------------------------------------- /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/LavaMail/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaMail/vendor/.gitkeep -------------------------------------------------------------------------------- /src/apps/LavaUtils/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "./bower_components", 3 | "interactive": false 4 | } -------------------------------------------------------------------------------- /src/apps/LavaUtils/blocks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/blocks/.gitkeep -------------------------------------------------------------------------------- /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/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/LavaUtils/configs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/configs/.gitkeep -------------------------------------------------------------------------------- /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/LavaUtils/constants/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/constants/.gitkeep -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/LavaUtils/decorators/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/decorators/.gitkeep -------------------------------------------------------------------------------- /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/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/LavaUtils/directives/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/directives/.gitkeep -------------------------------------------------------------------------------- /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 | }); -------------------------------------------------------------------------------- /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/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/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/LavaUtils/factories/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/factories/.gitkeep -------------------------------------------------------------------------------- /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/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/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/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/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/LavaUtils/runs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/runs/.gitkeep -------------------------------------------------------------------------------- /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/LavaUtils/services/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/services/.gitkeep -------------------------------------------------------------------------------- /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/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/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 | }; -------------------------------------------------------------------------------- /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/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/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 | }; -------------------------------------------------------------------------------- /src/apps/LavaUtils/translations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/translations/.gitkeep -------------------------------------------------------------------------------- /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/LavaUtils/vendor/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/apps/LavaUtils/vendor/.gitkeep -------------------------------------------------------------------------------- /src/apps/app.js: -------------------------------------------------------------------------------- 1 | require('AngularApplication'); -------------------------------------------------------------------------------- /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/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/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/fonts/fonts/lavaboom.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/fonts/fonts/lavaboom.eot -------------------------------------------------------------------------------- /src/fonts/fonts/lavaboom.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/fonts/fonts/lavaboom.ttf -------------------------------------------------------------------------------- /src/fonts/fonts/lavaboom.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/fonts/fonts/lavaboom.woff -------------------------------------------------------------------------------- /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/img/Lavaboom-logo-no-shadow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /src/img/assets/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/apple-touch-icon.png -------------------------------------------------------------------------------- /src/img/assets/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | #000000 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/img/assets/favicon-160x160.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/favicon-160x160.png -------------------------------------------------------------------------------- /src/img/assets/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/favicon-16x16.png -------------------------------------------------------------------------------- /src/img/assets/favicon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/favicon-192x192.png -------------------------------------------------------------------------------- /src/img/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/favicon-32x32.png -------------------------------------------------------------------------------- /src/img/assets/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/favicon-96x96.png -------------------------------------------------------------------------------- /src/img/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/favicon.ico -------------------------------------------------------------------------------- /src/img/assets/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/mstile-144x144.png -------------------------------------------------------------------------------- /src/img/assets/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/mstile-150x150.png -------------------------------------------------------------------------------- /src/img/assets/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/mstile-310x150.png -------------------------------------------------------------------------------- /src/img/assets/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/mstile-310x310.png -------------------------------------------------------------------------------- /src/img/assets/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/assets/mstile-70x70.png -------------------------------------------------------------------------------- /src/img/avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/img/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/img/loader.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/img/no-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/img/no-image.png -------------------------------------------------------------------------------- /src/js/helpers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/js/helpers/.gitkeep -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/mixins/type.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lavab/web/0e155355d3abfcb1aa12e4c61de97d592a9939a9/src/less/mixins/type.less -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | } --------------------------------------------------------------------------------