mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-11 00:14:50 +03:00
Merge branch 'master' into plugin-2fa
This commit is contained in:
commit
7985eaa23a
303 changed files with 51374 additions and 11952 deletions
|
|
@ -34,8 +34,6 @@ module.exports = {
|
|||
'Crossroads': "readonly",
|
||||
// vendors/jua
|
||||
'Jua': "readonly",
|
||||
// vendors/qr.js
|
||||
'qr': "readonly",
|
||||
// vendors/bootstrap/bootstrap.native.js
|
||||
'BSN': "readonly"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ RewriteRule cpsess.* https://%{HTTP_HOST}/ [L,R=301]
|
|||
Header set X-XSS-Protection "1; mode=block"
|
||||
Header set Service-Worker-Allowed "/"
|
||||
|
||||
# Google FLoC
|
||||
Header set Permissions-Policy "interest-cohort=()"
|
||||
|
||||
RewriteCond %{HTTP:Accept-encoding} br
|
||||
RewriteCond "%{REQUEST_FILENAME}\.br" -s
|
||||
RewriteRule "^(.+)" "$1\.br" [L,T=text/javascript,QSA]
|
||||
|
|
|
|||
54
Makefile
54
Makefile
|
|
@ -1,75 +1,75 @@
|
|||
#!make
|
||||
|
||||
rebuild: _down
|
||||
docker-compose build --no-cache
|
||||
docker compose build --no-cache
|
||||
|
||||
up: _up status
|
||||
_up:
|
||||
docker-compose up -d
|
||||
docker compose up -d
|
||||
|
||||
stop: _stop status
|
||||
_stop:
|
||||
docker-compose stop
|
||||
docker compose stop
|
||||
|
||||
down: _down status
|
||||
_down:
|
||||
docker-compose down
|
||||
docker compose down
|
||||
|
||||
restart: _stop _up status
|
||||
|
||||
status:
|
||||
@docker-compose ps
|
||||
@docker compose ps
|
||||
|
||||
tx:
|
||||
@docker-compose run --no-deps --rm tx tx pull -a -s -f -d
|
||||
@docker compose run --no-deps --rm tx tx pull -a -s -f -d
|
||||
|
||||
console-node:
|
||||
@docker-compose run --no-deps --rm node sh
|
||||
@docker compose run --no-deps --rm node sh
|
||||
console-tx:
|
||||
@docker-compose run --no-deps --rm tx sh
|
||||
@docker compose run --no-deps --rm tx sh
|
||||
console-php:
|
||||
@docker-compose exec php sh
|
||||
@docker compose exec php sh
|
||||
console: console-node
|
||||
|
||||
logs:
|
||||
@docker-compose logs --tail=100 -f
|
||||
@docker compose logs --tail=100 -f
|
||||
logs-db:
|
||||
@docker-compose logs --tail=100 -f db
|
||||
@docker compose logs --tail=100 -f db
|
||||
logs-php:
|
||||
@docker-compose logs --tail=100 -f php
|
||||
@docker compose logs --tail=100 -f php
|
||||
logs-node:
|
||||
@docker-compose logs --tail=100 -f node
|
||||
@docker compose logs --tail=100 -f node
|
||||
logs-nginx:
|
||||
@docker-compose logs --tail=100 -f nginx
|
||||
@docker compose logs --tail=100 -f nginx
|
||||
logs-mail:
|
||||
@docker-compose logs --tail=100 -f mail
|
||||
@docker compose logs --tail=100 -f mail
|
||||
logs-tx:
|
||||
@docker-compose logs --tail=100 -f tx
|
||||
@docker compose logs --tail=100 -f tx
|
||||
|
||||
rl-lint:
|
||||
@docker-compose run --no-deps --rm node gulp lint
|
||||
@docker compose run --no-deps --rm node gulp lint
|
||||
rl-dev:
|
||||
@docker-compose run --no-deps --rm node npm run watch-js
|
||||
@docker compose run --no-deps --rm node npm run watch-js
|
||||
rl-compile:
|
||||
@docker-compose run --no-deps --rm node gulp build
|
||||
@docker compose run --no-deps --rm node gulp build
|
||||
rl-compile-with-source:
|
||||
@docker-compose run --no-deps --rm node gulp build --source
|
||||
@docker compose run --no-deps --rm node gulp build --source
|
||||
rl-watch-css:
|
||||
@docker-compose run --no-deps --rm node npm run watch-css
|
||||
@docker compose run --no-deps --rm node npm run watch-css
|
||||
rl-watch-js:
|
||||
@docker-compose run --no-deps --rm node npm run watch-js
|
||||
@docker compose run --no-deps --rm node npm run watch-js
|
||||
|
||||
rl-build:
|
||||
@docker-compose run --no-deps --rm node gulp all
|
||||
@docker compose run --no-deps --rm node gulp all
|
||||
rl-build-pro:
|
||||
@docker-compose run --no-deps --rm node gulp all --pro
|
||||
@docker compose run --no-deps --rm node gulp all --pro
|
||||
|
||||
yarn-install:
|
||||
@docker-compose run --no-deps --rm node yarn install
|
||||
@docker compose run --no-deps --rm node yarn install
|
||||
yarn-outdated:
|
||||
@docker-compose run --no-deps --rm node yarn outdated
|
||||
@docker compose run --no-deps --rm node yarn outdated
|
||||
yarn-upgrade:
|
||||
@docker-compose run --no-deps --rm node yarn upgrade-interactive --exact --latest
|
||||
@docker compose run --no-deps --rm node yarn upgrade-interactive --exact --latest
|
||||
|
||||
gpg:
|
||||
docker run -it --rm -w=/var/www \
|
||||
|
|
|
|||
48
README.md
48
README.md
|
|
@ -1,6 +1,6 @@
|
|||
<div align="center">
|
||||
<a href="https://github.com/the-djmaze/snappymail">
|
||||
<img width="200" heigth="200" src="https://snappymail.eu/static/img/logo-256x256.png">
|
||||
<img src="https://snappymail.eu/static/img/logo-256x256.png">
|
||||
</a>
|
||||
<br>
|
||||
<h1>SnappyMail</h1>
|
||||
|
|
@ -71,6 +71,9 @@ This fork of RainLoop has the following changes:
|
|||
* Replaced webpack with rollup
|
||||
* No user-agent detection (use device width)
|
||||
* Added support to load plugins as .phar
|
||||
* Replaced old Sabre library
|
||||
* AddressBook Contacts support MySQL/MariaDB utf8mb4
|
||||
* Prevent Google FLoC
|
||||
|
||||
### Removal of old JavaScript
|
||||
|
||||
|
|
@ -116,25 +119,30 @@ RainLoop 1.15 vs SnappyMail
|
|||
|
||||
|js/* |RainLoop |Snappy |
|
||||
|--------------- |--------: |--------: |
|
||||
|admin.js |2.158.025 | 94.597 |
|
||||
|app.js |4.215.733 | 460.014 |
|
||||
|boot.js | 672.433 | 4.554 |
|
||||
|libs.js | 647.679 | 218.511 |
|
||||
|admin.js |2.158.025 | 91.664 |
|
||||
|app.js |4.215.733 | 453.928 |
|
||||
|boot.js | 672.433 | 3.147 |
|
||||
|libs.js | 647.679 | 218.466 |
|
||||
|polyfills.js | 325.908 | 0 |
|
||||
|serviceworker.js | 0 | 285 |
|
||||
|TOTAL |8.019.778 | 777.961 |
|
||||
|TOTAL |8.019.778 | 767.490 |
|
||||
|
||||
|js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
||||
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|
||||
|admin.min.js | 255.514 | 49.908 | 73.899 | 14.781 | 60.674 | 13.233 |
|
||||
|app.min.js | 516.000 | 236.129 |140.430 | 69.396 |110.657 | 58.601 |
|
||||
|boot.min.js | 66.456 | 2.442 | 22.553 | 1.371 | 20.043 | 1.178 |
|
||||
|libs.min.js | 574.626 | 106.574 |177.280 | 38.600 |151.855 | 34.532 |
|
||||
|polyfills.min.js | 32.608 | 0 | 11.315 | 0 | 10.072 | 0 |
|
||||
|TOTAL |1.445.204 | 395.053 |425.477 |124.148 |353.301 |107.544 |
|
||||
|TOTAL (no admin) |1.189.690 | 345.145 |351.061 |109.367 |292.627 | 94.311 |
|
||||
|admin.min.js | 255.514 | 47.908 | 73.899 | 14.313 | 60.674 | 12.787 |
|
||||
|app.min.js | 516.000 | 233.532 |140.430 | 68.577 |110.657 | 57.878 |
|
||||
|boot.min.js | 66.456 | 1.751 | 22.553 | 1.025 | 20.043 | 858 |
|
||||
|libs.min.js | 574.626 | 106.529 |177.280 | 38.607 |151.855 | 34.567 |
|
||||
|polyfills.min.js | 32.608 | 0 | 11.315 | 0 | 10.072 | 0 |
|
||||
|TOTAL |1.445.204 | 389.720 |425.477 |122.522 |353.301 |106.090 |
|
||||
|TOTAL (no admin) |1.189.690 | 341.812 |351.061 |108.209 |292.627 | 93.303 |
|
||||
|
||||
For a user its around 66% smaller and faster than traditional RainLoop.
|
||||
For a user its around 68% smaller and faster than traditional RainLoop.
|
||||
|
||||
|OpenPGP |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
||||
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|
||||
|openpgp.min.js | 330.742 | 293.972 |102.388 | 93.030 | 84.241 | 77.142 |
|
||||
|openpgp.worker | 1.499 | 1.125 | 824 | 567 | 695 | 467 |
|
||||
|
||||
### CSS changes
|
||||
|
||||
|
|
@ -166,12 +174,12 @@ For a user its around 66% smaller and faster than traditional RainLoop.
|
|||
|
||||
|css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli |
|
||||
|------------ |-------: |-------: |------: |------: |--------: |
|
||||
|app.css | 340.334 | 107.026 | 46,959 | 18.779 | 16.133 |
|
||||
|app.min.css | 274.791 | 88.207 | 39.618 | 16.817 | 14.816 |
|
||||
|boot.css | | 2.066 | | 913 | 742 |
|
||||
|boot.min.css | | 1.696 | | 818 | 664 |
|
||||
|admin.css | | 46.326 | | 9.219 | 8.067 |
|
||||
|admin.min.css | | 37.240 | | 8.178 | 7.268 |
|
||||
|app.css | 340.334 | 100.825 | 46,959 | 18.002 | 15.540 |
|
||||
|app.min.css | 274.791 | 82.684 | 39.618 | 16.138 | 14.234 |
|
||||
|boot.css | | 1.326 | | 664 | 545 |
|
||||
|boot.min.css | | 1.071 | | 590 | 474 |
|
||||
|admin.css | | 42.217 | | 8.714 | 7.602 |
|
||||
|admin.min.css | | 33.576 | | 7.680 | 6.810 |
|
||||
|
||||
|
||||
### Squire vs CKEditor
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@
|
|||
// Uncomment to enable multiple domain installation.
|
||||
//define('MULTIDOMAIN', 1);
|
||||
|
||||
// Uncomment to disable APCU.
|
||||
//define('APP_USE_APCU_CACHE', false);
|
||||
|
||||
/**
|
||||
* Custom 'data' folder path
|
||||
* @return string
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ export class AbstractApp {
|
|||
logoutReload(close = false) {
|
||||
const url = logoutLink();
|
||||
|
||||
rl.hash.clear();
|
||||
close && window.close && window.close();
|
||||
|
||||
if (location.href !== url) {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ class AdminApp extends AbstractApp {
|
|||
|
||||
bootstart() {
|
||||
super.bootstart();
|
||||
|
||||
this.hideLoading();
|
||||
|
||||
if (!Settings.app('allowAdminPanel')) {
|
||||
rl.route.root();
|
||||
setTimeout(() => location.href = '/', 1);
|
||||
|
|
@ -30,8 +27,7 @@ class AdminApp extends AbstractApp {
|
|||
} else {
|
||||
startScreens([LoginAdminScreen]);
|
||||
}
|
||||
|
||||
progressJs.end();
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { isPosNumeric, delegateRunOnDestroy, mailToHelper } from 'Common/UtilsUs
|
|||
|
||||
import {
|
||||
Capa,
|
||||
Notification,
|
||||
Scope
|
||||
} from 'Common/Enums';
|
||||
|
||||
|
|
@ -100,9 +101,6 @@ class AppUser extends AbstractApp {
|
|||
setInterval(() => {
|
||||
const currentTime = Date.now();
|
||||
if (currentTime > (lastTime + interval + 1000)) {
|
||||
if (rl.hash.check()) {
|
||||
this.reload();
|
||||
}
|
||||
Remote.jsVersion(iError => {
|
||||
if (100 < iError) {
|
||||
this.reload();
|
||||
|
|
@ -112,10 +110,6 @@ class AppUser extends AbstractApp {
|
|||
lastTime = currentTime;
|
||||
}, interval);
|
||||
|
||||
if (rl.hash.check()) {
|
||||
this.reload();
|
||||
}
|
||||
|
||||
if (SettingsGet('UserBackgroundHash')) {
|
||||
setTimeout(() => {
|
||||
const img = userBackground(SettingsGet('UserBackgroundHash'));
|
||||
|
|
@ -886,12 +880,7 @@ class AppUser extends AbstractApp {
|
|||
}
|
||||
|
||||
logout() {
|
||||
Remote.logout(() => this.logoutReload((SettingsGet('ParentEmail')||{length:0}).length));
|
||||
}
|
||||
|
||||
bootend() {
|
||||
progressJs.end();
|
||||
this.hideLoading();
|
||||
Remote.logout(() => rl.logoutReload((SettingsGet('ParentEmail')||{length:0}).length));
|
||||
}
|
||||
|
||||
bootstart() {
|
||||
|
|
@ -907,16 +896,12 @@ class AppUser extends AbstractApp {
|
|||
|
||||
const startupUrl = pString(SettingsGet('StartupUrl'));
|
||||
|
||||
progressJs.set(90);
|
||||
|
||||
rl.setWindowTitle();
|
||||
if (SettingsGet('Auth')) {
|
||||
rl.setWindowTitle(i18n('GLOBAL/LOADING'));
|
||||
|
||||
this.foldersReload(value => {
|
||||
try {
|
||||
this.bootend();
|
||||
|
||||
if (value) {
|
||||
if (startupUrl) {
|
||||
rl.route.setHash(root(startupUrl), true);
|
||||
|
|
@ -960,6 +945,7 @@ class AppUser extends AbstractApp {
|
|||
Settings.capa(Capa.Settings) ? SettingsUserScreen : null
|
||||
// false ? AboutUserScreen : null
|
||||
]);
|
||||
this.hideLoading();
|
||||
|
||||
setInterval(() => {
|
||||
const cF = FolderUserStore.currentFolderFullNameRaw(),
|
||||
|
|
@ -1030,8 +1016,8 @@ class AppUser extends AbstractApp {
|
|||
});
|
||||
|
||||
} else {
|
||||
this.bootend();
|
||||
startScreens([LoginUserScreen]);
|
||||
this.hideLoading();
|
||||
}
|
||||
|
||||
setInterval(() => dispatchEvent(new CustomEvent('reload-time')), 60000);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
import { pString, pInt } from 'Common/Utils';
|
||||
import { Settings, SettingsGet } from 'Common/Globals';
|
||||
import { Settings } from 'Common/Globals';
|
||||
|
||||
const
|
||||
ROOT = './',
|
||||
HASH_PREFIX = '#/',
|
||||
SERVER_PREFIX = './?',
|
||||
VERSION = Settings.app('version'),
|
||||
VERSION_PREFIX = Settings.app('webVersionPath') || 'snappymail/v/' + VERSION + '/',
|
||||
|
||||
getHash = () => SettingsGet('AuthAccountHash') || '0';
|
||||
VERSION_PREFIX = Settings.app('webVersionPath') || 'snappymail/v/' + VERSION + '/';
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
|
|
@ -40,7 +38,7 @@ export function logoutLink() {
|
|||
*/
|
||||
export function serverRequestRaw(type, hash, customSpecSuffix) {
|
||||
return SERVER_PREFIX + '/Raw/' + SUB_QUERY_PREFIX + '/'
|
||||
+ (null == customSpecSuffix ? getHash() : customSpecSuffix) + '/'
|
||||
+ (null == customSpecSuffix ? '0' : customSpecSuffix) + '/'
|
||||
+ (type
|
||||
? type + '/' + (hash ? SUB_QUERY_PREFIX + '/' + hash : '')
|
||||
: '')
|
||||
|
|
@ -61,7 +59,7 @@ export function attachmentDownload(download, customSpecSuffix) {
|
|||
* @returns {string}
|
||||
*/
|
||||
export function serverRequest(type) {
|
||||
return SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/' + getHash() + '/';
|
||||
return SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/0/';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Notification, UploadErrorCode } from 'Common/Enums';
|
|||
import { langLink } from 'Common/Links';
|
||||
import { doc, createElement } from 'Common/Globals';
|
||||
|
||||
let I18N_DATA = window.snappymailI18N || {};
|
||||
let I18N_DATA = {};
|
||||
|
||||
export const trigger = ko.observable(false);
|
||||
|
||||
|
|
@ -49,10 +49,21 @@ const i18nToNode = element => {
|
|||
}
|
||||
},
|
||||
|
||||
init = () => {
|
||||
if (rl.I18N) {
|
||||
I18N_DATA = rl.I18N;
|
||||
Date.defineRelativeTimeFormat(rl.relativeTime || {});
|
||||
rl.I18N = null;
|
||||
return 1;
|
||||
}
|
||||
},
|
||||
|
||||
i18nKey = key => key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(),
|
||||
|
||||
getKeyByValue = (o, v) => Object.keys(o).find(key => o[key] === v);
|
||||
|
||||
init();
|
||||
|
||||
/**
|
||||
* @param {Object} elements
|
||||
* @param {boolean=} animate = false
|
||||
|
|
@ -127,13 +138,11 @@ export function reload(admin, language) {
|
|||
const script = createElement('script');
|
||||
script.onload = () => {
|
||||
// reload the data
|
||||
if (window.snappymailI18N) {
|
||||
I18N_DATA = window.snappymailI18N;
|
||||
if (init()) {
|
||||
i18nToNodes(doc);
|
||||
dispatchEvent(new CustomEvent('reload-time'));
|
||||
trigger(!trigger());
|
||||
}
|
||||
window.snappymailI18N = null;
|
||||
script.remove();
|
||||
resolve();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { SaveSettingsStep } from 'Common/Enums';
|
||||
import { doc, createElement, elementById } from 'Common/Globals';
|
||||
import { doc, elementById } from 'Common/Globals';
|
||||
|
||||
export const
|
||||
isArray = Array.isArray,
|
||||
|
|
@ -84,14 +84,13 @@ let __themeTimer = 0,
|
|||
* @returns {void}
|
||||
*/
|
||||
export function changeTheme(value, themeTrigger = ()=>{}) {
|
||||
const themeLink = elementById('app-theme-link'),
|
||||
const themeStyle = elementById('app-theme-style'),
|
||||
clearTimer = () => {
|
||||
__themeTimer = setTimeout(() => themeTrigger(SaveSettingsStep.Idle), 1000);
|
||||
__themeJson = null;
|
||||
};
|
||||
|
||||
let themeStyle = elementById('app-theme-style'),
|
||||
url = (themeLink && themeLink.href) || (themeStyle && themeStyle.dataset.href);
|
||||
let url = themeStyle.dataset.href;
|
||||
|
||||
if (url) {
|
||||
url = url.toString()
|
||||
|
|
@ -118,19 +117,9 @@ export function changeTheme(value, themeTrigger = ()=>{}) {
|
|||
rl.fetchJSON(url, init)
|
||||
.then(data => {
|
||||
if (data && isArray(data) && 2 === data.length) {
|
||||
if (themeLink && !themeStyle) {
|
||||
themeStyle = createElement('style');
|
||||
themeStyle.id = 'app-theme-style';
|
||||
themeLink.after(themeStyle);
|
||||
themeLink.remove();
|
||||
}
|
||||
|
||||
if (themeStyle) {
|
||||
themeStyle.textContent = data[1];
|
||||
themeStyle.dataset.href = url;
|
||||
themeStyle.dataset.theme = data[0];
|
||||
}
|
||||
|
||||
themeStyle.textContent = data[1];
|
||||
themeStyle.dataset.href = url;
|
||||
themeStyle.dataset.theme = data[0];
|
||||
themeTrigger(SaveSettingsStep.TrueResult);
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,31 +11,18 @@ export class CheckboxMaterialDesignComponent extends AbstractCheckbox {
|
|||
this.animationBox = ko.observable(false).extend({ falseTimeout: 200 });
|
||||
this.animationCheckmark = ko.observable(false).extend({ falseTimeout: 200 });
|
||||
|
||||
this.animationBoxSetTrue = this.animationBoxSetTrue.bind(this);
|
||||
this.animationCheckmarkSetTrue = this.animationCheckmarkSetTrue.bind(this);
|
||||
|
||||
this.disposable.push(
|
||||
this.value.subscribe((value) => {
|
||||
this.triggerAnimation(value);
|
||||
}, this)
|
||||
this.value.subscribe(value => this.triggerAnimation(value), this)
|
||||
);
|
||||
}
|
||||
|
||||
animationBoxSetTrue() {
|
||||
this.animationBox(true);
|
||||
}
|
||||
|
||||
animationCheckmarkSetTrue() {
|
||||
this.animationCheckmark(true);
|
||||
}
|
||||
|
||||
triggerAnimation(box) {
|
||||
if (box) {
|
||||
this.animationBoxSetTrue();
|
||||
setTimeout(this.animationCheckmarkSetTrue, 200);
|
||||
this.animationBox(true);
|
||||
setTimeout(()=>this.animationCheckmark(true), 200);
|
||||
} else {
|
||||
this.animationCheckmarkSetTrue();
|
||||
setTimeout(this.animationBoxSetTrue, 200);
|
||||
this.animationCheckmark(true);
|
||||
setTimeout(()=>this.animationBox(true), 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
dev/External/SquireUI.js
vendored
1
dev/External/SquireUI.js
vendored
|
|
@ -412,6 +412,7 @@ class SquireUI
|
|||
input.title = ctrlKey + cfg.key;
|
||||
}
|
||||
input.dataset.action = action;
|
||||
input.tabIndex = -1;
|
||||
cfg.input = input;
|
||||
toolgroup.append(input);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export class AbstractViewCenter extends AbstractView
|
|||
{
|
||||
constructor(name, templateID)
|
||||
{
|
||||
super(name, templateID, ViewType.Center);
|
||||
super(name, templateID, ViewType.Content);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const ViewType = {
|
|||
Popup: 'Popups',
|
||||
Left: 'Left',
|
||||
Right: 'Right',
|
||||
Center: 'Center'
|
||||
Content: 'Content'
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -76,7 +76,7 @@ function buildViewModel(ViewModelClass, vmScreen) {
|
|||
let vmDom = null;
|
||||
const vm = new ViewModelClass(vmScreen),
|
||||
position = vm.viewModelPosition || '',
|
||||
vmPlace = position ? doc.querySelector('#rl-content #rl-' + position.toLowerCase()) : null;
|
||||
vmPlace = position ? doc.getElementById('rl-' + position.toLowerCase()) : null;
|
||||
|
||||
ViewModelClass.__builded = true;
|
||||
ViewModelClass.__vm = vm;
|
||||
|
|
@ -145,6 +145,8 @@ function buildViewModel(ViewModelClass, vmScreen) {
|
|||
if (vm && ViewType.Popup === position) {
|
||||
vm.registerPopupKeyDown();
|
||||
}
|
||||
|
||||
dispatchEvent(new CustomEvent('rl-view-model', {detail:vm}));
|
||||
} else {
|
||||
console.log('Cannot find view model position: ' + position);
|
||||
}
|
||||
|
|
@ -153,7 +155,7 @@ function buildViewModel(ViewModelClass, vmScreen) {
|
|||
return ViewModelClass && ViewModelClass.__vm;
|
||||
}
|
||||
|
||||
function getScreenPopupViewModel(ViewModelClassToShow) {
|
||||
export function getScreenPopupViewModel(ViewModelClassToShow) {
|
||||
return (buildViewModel(ViewModelClassToShow) && ViewModelClassToShow.__dom) && ViewModelClassToShow.__vm;
|
||||
}
|
||||
|
||||
|
|
@ -175,15 +177,6 @@ export function showScreenPopup(ViewModelClassToShow, params = []) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} ViewModelClassToShow
|
||||
* @returns {void}
|
||||
*/
|
||||
export function warmUpScreenPopup(ViewModelClassToShow) {
|
||||
const vm = getScreenPopupViewModel(ViewModelClassToShow);
|
||||
vm && vm.onWarmUp && vm.onWarmUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} ViewModelClassToShow
|
||||
* @returns {boolean}
|
||||
|
|
|
|||
|
|
@ -100,10 +100,8 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
|||
storeIt() {
|
||||
const cnt = pInt(this.CountRec);
|
||||
|
||||
let limit = pInt(Settings.app('folderSpecLimit'));
|
||||
limit = 100 < limit ? 100 : 10 > limit ? 10 : limit;
|
||||
|
||||
FolderUserStore.displaySpecSetting(0 >= cnt || limit < cnt);
|
||||
FolderUserStore.displaySpecSetting(0 >= cnt
|
||||
|| Math.max(10, Math.min(100, pInt(Settings.app('folderSpecLimit')))) < cnt);
|
||||
|
||||
FolderUserStore.folderList(this);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Notification } from 'Common/Enums';
|
||||
import { Settings } from 'Common/Globals';
|
||||
import { isArray, pInt, pString } from 'Common/Utils';
|
||||
import { serverRequest } from 'Common/Links';
|
||||
|
||||
|
|
@ -8,13 +7,6 @@ let iJsonErrorCount = 0,
|
|||
|
||||
const getURL = (add = '') => serverRequest('Json') + add,
|
||||
|
||||
updateToken = data => {
|
||||
if (data.UpdateToken) {
|
||||
rl.hash.set();
|
||||
Settings.set('AuthAccountHash', data.UpdateToken);
|
||||
}
|
||||
},
|
||||
|
||||
checkResponseError = data => {
|
||||
const err = data ? data.ErrorCode : null;
|
||||
if (Notification.InvalidToken === err && 10 < ++iTokenErrorCount) {
|
||||
|
|
@ -32,9 +24,8 @@ checkResponseError = data => {
|
|||
) {
|
||||
++iJsonErrorCount;
|
||||
}
|
||||
if (data.ClearAuth || data.Logout || 7 < iJsonErrorCount) {
|
||||
rl.hash.clear();
|
||||
data.ClearAuth || rl.logoutReload();
|
||||
if (data.Logout || 7 < iJsonErrorCount) {
|
||||
rl.logoutReload();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -97,12 +88,8 @@ export class AbstractFetchRemote
|
|||
undefined === iTimeout ? 30000 : pInt(iTimeout),
|
||||
data => {
|
||||
let cached = false;
|
||||
if (data) {
|
||||
if (data.Time) {
|
||||
cached = pInt(data.Time) > Date.now() - start;
|
||||
}
|
||||
|
||||
updateToken(data);
|
||||
if (data && data.Time) {
|
||||
cached = pInt(data.Time) > Date.now() - start;
|
||||
}
|
||||
|
||||
let iError = 0;
|
||||
|
|
@ -188,8 +175,6 @@ export class AbstractFetchRemote
|
|||
if (!data) {
|
||||
return Promise.reject(Notification.JsonParse);
|
||||
}
|
||||
|
||||
updateToken(data);
|
||||
/*
|
||||
let isCached = false, type = '';
|
||||
if (data && data.Time) {
|
||||
|
|
|
|||
|
|
@ -126,8 +126,7 @@ export class AbstractSettingsScreen extends AbstractScreen {
|
|||
this.menu.push({
|
||||
route: SettingsViewModel.__rlSettingsData.Route,
|
||||
label: SettingsViewModel.__rlSettingsData.Label,
|
||||
selected: ko.observable(false),
|
||||
disabled: false
|
||||
selected: ko.observable(false)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { FolderListMailBoxUserView } from 'View/User/MailBox/FolderList';
|
|||
import { MessageListMailBoxUserView } from 'View/User/MailBox/MessageList';
|
||||
import { MessageViewMailBoxUserView } from 'View/User/MailBox/MessageView';
|
||||
|
||||
import { warmUpScreenPopup } from 'Knoin/Knoin';
|
||||
import { getScreenPopupViewModel } from 'Knoin/Knoin';
|
||||
|
||||
import { AbstractScreen } from 'Knoin/AbstractScreen';
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ export class MailBoxUserScreen extends AbstractScreen {
|
|||
onStart() {
|
||||
if (!this.__started) {
|
||||
super.onStart();
|
||||
setTimeout(() => warmUpScreenPopup(ComposePopupView), 500);
|
||||
setTimeout(() => getScreenPopupViewModel(ComposePopupView), 500);
|
||||
|
||||
addEventListener('mailbox.inbox-unread-count', e => {
|
||||
FolderUserStore.foldersInboxUnreadCount(e.detail);
|
||||
|
|
|
|||
|
|
@ -105,8 +105,6 @@ export class ContactsAdminSettings {
|
|||
})
|
||||
})
|
||||
|
||||
this.onTestContactsResponse = this.onTestContactsResponse.bind(this);
|
||||
|
||||
decorateKoCommands(this, {
|
||||
testContactsCommand: self => self.pdoDsn() && self.pdoUser()
|
||||
});
|
||||
|
|
@ -118,7 +116,24 @@ export class ContactsAdminSettings {
|
|||
this.testContactsErrorMessage('');
|
||||
this.testing(true);
|
||||
|
||||
Remote.testContacts(this.onTestContactsResponse, {
|
||||
Remote.testContacts((iError, data) => {
|
||||
this.testContactsSuccess(false);
|
||||
this.testContactsError(false);
|
||||
this.testContactsErrorMessage('');
|
||||
|
||||
if (!iError && data.Result.Result) {
|
||||
this.testContactsSuccess(true);
|
||||
} else {
|
||||
this.testContactsError(true);
|
||||
if (data && data.Result) {
|
||||
this.testContactsErrorMessage(data.Result.Message || '');
|
||||
} else {
|
||||
this.testContactsErrorMessage('');
|
||||
}
|
||||
}
|
||||
|
||||
this.testing(false);
|
||||
}, {
|
||||
ContactsPdoType: this.contactsType(),
|
||||
ContactsPdoDsn: this.pdoDsn(),
|
||||
ContactsPdoUser: this.pdoUser(),
|
||||
|
|
@ -126,25 +141,6 @@ export class ContactsAdminSettings {
|
|||
});
|
||||
}
|
||||
|
||||
onTestContactsResponse(iError, data) {
|
||||
this.testContactsSuccess(false);
|
||||
this.testContactsError(false);
|
||||
this.testContactsErrorMessage('');
|
||||
|
||||
if (!iError && data.Result.Result) {
|
||||
this.testContactsSuccess(true);
|
||||
} else {
|
||||
this.testContactsError(true);
|
||||
if (data && data.Result) {
|
||||
this.testContactsErrorMessage(data.Result.Message || '');
|
||||
} else {
|
||||
this.testContactsErrorMessage('');
|
||||
}
|
||||
}
|
||||
|
||||
this.testing(false);
|
||||
}
|
||||
|
||||
onShow() {
|
||||
this.testContactsSuccess(false);
|
||||
this.testContactsError(false);
|
||||
|
|
|
|||
|
|
@ -13,9 +13,6 @@ export class DomainsAdminSettings {
|
|||
this.domains = DomainAdminStore;
|
||||
|
||||
this.domainForDeletion = ko.observable(null).deleteAccessHelper();
|
||||
|
||||
this.onDomainListChangeRequest = this.onDomainListChangeRequest.bind(this);
|
||||
this.onDomainLoadRequest = this.onDomainLoadRequest.bind(this);
|
||||
}
|
||||
|
||||
createDomain() {
|
||||
|
|
@ -28,30 +25,22 @@ export class DomainsAdminSettings {
|
|||
|
||||
deleteDomain(domain) {
|
||||
DomainAdminStore.remove(domain);
|
||||
Remote.domainDelete(this.onDomainListChangeRequest, domain.name);
|
||||
Remote.domainDelete(DomainAdminStore.fetch, domain.name);
|
||||
}
|
||||
|
||||
disableDomain(domain) {
|
||||
domain.disabled(!domain.disabled());
|
||||
Remote.domainDisable(this.onDomainListChangeRequest, domain.name, domain.disabled());
|
||||
Remote.domainDisable(DomainAdminStore.fetch, domain.name, domain.disabled());
|
||||
}
|
||||
|
||||
onBuild(oDom) {
|
||||
oDom.addEventListener('click', event => {
|
||||
let el = event.target.closestWithin('.b-admin-domains-list-table .e-item .e-action', oDom);
|
||||
el && ko.dataFor(el) && Remote.domain(this.onDomainLoadRequest, ko.dataFor(el).name);
|
||||
let el = event.target.closestWithin('.b-admin-domains-list-table .e-action', oDom);
|
||||
el && ko.dataFor(el) && Remote.domain(
|
||||
(iError, oData) => iError || showScreenPopup(DomainPopupView, [oData.Result]), ko.dataFor(el).name
|
||||
);
|
||||
});
|
||||
|
||||
DomainAdminStore.fetch();
|
||||
}
|
||||
|
||||
onDomainLoadRequest(iError, oData) {
|
||||
if (!iError) {
|
||||
showScreenPopup(DomainPopupView, [oData.Result]);
|
||||
}
|
||||
}
|
||||
|
||||
onDomainListChangeRequest() {
|
||||
DomainAdminStore.fetch();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,16 +12,14 @@ import Remote from 'Remote/Admin/Fetch';
|
|||
|
||||
import { PluginPopupView } from 'View/Popup/Plugin';
|
||||
|
||||
export class PluginsAdminSettings {
|
||||
export class PluginsAdminSettings
|
||||
{
|
||||
constructor() {
|
||||
this.enabledPlugins = ko.observable(!!SettingsGet('EnabledPlugins'));
|
||||
|
||||
this.plugins = PluginAdminStore;
|
||||
this.pluginsError = PluginAdminStore.error;
|
||||
|
||||
this.onPluginLoadRequest = this.onPluginLoadRequest.bind(this);
|
||||
this.onPluginDisableRequest = this.onPluginDisableRequest.bind(this);
|
||||
|
||||
this.enabledPlugins.subscribe(value =>
|
||||
Remote.saveAdminConfig(null, {
|
||||
EnabledPlugins: value ? 1 : 0
|
||||
|
|
@ -30,20 +28,30 @@ export class PluginsAdminSettings {
|
|||
}
|
||||
|
||||
disablePlugin(plugin) {
|
||||
plugin.disabled(!plugin.disabled());
|
||||
Remote.pluginDisable(this.onPluginDisableRequest, plugin.name, plugin.disabled());
|
||||
let b = !plugin.disabled();
|
||||
plugin.disabled(b);
|
||||
Remote.pluginDisable((iError, data) => {
|
||||
if (iError) {
|
||||
PluginAdminStore.error(
|
||||
(Notification.UnsupportedPluginPackage === iError && data && data.ErrorMessage)
|
||||
? data.ErrorMessage
|
||||
: getNotification(iError)
|
||||
);
|
||||
}
|
||||
PluginAdminStore.fetch();
|
||||
}, plugin.name, b);
|
||||
}
|
||||
|
||||
configurePlugin(plugin) {
|
||||
Remote.plugin(this.onPluginLoadRequest, plugin.name);
|
||||
Remote.plugin((iError, data) => iError || showScreenPopup(PluginPopupView, [data.Result]), plugin.name);
|
||||
}
|
||||
|
||||
onBuild(oDom) {
|
||||
oDom.addEventListener('click', event => {
|
||||
let el = event.target.closestWithin('.e-item .configure-plugin-action', oDom);
|
||||
let el = event.target.closestWithin('.configure-plugin-action', oDom);
|
||||
el && ko.dataFor(el) && this.configurePlugin(ko.dataFor(el));
|
||||
|
||||
el = event.target.closestWithin('.e-item .disabled-plugin', oDom);
|
||||
el = event.target.closestWithin('.disabled-plugin', oDom);
|
||||
el && ko.dataFor(el) && this.disablePlugin(ko.dataFor(el));
|
||||
});
|
||||
}
|
||||
|
|
@ -52,22 +60,4 @@ export class PluginsAdminSettings {
|
|||
PluginAdminStore.error('');
|
||||
PluginAdminStore.fetch();
|
||||
}
|
||||
|
||||
onPluginLoadRequest(iError, data) {
|
||||
if (!iError) {
|
||||
showScreenPopup(PluginPopupView, [data.Result]);
|
||||
}
|
||||
}
|
||||
|
||||
onPluginDisableRequest(iError, data) {
|
||||
if (iError) {
|
||||
if (Notification.UnsupportedPluginPackage === iError && data && data.ErrorMessage) {
|
||||
PluginAdminStore.error(data.ErrorMessage);
|
||||
} else {
|
||||
PluginAdminStore.error(getNotification(iError));
|
||||
}
|
||||
}
|
||||
|
||||
PluginAdminStore.fetch();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,6 @@ export class SecurityAdminSettings {
|
|||
})
|
||||
});
|
||||
|
||||
this.onNewAdminPasswordResponse = this.onNewAdminPasswordResponse.bind(this);
|
||||
|
||||
decorateKoCommands(this, {
|
||||
saveNewAdminPasswordCommand: self => self.adminLogin().trim() && self.adminPassword()
|
||||
});
|
||||
|
|
@ -93,7 +91,19 @@ export class SecurityAdminSettings {
|
|||
this.adminPasswordUpdateError(false);
|
||||
this.adminPasswordUpdateSuccess(false);
|
||||
|
||||
Remote.saveNewAdminPassword(this.onNewAdminPasswordResponse, {
|
||||
Remote.saveNewAdminPassword((iError, data) => {
|
||||
if (iError) {
|
||||
this.adminPasswordUpdateError(true);
|
||||
} else {
|
||||
this.adminPassword('');
|
||||
this.adminPasswordNew('');
|
||||
this.adminPasswordNew2('');
|
||||
|
||||
this.adminPasswordUpdateSuccess(true);
|
||||
|
||||
this.weakPassword(!!data.Result.Weak);
|
||||
}
|
||||
}, {
|
||||
'Login': this.adminLogin(),
|
||||
'Password': this.adminPassword(),
|
||||
'NewPassword': this.adminPasswordNew()
|
||||
|
|
@ -102,20 +112,6 @@ export class SecurityAdminSettings {
|
|||
return true;
|
||||
}
|
||||
|
||||
onNewAdminPasswordResponse(iError, data) {
|
||||
if (iError) {
|
||||
this.adminPasswordUpdateError(true);
|
||||
} else {
|
||||
this.adminPassword('');
|
||||
this.adminPasswordNew('');
|
||||
this.adminPasswordNew2('');
|
||||
|
||||
this.adminPasswordUpdateSuccess(true);
|
||||
|
||||
this.weakPassword(!!data.Result.Weak);
|
||||
}
|
||||
}
|
||||
|
||||
onHide() {
|
||||
this.adminPassword('');
|
||||
this.adminPasswordNew('');
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const storage = localStorage,
|
||||
const
|
||||
CLIENT_SIDE_STORAGE_INDEX_NAME = 'rlcsc',
|
||||
getStorage = () => {
|
||||
try {
|
||||
const value = storage.getItem(CLIENT_SIDE_STORAGE_INDEX_NAME) || null;
|
||||
return null == value ? null : JSON.parse(value);
|
||||
const value = localStorage.getItem(CLIENT_SIDE_STORAGE_INDEX_NAME);
|
||||
return value ? JSON.parse(value) : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -19,7 +19,7 @@ export function set(key, data) {
|
|||
storageResult['p' + key] = data;
|
||||
|
||||
try {
|
||||
storage.setItem(CLIENT_SIDE_STORAGE_INDEX_NAME, JSON.stringify(storageResult));
|
||||
localStorage.setItem(CLIENT_SIDE_STORAGE_INDEX_NAME, JSON.stringify(storageResult));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
|
@ -32,10 +32,7 @@ export function set(key, data) {
|
|||
*/
|
||||
export function get(key) {
|
||||
try {
|
||||
key = 'p' + key;
|
||||
const storageResult = getStorage();
|
||||
|
||||
return storageResult && null != storageResult[key] ? storageResult[key] : null;
|
||||
return (getStorage() || {})['p' + key];
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -220,8 +220,6 @@ export const MessageUserStore = new class {
|
|||
}
|
||||
});
|
||||
|
||||
this.onMessageResponse = this.onMessageResponse.bind(this);
|
||||
|
||||
this.purgeMessageBodyCacheThrottle = this.purgeMessageBodyCache.throttle(30000);
|
||||
}
|
||||
|
||||
|
|
@ -437,7 +435,6 @@ export const MessageUserStore = new class {
|
|||
id = '',
|
||||
plain = '',
|
||||
resultHtml = '',
|
||||
pgpSigned = false,
|
||||
messagesDom = this.messagesBodiesDom(),
|
||||
selectedMessage = this.selectorMessageSelected(),
|
||||
message = this.message();
|
||||
|
|
@ -497,27 +494,17 @@ export const MessageUserStore = new class {
|
|||
|
||||
if ((message.isPgpSigned() || message.isPgpEncrypted()) && PgpUserStore.capaOpenPGP()) {
|
||||
plain = pString(json.Plain);
|
||||
|
||||
const isPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(plain);
|
||||
if (!isPgpEncrypted) {
|
||||
pgpSigned =
|
||||
/-----BEGIN PGP SIGNED MESSAGE-----/.test(plain) && /-----BEGIN PGP SIGNATURE-----/.test(plain);
|
||||
}
|
||||
|
||||
const pre = createElement('pre');
|
||||
if (pgpSigned && message.isPgpSigned()) {
|
||||
if (message.isPgpSigned()) {
|
||||
pre.className = 'b-plain-openpgp signed';
|
||||
pre.textContent = plain;
|
||||
} else if (isPgpEncrypted && message.isPgpEncrypted()) {
|
||||
} else if (message.isPgpEncrypted()) {
|
||||
pre.className = 'b-plain-openpgp encrypted';
|
||||
pre.textContent = plain;
|
||||
} else {
|
||||
pre.innerHTML = resultHtml;
|
||||
}
|
||||
resultHtml = pre.outerHTML;
|
||||
|
||||
message.isPgpSigned(pgpSigned);
|
||||
message.isPgpEncrypted(isPgpEncrypted);
|
||||
} else {
|
||||
resultHtml = '<pre>' + resultHtml + '</pre>';
|
||||
}
|
||||
|
|
@ -630,27 +617,20 @@ export const MessageUserStore = new class {
|
|||
if (oMessage) {
|
||||
this.hideMessageBodies();
|
||||
this.messageLoading(true);
|
||||
Remote.message(this.onMessageResponse, oMessage.folder, oMessage.uid);
|
||||
Remote.message((iError, oData, bCached) => {
|
||||
if (iError) {
|
||||
if (Notification.RequestAborted !== iError) {
|
||||
this.message(null);
|
||||
this.messageError(getNotification(iError));
|
||||
}
|
||||
} else {
|
||||
this.setMessage(oData, bCached);
|
||||
}
|
||||
this.messageLoading(false);
|
||||
}, oMessage.folder, oMessage.uid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} sResult
|
||||
* @param {FetchJsonDefaultResponse} oData
|
||||
* @param {boolean} bCached
|
||||
*/
|
||||
onMessageResponse(iError, oData, bCached) {
|
||||
if (iError) {
|
||||
if (Notification.RequestAborted !== iError) {
|
||||
this.message(null);
|
||||
this.messageError(getNotification(iError));
|
||||
}
|
||||
} else {
|
||||
this.setMessage(oData, bCached);
|
||||
}
|
||||
this.messageLoading(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} list
|
||||
* @returns {string}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import * as Links from 'Common/Links';
|
|||
/**
|
||||
* Might not work due to the new ServiceWorkerRegistration.showNotification
|
||||
*/
|
||||
const HTML5Notification = window.Notification ? Notification : null,
|
||||
const HTML5Notification = window.Notification,
|
||||
HTML5NotificationStatus = () => (HTML5Notification && HTML5Notification.permission) || 'denied',
|
||||
NotificationsDenied = () => 'denied' === HTML5NotificationStatus(),
|
||||
NotificationsGranted = () => 'granted' === HTML5NotificationStatus(),
|
||||
|
|
|
|||
|
|
@ -340,11 +340,11 @@ export const PgpUserStore = new class {
|
|||
*/
|
||||
initMessageBodyControls(dom, rainLoopMessage) {
|
||||
const cl = dom && dom.classList;
|
||||
if (!cl.has('inited')) {
|
||||
if (!cl.contains('inited')) {
|
||||
cl.add('inited');
|
||||
|
||||
const encrypted = cl.has('encrypted'),
|
||||
signed = cl.has('signed'),
|
||||
const encrypted = cl.contains('encrypted'),
|
||||
signed = cl.contains('signed'),
|
||||
recipients = rainLoopMessage ? rainLoopMessage.getEmails(['from', 'to', 'cc']) : [];
|
||||
|
||||
let verControl = null;
|
||||
|
|
@ -355,10 +355,10 @@ export const PgpUserStore = new class {
|
|||
verControl = Element.fromHTML('<div class="b-openpgp-control"><i class="fontastic">🔒</i></div>');
|
||||
if (encrypted) {
|
||||
verControl.title = i18n('MESSAGE/PGP_ENCRYPTED_MESSAGE_DESC');
|
||||
verControl.addEventHandler('click', domControlEncryptedClickHelper(this, dom, domText, recipients));
|
||||
verControl.addEventListener('click', domControlEncryptedClickHelper(this, dom, domText, recipients));
|
||||
} else {
|
||||
verControl.title = i18n('MESSAGE/PGP_SIGNED_MESSAGE_DESC');
|
||||
verControl.addEventHandler('click', domControlSignedClickHelper(this, dom, domText));
|
||||
verControl.addEventListener('click', domControlSignedClickHelper(this, dom, domText));
|
||||
}
|
||||
|
||||
dom.before(verControl, createElement('div'));
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ body {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.e-spinner .e-bounce {
|
||||
.e-spinner div {
|
||||
animation: bouncedelay 1.4s infinite ease-in-out;
|
||||
background-color: var(--loading-color, #000);
|
||||
border-radius: 100%;
|
||||
|
|
@ -46,12 +46,12 @@ body {
|
|||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.e-spinner .bounce1 {
|
||||
animation-delay: -0.32s;
|
||||
.e-spinner div + div {
|
||||
animation-delay: 0.16s;
|
||||
}
|
||||
|
||||
.e-spinner .bounce2 {
|
||||
animation-delay: -0.16s;
|
||||
.e-spinner div + div + div {
|
||||
animation-delay: 0.32s;
|
||||
}
|
||||
|
||||
@keyframes bouncedelay {
|
||||
|
|
|
|||
|
|
@ -1,67 +1,25 @@
|
|||
|
||||
.b-admin-left {
|
||||
#rl-left {
|
||||
overflow: auto;
|
||||
padding-top: 50px + @rlLowMargin + 10px;
|
||||
|
||||
.b-toolbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 34px;
|
||||
padding: 8px 0 0 @rlLowMargin;
|
||||
}
|
||||
|
||||
.b-content {
|
||||
position: absolute;
|
||||
top: 50px + @rlLowMargin + 10px;
|
||||
bottom: @rlLowMargin;
|
||||
left: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
|
||||
.content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
nav {
|
||||
a {
|
||||
color: var(--settings-menu-color, #333);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
line-height: 30px;
|
||||
padding: 4px 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.b-admin-menu {
|
||||
|
||||
.e-item {
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.e-link {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 30px;
|
||||
line-height: 29px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
z-index: 1;
|
||||
cursor: default;
|
||||
|
||||
background-color: transparent;
|
||||
color: var(--settings-menu-disabled-color, #666);
|
||||
|
||||
padding: 4px 10px;
|
||||
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.e-item.selectable .e-link {
|
||||
cursor: pointer;
|
||||
color: var(--settings-menu-color, #333);
|
||||
}
|
||||
|
||||
.e-item.selectable {
|
||||
&:hover .e-link{
|
||||
a:focus, a:hover {
|
||||
background-color: var(--settings-menu-hover-bg-color, #333);
|
||||
color: var(--settings-menu-hover-color, #eee);
|
||||
}
|
||||
&.selected .e-link {
|
||||
|
||||
a.selected {
|
||||
background-color: var(--settings-menu-selected-bg-color, #333);
|
||||
color: var(--settings-menu-selected-color, #eee);
|
||||
}
|
||||
|
|
@ -97,14 +55,10 @@
|
|||
z-index: 2;
|
||||
|
||||
background-color: #fff;
|
||||
border: @rlMainBorderSize solid @rlMainDarkColor;
|
||||
border: 1px solid @rlMainDarkColor;
|
||||
|
||||
box-shadow: @rlMainShadow;
|
||||
border-radius: @rlMainBorderRadius;
|
||||
|
||||
.content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,54 +3,49 @@
|
|||
|
||||
width: 600px;
|
||||
|
||||
.e-item {
|
||||
.domain-name {
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.domain-name {
|
||||
display: inline-block;
|
||||
word-break: break-all;
|
||||
box-sizing: border-box;
|
||||
.domain-alias {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
color: #bbb;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.button-delete {
|
||||
position: relative;
|
||||
right: 0;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
white-space: nowrap;
|
||||
|
||||
&.delete-access {
|
||||
visibility: visible;
|
||||
right: -15px;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.domain-alias {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
.delete-domain, .disabled-domain {
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.domain-name, .domain-alias {
|
||||
color: #bbb;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.domain-name, .domain-alias {
|
||||
color: #bbb;
|
||||
}
|
||||
}
|
||||
|
||||
.button-delete {
|
||||
margin-right: 15px;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.delete-access {
|
||||
&.button-delete {
|
||||
visibility: visible;
|
||||
margin-right: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.delete-domain, .disabled-domain {
|
||||
cursor: pointer;
|
||||
.disabled-domain {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.disabled .disabled-domain {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.delete-domain, .disabled-domain {
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,26 +11,23 @@
|
|||
|
||||
width: 700px;
|
||||
|
||||
.e-item {
|
||||
.package-img {
|
||||
font-size: 12px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.package-img {
|
||||
font-size: 12px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.package-name.core {
|
||||
font-weight: bold;
|
||||
}
|
||||
.package-desc {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.package-name.core {
|
||||
font-weight: bold;
|
||||
}
|
||||
.package-desc {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.package-release, .package-actions {
|
||||
text-align: center;
|
||||
}
|
||||
.package-actions {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.package-release, .package-actions {
|
||||
text-align: center;
|
||||
}
|
||||
.package-actions {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,25 +1,13 @@
|
|||
|
||||
.b-admin-plugins-list-table {
|
||||
max-width: 620px; /* span8 */
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.e-item {
|
||||
.plugin-img {
|
||||
font-size: 12px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.plugin-img, .plugin-name {
|
||||
color: #bbb;
|
||||
}
|
||||
.disabled-plugin {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ html.rl-started-delay {
|
|||
|
||||
@media screen and (min-width: 1000px) {
|
||||
|
||||
html.rl-started-trigger .b-login-content .loginFormWrapper {
|
||||
html.rl-started-trigger .b-login-content {
|
||||
/*transform: scale(1.1);*/
|
||||
transform: translateY(-20px);
|
||||
opacity: 0.5;
|
||||
|
|
@ -40,11 +40,7 @@ html.rl-started-delay {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.b-login-content .afterLoginHide {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.b-login-content .loginFormWrapper {
|
||||
.b-login-content {
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
transition: margin 0.3s linear;
|
||||
}
|
||||
|
||||
.b-folders .b-content.opacity-on-panel-disabled {
|
||||
#rl-left .b-content {
|
||||
transition: opacity 0.3s linear;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
}
|
||||
|
||||
&.error {
|
||||
.attachmentIcon, .attachmentSize, .attachmentName {
|
||||
.iconMain, .iconPreview, .attachmentSize, .attachmentName {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
|
@ -91,11 +91,10 @@
|
|||
border-left: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.attachmentIcon {
|
||||
margin: 6px 0 0;
|
||||
.iconMain, .iconPreview {
|
||||
box-sizing: border-box;
|
||||
padding: 6px 0 0;
|
||||
font-size: 36px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
color: #aaa;
|
||||
|
||||
&.icon-none {
|
||||
|
|
@ -125,7 +124,7 @@
|
|||
}
|
||||
|
||||
.attachmentIconParent.hasPreview .showPreview,
|
||||
.attachmentIconParent.hasPreplay .showPreview {
|
||||
.attachmentIconParent.hasPreplay .showPreplay {
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@
|
|||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@
|
|||
.attachmentAreaParent {
|
||||
border-top: 1px solid #ccc;
|
||||
overflow-y: auto;
|
||||
padding: 10px 10px 6px 10px;
|
||||
padding: 10px;
|
||||
|
||||
.no-attachments-desc {
|
||||
padding: 50px 10px;
|
||||
padding-top: 50px;
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
color: #666;
|
||||
|
|
@ -38,7 +38,6 @@
|
|||
|
||||
.b-header-toolbar {
|
||||
|
||||
color: #fff;
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
|
||||
.close, .minimize-custom {
|
||||
|
|
@ -72,23 +71,17 @@
|
|||
.b-header {
|
||||
|
||||
padding: 10px;
|
||||
background-color: #eee;
|
||||
color: #333;
|
||||
background-color: rgba(128,128,128,0.2);;
|
||||
|
||||
.e-identity {
|
||||
|
||||
color: #333;
|
||||
color: var(--main-color);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.multiply {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px dashed #555;
|
||||
border-bottom: 1px dashed var(--main-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,16 +8,11 @@
|
|||
padding-top: 0;
|
||||
width: 50px;
|
||||
}
|
||||
.controls {
|
||||
margin-left: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
&.modal {
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: auto;
|
||||
|
|
@ -28,9 +23,9 @@
|
|||
}
|
||||
|
||||
.modal-body {
|
||||
overflow: auto;
|
||||
height: calc(100% - 49px);
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.b-list-toolbar {
|
||||
|
|
@ -71,10 +66,6 @@
|
|||
overflow-y: auto;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
.content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.listClear {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
|
|
@ -184,27 +175,13 @@
|
|||
overflow: auto;
|
||||
border-left: 1px solid rgba(128,128,128,0.25);
|
||||
|
||||
.content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.contactValueStatic, .contactValueLargeStatic, .contactValueTextAreaStatic {
|
||||
span {
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-size: 18px;
|
||||
display: inline-block;
|
||||
padding: 5px 7px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.read-only {
|
||||
.contactValueStatic, .contactValueLargeStatic, .contactValueTextAreaStatic {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.contactValueInput, .contactValueInputLarge, .contactValueTextArea {
|
||||
display: none;
|
||||
}
|
||||
padding: 5px 7px;
|
||||
font-size: 18px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.b-contact-view-desc {
|
||||
|
|
@ -235,16 +212,12 @@
|
|||
color: #aaa;
|
||||
}
|
||||
|
||||
.contactValueStatic, .contactValueLargeStatic, .contactValueTextAreaStatic {
|
||||
font-size: 18px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.contactValueInput, .contactValueInputLarge, .contactValueTextArea {
|
||||
input, textarea {
|
||||
box-shadow: none;
|
||||
border-color: #fff;
|
||||
font-size: 18px;
|
||||
width: 300px;
|
||||
width: 70vw;
|
||||
max-width: 400px;
|
||||
|
||||
&:hover {
|
||||
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
|
||||
|
|
@ -257,16 +230,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
.contactValueTextArea {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.contactValueInputLarge {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.hasError {
|
||||
.contactValueInput {
|
||||
input {
|
||||
color: #ee5f5b;
|
||||
border-color: #ee5f5b;
|
||||
}
|
||||
|
|
@ -293,11 +258,11 @@
|
|||
}
|
||||
|
||||
&.read-only {
|
||||
.e-read-only-sign {
|
||||
span, .e-read-only-sign {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.e-share-sign {
|
||||
input, textarea, .e-share-sign {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
.emailaddresses.emailaddresses-focused {
|
||||
background-color: #fff;
|
||||
border: @rlInputBorderSize solid darken(@inputBorder, 20%);
|
||||
border: 1px solid darken(@inputBorder, 20%);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +49,6 @@
|
|||
.emailaddresses li a {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
outline: 0;
|
||||
padding: 1px;
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
|
|
@ -73,7 +72,6 @@
|
|||
height: auto;
|
||||
line-height: inherit;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
padding: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
}
|
||||
|
||||
.b-folders-user {
|
||||
.e-link.print-count.system .badge {
|
||||
a.print-count.system .badge {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -58,10 +58,6 @@
|
|||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
min-width: 100px;
|
||||
|
||||
.content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
|
|
@ -75,7 +71,7 @@
|
|||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
.e-link {
|
||||
a {
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
|
@ -108,11 +104,8 @@
|
|||
display: inline;
|
||||
}
|
||||
|
||||
padding: 0;
|
||||
padding-left: @folderItemPadding;
|
||||
padding-right: @folderItemPadding;
|
||||
padding: 0 @folderItemPadding;
|
||||
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
|
||||
&.selectable {
|
||||
|
|
@ -204,21 +197,21 @@
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
.b-sub-folders .e-item .e-link {
|
||||
.b-sub-folders .e-item a {
|
||||
padding-left: @subPadding * 1 + @folderItemPadding;
|
||||
}
|
||||
.b-sub-folders .b-sub-folders .e-item .e-link {
|
||||
.b-sub-folders .b-sub-folders .e-item a {
|
||||
padding-left: @subPadding * 2 + @folderItemPadding;
|
||||
}
|
||||
.b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link {
|
||||
.b-sub-folders .b-sub-folders .b-sub-folders .e-item a {
|
||||
padding-left: @subPadding * 3 + @folderItemPadding;
|
||||
}
|
||||
.b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link {
|
||||
.b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item a {
|
||||
padding-left: @subPadding * 4 + @folderItemPadding;
|
||||
}
|
||||
|
||||
/**/
|
||||
&.single-root-inbox .i-am-inbox.e-link {
|
||||
&.single-root-inbox a.i-am-inbox {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
|
@ -229,16 +222,16 @@
|
|||
}
|
||||
|
||||
&.single-root-inbox .i-am-inbox-wrapper {
|
||||
.b-sub-folders .e-item .e-link {
|
||||
.b-sub-folders .e-item a {
|
||||
padding-left: @folderItemPadding;
|
||||
}
|
||||
.b-sub-folders .b-sub-folders .e-item .e-link {
|
||||
.b-sub-folders .b-sub-folders .e-item a {
|
||||
padding-left: @subPadding * 1 + @folderItemPadding;
|
||||
}
|
||||
.b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link {
|
||||
.b-sub-folders .b-sub-folders .b-sub-folders .e-item a {
|
||||
padding-left: @subPadding * 2 + @folderItemPadding;
|
||||
}
|
||||
.b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item .e-link {
|
||||
.b-sub-folders .b-sub-folders .b-sub-folders .b-sub-folders .e-item a {
|
||||
padding-left: @subPadding * 3 + @folderItemPadding;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
background-position: center;
|
||||
}
|
||||
|
||||
#rl-center, #rl-top, #rl-left, #rl-right {
|
||||
#rl-left, #rl-right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
|
@ -17,23 +17,8 @@
|
|||
z-index: 0;
|
||||
}
|
||||
|
||||
#rl-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#rl-center {
|
||||
min-width: 600px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
html.rl-mobile {
|
||||
|
||||
#rl-center {
|
||||
min-width: 250px;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
&:not(.rl-left-panel-disabled) #rl-right {
|
||||
right: -150px;
|
||||
}
|
||||
|
|
@ -43,11 +28,6 @@ html.rl-mobile {
|
|||
}
|
||||
}
|
||||
|
||||
#rl-top {
|
||||
bottom: auto;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#rl-left {
|
||||
width: @rlLeftWidth;
|
||||
}
|
||||
|
|
@ -223,11 +203,10 @@ html:not(.rl-left-panel-disabled) #rl-left.resizable > .resizer,
|
|||
z-index: 1100;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
// -webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.b-settings-content {
|
||||
padding:20px 20px 20px 30px;
|
||||
#rl-settings-subscreen {
|
||||
padding:20px;
|
||||
}
|
||||
|
||||
.dropdown-menu * + .dividerbar {
|
||||
|
|
@ -264,7 +243,7 @@ html:not(.rl-left-panel-disabled) #rl-left.resizable > .resizer,
|
|||
}
|
||||
|
||||
.btn-group > .btn.single {
|
||||
border-radius: @btnBorderRadius !important;
|
||||
border-radius: 3px !important;
|
||||
}
|
||||
|
||||
/* desktop */
|
||||
|
|
@ -300,8 +279,8 @@ html:not(.rl-left-panel-disabled) #rl-left.resizable > .resizer,
|
|||
left: 155px;
|
||||
}
|
||||
|
||||
.b-settings-content {
|
||||
padding: 10px 10px 10px 20px;
|
||||
#rl-settings-subscreen {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.dropdown-menu a {
|
||||
|
|
@ -324,7 +303,7 @@ html.rl-left-panel-disabled {
|
|||
display: block;
|
||||
}
|
||||
|
||||
.opacity-on-panel-disabled {
|
||||
.b-content {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
background-position: center;
|
||||
}
|
||||
|
||||
#rl-center, #rl-top, #rl-left, #rl-right {
|
||||
#rl-left, #rl-right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
|
@ -17,21 +17,6 @@
|
|||
z-index: 0;
|
||||
}
|
||||
|
||||
#rl-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#rl-center {
|
||||
min-width: 600px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
#rl-top {
|
||||
bottom: auto;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#rl-left {
|
||||
width: @rlLeftWidth;
|
||||
}
|
||||
|
|
@ -50,11 +35,10 @@
|
|||
z-index: 1100;
|
||||
overflow: auto;
|
||||
background-color: rgba(0,0,0,0.3);
|
||||
// -webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.b-settings-content {
|
||||
padding:20px 20px 20px 30px;
|
||||
#rl-settings-subscreen {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* desktop */
|
||||
|
|
@ -77,13 +61,8 @@
|
|||
|
||||
/* mobile and tablet */
|
||||
@media screen and (max-width: 999px) {
|
||||
.b-settings-content {
|
||||
padding: 10px 10px 10px 20px;
|
||||
}
|
||||
|
||||
#rl-center {
|
||||
min-width: 250px;
|
||||
min-height: 250px;
|
||||
#rl-settings-subscreen {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
html:not(.rl-left-panel-disabled) #rl-right {
|
||||
|
|
@ -92,11 +71,7 @@
|
|||
|
||||
html.rl-left-panel-disabled {
|
||||
#rl-left {
|
||||
width: 0px !important;
|
||||
|
||||
.opacity-on-panel-disabled {
|
||||
opacity: 0.3;
|
||||
}
|
||||
width: 0 !important;
|
||||
}
|
||||
#rl-right {
|
||||
left: 5px !important;
|
||||
|
|
|
|||
|
|
@ -1,139 +1,145 @@
|
|||
|
||||
.rl-view-model {
|
||||
&.RL-Login, &.RL-AdminLogin {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
z-index: 5;
|
||||
}
|
||||
.RL-Login, .RL-AdminLogin {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
min-width: 250px;
|
||||
max-width: 600px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
@glass-color: #fff !important;
|
||||
@glass-error-color: #f76260 !important;
|
||||
@glass-m-color: rgba(255, 255, 255, .8) !important;
|
||||
@glass-color: #fff;
|
||||
@glass-error-color: #f76260;
|
||||
@glass-m-color: rgba(255, 255, 255, .8);
|
||||
|
||||
.b-login-content {
|
||||
|
||||
.loginFormWrapper {
|
||||
position: relative;
|
||||
|
||||
.descWrapper {
|
||||
color: var(--loading-color, #000);
|
||||
font-size: 30px;
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
text-shadow: var(--loading-text-shadow);
|
||||
}
|
||||
|
||||
.controls, .control-group {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
form {
|
||||
background-color: var(--login-bg-color, rgba(0, 0, 0, .5));
|
||||
border: var(--login-border, 1px solid rgba(255, 255, 255, .2));
|
||||
border-radius: var(--login-border-radius, 7px);
|
||||
box-shadow: var(--login-box-shadow);
|
||||
color: var(--login-color, @glass-m-color);
|
||||
margin: 0;
|
||||
padding: 40px 40px 20px 40px;
|
||||
position: relative;
|
||||
|
||||
.descWrapper {
|
||||
margin-bottom: 10px;
|
||||
text-align: center;
|
||||
|
||||
.desc {
|
||||
font-size: 30px;
|
||||
color: var(--loading-color, #000);
|
||||
text-shadow: var(--loading-text-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
.loginForm {
|
||||
background-color: var(--login-bg-color, rgba(0, 0, 0, .5));
|
||||
border: var(--login-border, 1px solid rgba(255, 255, 255, .2));
|
||||
border-radius: var(--login-border-radius, 7px);
|
||||
box-shadow: var(--login-box-shadow);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.input-append .add-on *,
|
||||
.loginForm, .legend, .e-checkbox-icon, .g-ui-link, .language-button {
|
||||
* {
|
||||
color: var(--login-color, @glass-m-color);
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
.controls, .control-group {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 40px 40px 20px 40px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword {
|
||||
font-size: 18px;
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.inputEmail, .inputLogin, .inputPassword {
|
||||
padding-right: 35px;
|
||||
}
|
||||
|
||||
input, .btn {
|
||||
border: 1px solid none !important;
|
||||
background: none !important;
|
||||
outline: none !important;
|
||||
text-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
color: @glass-color;
|
||||
border-color: @glass-m-color;
|
||||
.controls.error {
|
||||
.input-append .add-on,
|
||||
.input-append .add-on * {
|
||||
color: @glass-error-color;
|
||||
}
|
||||
|
||||
input {
|
||||
|
||||
&:placeholder {
|
||||
color: @glass-color;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
&:focus, &:hover {
|
||||
border-color: @glass-color;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
|
||||
&:hover, &:active {
|
||||
border-color: @glass-color;
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
.input-append .add-on * {
|
||||
color: @glass-error-color;
|
||||
}
|
||||
|
||||
input {
|
||||
color: @glass-error-color;
|
||||
border-color: @glass-error-color;
|
||||
}
|
||||
color: @glass-error-color;
|
||||
border-color: @glass-error-color;
|
||||
}
|
||||
}
|
||||
|
||||
.signMeLabel .e-checkbox {
|
||||
margin-top: 5px;
|
||||
&.submitting > * {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.input-append .add-on {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
background: none;
|
||||
margin-left: -35px;
|
||||
z-index: 1000;
|
||||
border: 0;
|
||||
&.submitting::after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -30px;
|
||||
margin-left: -30px;
|
||||
|
||||
border: 8px solid transparent;
|
||||
border-top-color: var(--spinner-color, #fff);
|
||||
animation: loginRotation 1s infinite ease-in-out;
|
||||
|
||||
border-radius: 50%;
|
||||
z-index: 1052;
|
||||
}
|
||||
}
|
||||
|
||||
.controls {
|
||||
.inputLoginForm, .inputLogin, .inputPassword {
|
||||
font-size: 18px;
|
||||
height: 40px;
|
||||
line-height: 20px;
|
||||
padding-left: 12px;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.inputLogin, .inputPassword {
|
||||
padding-right: 35px;
|
||||
}
|
||||
|
||||
.input-append .add-on * {
|
||||
font-size: 17px;
|
||||
line-height: 29px;
|
||||
outline: none !important;
|
||||
box-shadow: none !important;
|
||||
input, .btn {
|
||||
border: 1px solid none;
|
||||
background: none !important;
|
||||
text-shadow: none;
|
||||
box-shadow: none;
|
||||
|
||||
color: @glass-color;
|
||||
border-color: @glass-m-color;
|
||||
}
|
||||
|
||||
.controls.error .add-on *,
|
||||
.control-group.error .add-on * {
|
||||
color: #b94a48;
|
||||
input {
|
||||
|
||||
&:placeholder {
|
||||
color: @glass-color;
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
&:focus, &:hover {
|
||||
border-color: @glass-color;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
|
||||
text-transform: uppercase;
|
||||
font-size: 13px;
|
||||
|
||||
&:hover, &:active {
|
||||
border-color: @glass-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.signMeLabel .e-checkbox {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.input-append .add-on {
|
||||
margin-left: -1.5em;
|
||||
position: absolute;
|
||||
font-size: 17px;
|
||||
line-height: 29px;
|
||||
right: 6px;
|
||||
top: 6px;
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.input-append .add-on * {
|
||||
font-size: 17px;
|
||||
line-height: 29px;
|
||||
}
|
||||
|
||||
.buttonLogin {
|
||||
|
|
@ -163,14 +169,13 @@
|
|||
|
||||
.language-button {
|
||||
padding: 5px;
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.b-login-content .loginFormWrapper {
|
||||
.wrapper {
|
||||
.b-login-content {
|
||||
form {
|
||||
padding: 30px 4vw 10px;
|
||||
}
|
||||
}
|
||||
|
|
@ -180,36 +185,11 @@
|
|||
to {transform: rotate(1turn);}
|
||||
}
|
||||
|
||||
.submitting-pane {
|
||||
position: relative;
|
||||
&.submitting > * {
|
||||
opacity: 0.3;
|
||||
}
|
||||
html &.submitting::after{
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -30px;
|
||||
margin-left: -30px;
|
||||
|
||||
border: 8px solid transparent;
|
||||
border-top-color: var(--spinner-color, #fff);
|
||||
animation: loginRotation 1s infinite ease-in-out;
|
||||
|
||||
border-radius: 50%;
|
||||
z-index: 1052;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-submit-icon-wrp {
|
||||
border: none;
|
||||
background: none;
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ html.rl-no-preview-pane {
|
|||
right: 0;
|
||||
left: 0;
|
||||
height: 30px;
|
||||
padding: 10px @rlMainBorderSize;
|
||||
padding: 10px 1px;
|
||||
z-index: 102;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ html.rl-no-preview-pane {
|
|||
left: 0;
|
||||
bottom: @rlBottomMargin;
|
||||
|
||||
border: @rlMainBorderSize solid @rlMainDarkColor;
|
||||
border: 1px solid @rlMainDarkColor;
|
||||
|
||||
box-shadow: @rlMainShadow;
|
||||
border-radius: @rlMainBorderRadius;
|
||||
|
|
@ -90,7 +90,7 @@ html.rl-no-preview-pane {
|
|||
|
||||
.checkboxCheckAll {
|
||||
cursor: pointer;
|
||||
line-height: 30px;
|
||||
vertical-align: middle
|
||||
}
|
||||
|
||||
.input-append {
|
||||
|
|
@ -134,10 +134,6 @@ html.rl-no-preview-pane {
|
|||
|
||||
background-color: #fff;
|
||||
|
||||
.content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.listClear {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
.g-ui-min-height-300 {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
|
@ -24,9 +23,9 @@
|
|||
right: @rlLowMargin;
|
||||
left: -2px;
|
||||
/*overflow: hidden;*/
|
||||
border: @rlLowBorderSize solid @rlMainDarkColor;
|
||||
border: 1px solid @rlMainDarkColor;
|
||||
|
||||
border-radius: @rlLowBorderRadius;
|
||||
border-radius: @rlMainBorderRadius;
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
|
|
@ -57,10 +56,6 @@
|
|||
color: #DA4F49;
|
||||
}
|
||||
|
||||
.content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.message-fixed-button-toolbar {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
|
|
@ -96,11 +91,8 @@
|
|||
|
||||
padding: 10px;
|
||||
background-color: #f8f8f8;
|
||||
border-top: 0;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-top-right-radius: 5px;
|
||||
border-top-left-radius: 5px;
|
||||
z-index: 1;
|
||||
border-radius: @rlMainBorderRadius @rlMainBorderRadius 0 0;
|
||||
|
||||
.subjectParent {
|
||||
font-size: 18px;
|
||||
|
|
@ -109,9 +101,6 @@
|
|||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 8px;
|
||||
line-height: 100%;
|
||||
height: 22px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.messageButtons {
|
||||
|
|
@ -190,8 +179,6 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
border-radius: @rlLowBorderRadius;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
.buttonFull {
|
||||
|
|
@ -294,13 +281,10 @@
|
|||
}
|
||||
|
||||
.rlBlockquoteSwitcher {
|
||||
|
||||
background-color: #eee;
|
||||
border: 1px solid #999;
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
width: 3em;
|
||||
line-height: 1em;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin: 2em 0 10px;
|
||||
|
|
@ -311,10 +295,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.bodySubHeader {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.b-text-part {
|
||||
|
||||
height: 100%;
|
||||
|
|
@ -458,19 +438,21 @@
|
|||
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
color: #777;
|
||||
opacity: 0.5;
|
||||
|
||||
&:hover {
|
||||
color: #111;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: green;
|
||||
cursor: help;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.error {
|
||||
color: red;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -479,7 +461,6 @@
|
|||
&.message-focused .b-content {
|
||||
z-index: 101;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
border-radius: @rlLowBorderRadius;
|
||||
border-color: darken(@rlMainDarkColor, 5%);
|
||||
}
|
||||
|
||||
|
|
@ -528,7 +509,7 @@ html.rl-no-preview-pane .messageView {
|
|||
}
|
||||
|
||||
.toolbar {
|
||||
padding-left: @rlMainBorderSize;
|
||||
padding-left: 1px;
|
||||
}
|
||||
|
||||
.b-content {
|
||||
|
|
@ -536,9 +517,8 @@ html.rl-no-preview-pane .messageView {
|
|||
left: 0;
|
||||
bottom: @rlBottomMargin;
|
||||
right: @rlBottomMargin;
|
||||
border: @rlMainBorderSize solid @rlMainDarkColor;
|
||||
border: 1px solid @rlMainDarkColor;
|
||||
box-shadow: @rlMainShadow;
|
||||
border-radius: @rlMainBorderRadius;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -577,8 +557,7 @@ html.rl-message-fullscreen {
|
|||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
z-index: 10000 !important;
|
||||
border: @rlLowBorderSize solid @rlMainDarkColor !important;
|
||||
border-radius: @rlLowBorderRadius !important;
|
||||
border: 1px solid @rlMainDarkColor !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,21 +1,11 @@
|
|||
|
||||
.b-settins-left {
|
||||
|
||||
.b-toolbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 34px;
|
||||
padding: 8px 0 0 @rlLowMargin;
|
||||
}
|
||||
|
||||
.b-footer {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
bottom: 10px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 20px;
|
||||
padding: 0 10px 0 5px;
|
||||
z-index: 101;
|
||||
}
|
||||
|
|
@ -27,50 +17,29 @@
|
|||
left: 0;
|
||||
right: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
nav {
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
color: var(--settings-menu-color, #333);
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
font-size: 18px;
|
||||
height: 30px;
|
||||
line-height: 29px;
|
||||
overflow: hidden;
|
||||
padding: 4px 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.b-settings-menu {
|
||||
|
||||
.e-item {
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.e-link {
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 30px;
|
||||
line-height: 29px;
|
||||
font-size: 18px;
|
||||
z-index: 1;
|
||||
cursor: default;
|
||||
|
||||
background-color: transparent;
|
||||
color: var(--settings-menu-disabled-color, #666);
|
||||
|
||||
padding: 4px 10px;
|
||||
|
||||
outline: 0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.e-item.selectable .e-link {
|
||||
cursor: pointer;
|
||||
color: var(--settings-menu-color, #333);
|
||||
}
|
||||
|
||||
.e-item.selectable {
|
||||
&:hover .e-link{
|
||||
a:focus, a:hover {
|
||||
background-color: var(--settings-menu-hover-bg-color, #333);
|
||||
color: var(--settings-menu-hover-color, #eee);
|
||||
}
|
||||
&.selected .e-link {
|
||||
|
||||
a.selected {
|
||||
background-color: var(--settings-menu-selected-bg-color, #333);
|
||||
color: var(--settings-menu-selected-color, #eee);
|
||||
}
|
||||
|
|
@ -85,7 +54,7 @@
|
|||
right: 0;
|
||||
left: 0;
|
||||
height: 34px;
|
||||
padding: 8px 5px;
|
||||
padding: 8px 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
|
@ -100,13 +69,9 @@
|
|||
scroll-behavior: smooth;
|
||||
|
||||
background-color: #fff;
|
||||
border: @rlMainBorderSize solid @rlMainDarkColor;
|
||||
border: 1px solid @rlMainDarkColor;
|
||||
box-shadow: @rlMainShadow;
|
||||
border-radius: @rlMainBorderRadius;
|
||||
|
||||
.content {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
.squire-toolbar {
|
||||
padding: 2px;
|
||||
border-bottom: 1px solid #b6b6b6;
|
||||
background: #EEE;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
white-space: nowrap;
|
||||
|
|
@ -143,7 +142,6 @@ Secondly, we can't rely on MUA's what to do with :empty
|
|||
border-radius: 0;
|
||||
display: none;
|
||||
font-family: var(--fontMono);
|
||||
outline: none;
|
||||
margin: 0;
|
||||
resize: none;
|
||||
white-space: pre-wrap;
|
||||
|
|
@ -162,6 +160,7 @@ Secondly, we can't rely on MUA's what to do with :empty
|
|||
display: none;
|
||||
}
|
||||
.rl-mobile .squire-toolbar {
|
||||
background: #EEE;
|
||||
padding: 1px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
.email-title {
|
||||
display: inline-block;
|
||||
max-width: 200px;
|
||||
max-width: 50vw;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
|
|
@ -61,41 +61,27 @@
|
|||
}
|
||||
|
||||
.accountPlace {
|
||||
background-color: #000;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 0 #000;
|
||||
|
||||
display: inline-block;
|
||||
height: 29px;
|
||||
max-width: 250px;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
line-height: 30px;
|
||||
padding: 1px 8px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
|
||||
font-size: 16px;
|
||||
height: 29px;
|
||||
line-height: 30px;
|
||||
max-width: 25vw;
|
||||
overflow: hidden;
|
||||
padding: 1px 8px;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: 0 1px 0 #000;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.account-item {
|
||||
|
||||
.icon-ok {
|
||||
&:not(.current) i:first-child,
|
||||
&.current i + i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.current {
|
||||
.icon-ok {
|
||||
display: inline-block;
|
||||
}
|
||||
.icon-user {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.counter {
|
||||
|
|
@ -107,13 +93,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* mobile */
|
||||
@media screen and (max-width: 767px) {
|
||||
.accountPlace {
|
||||
max-width: 150px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes firstBar {
|
||||
0% { height: 30%; }
|
||||
50% { height: 100%; }
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
top: 100%;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
white-space: nowrap;
|
||||
z-index: 2001 !important;
|
||||
z-index: 2001;
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
[data-rainloopErrorTip]::before {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
|
||||
@rlLeftWidth: 200px;
|
||||
@rlInputBorderSize: 1px;
|
||||
@rlMainBorderSize: 1px;
|
||||
@rlLowBorderSize: 1px;
|
||||
|
||||
@rlMainBorderRadius: 5px;
|
||||
@rlLowBorderRadius: 3px;
|
||||
|
||||
@rlMainShadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
|
||||
|
|
@ -13,6 +9,3 @@
|
|||
@rlBottomMargin: 5px;
|
||||
|
||||
@rlMainDarkColor: #aaa;
|
||||
|
||||
// bootstart
|
||||
@btnBorderRadius: 3px;
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
#progressjs {
|
||||
left: 0;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.progressjs-inner {
|
||||
background-color: #939595;
|
||||
height: 3px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: width .5s;
|
||||
width: 0;
|
||||
z-index: 2000;
|
||||
}
|
||||
|
||||
.progressjs-percent {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: -32px;
|
||||
bottom: 0;
|
||||
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.3) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.3) 50%, rgba(255, 255, 255, 0.3) 75%, transparent 75%, transparent);
|
||||
background-size: 32px 32px;
|
||||
animation: simple-pace-stripe-animation 500ms linear infinite;
|
||||
}
|
||||
|
||||
@keyframes simple-pace-stripe-animation {
|
||||
0% { transform: none; }
|
||||
100% { transform: translate(-32px, 0); }
|
||||
}
|
||||
|
|
@ -21,8 +21,6 @@ class LoginAdminView extends AbstractViewCenter {
|
|||
loginError: false,
|
||||
passwordError: false,
|
||||
|
||||
formHidden: false,
|
||||
|
||||
submitRequest: false,
|
||||
submitError: ''
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
import { Scope } from 'Common/Enums';
|
||||
|
||||
import { settingsMenuKeysHandler } from 'Knoin/Knoin';
|
||||
import { AbstractViewLeft } from 'Knoin/AbstractViews';
|
||||
|
||||
class MenuSettingsAdminView extends AbstractViewLeft {
|
||||
|
|
@ -16,11 +13,6 @@ class MenuSettingsAdminView extends AbstractViewLeft {
|
|||
link(route) {
|
||||
return '#/' + route;
|
||||
}
|
||||
|
||||
onBuild(dom) {
|
||||
shortcuts.add('arrowup,arrowdown', '', Scope.Settings,
|
||||
settingsMenuKeysHandler(dom.querySelectorAll('.b-admin-menu .e-item')));
|
||||
}
|
||||
}
|
||||
|
||||
export { MenuSettingsAdminView };
|
||||
|
|
|
|||
|
|
@ -419,7 +419,24 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
setFolderHash(sSentFolder, '');
|
||||
|
||||
Remote.sendMessage(
|
||||
this.sendMessageResponse.bind(this),
|
||||
(iError, data) => {
|
||||
this.sending(false);
|
||||
if (this.modalVisibility()) {
|
||||
if (iError) {
|
||||
if (Notification.CantSaveMessage === iError) {
|
||||
this.sendSuccessButSaveError(true);
|
||||
this.savedErrorDesc(i18n('COMPOSE/SAVED_ERROR_ON_SEND').trim());
|
||||
} else {
|
||||
this.sendError(true);
|
||||
this.sendErrorDesc(getNotification(iError, data && data.ErrorMessage)
|
||||
|| getNotification(Notification.CantSendMessage));
|
||||
}
|
||||
} else {
|
||||
this.closeCommand && this.closeCommand();
|
||||
}
|
||||
}
|
||||
this.reloadDraftFolder();
|
||||
},
|
||||
this.getMessageRequestParams(sSentFolder)
|
||||
);
|
||||
}
|
||||
|
|
@ -438,7 +455,40 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
setFolderHash(FolderUserStore.draftFolder(), '');
|
||||
|
||||
Remote.saveMessage(
|
||||
this.saveMessageResponse.bind(this),
|
||||
(iError, oData) => {
|
||||
let result = false;
|
||||
|
||||
this.saving(false);
|
||||
|
||||
if (!iError) {
|
||||
if (oData.Result.NewFolder && oData.Result.NewUid) {
|
||||
result = true;
|
||||
|
||||
if (this.bFromDraft) {
|
||||
const message = MessageUserStore.message();
|
||||
if (message && this.draftFolder() === message.folder && this.draftUid() === message.uid) {
|
||||
MessageUserStore.message(null);
|
||||
}
|
||||
}
|
||||
|
||||
this.draftFolder(oData.Result.NewFolder);
|
||||
this.draftUid(oData.Result.NewUid);
|
||||
|
||||
this.savedTime(new Date);
|
||||
|
||||
if (this.bFromDraft) {
|
||||
setFolderHash(this.draftFolder(), '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
this.savedError(true);
|
||||
this.savedErrorDesc(getNotification(Notification.CantSaveMessage));
|
||||
}
|
||||
|
||||
this.reloadDraftFolder();
|
||||
},
|
||||
this.getMessageRequestParams(FolderUserStore.draftFolder())
|
||||
);
|
||||
}
|
||||
|
|
@ -578,63 +628,6 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
}
|
||||
}
|
||||
|
||||
sendMessageResponse(iError, data) {
|
||||
|
||||
this.sending(false);
|
||||
|
||||
if (this.modalVisibility()) {
|
||||
if (iError) {
|
||||
if (Notification.CantSaveMessage === iError) {
|
||||
this.sendSuccessButSaveError(true);
|
||||
this.savedErrorDesc(i18n('COMPOSE/SAVED_ERROR_ON_SEND').trim());
|
||||
} else {
|
||||
this.sendError(true);
|
||||
this.sendErrorDesc(getNotification(iError, data && data.ErrorMessage)
|
||||
|| getNotification(Notification.CantSendMessage));
|
||||
}
|
||||
} else {
|
||||
this.closeCommand && this.closeCommand();
|
||||
}
|
||||
}
|
||||
|
||||
this.reloadDraftFolder();
|
||||
}
|
||||
|
||||
saveMessageResponse(iError, oData) {
|
||||
let result = false;
|
||||
|
||||
this.saving(false);
|
||||
|
||||
if (!iError) {
|
||||
if (oData.Result.NewFolder && oData.Result.NewUid) {
|
||||
result = true;
|
||||
|
||||
if (this.bFromDraft) {
|
||||
const message = MessageUserStore.message();
|
||||
if (message && this.draftFolder() === message.folder && this.draftUid() === message.uid) {
|
||||
MessageUserStore.message(null);
|
||||
}
|
||||
}
|
||||
|
||||
this.draftFolder(oData.Result.NewFolder);
|
||||
this.draftUid(oData.Result.NewUid);
|
||||
|
||||
this.savedTime(new Date);
|
||||
|
||||
if (this.bFromDraft) {
|
||||
setFolderHash(this.draftFolder(), '');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
this.savedError(true);
|
||||
this.savedErrorDesc(getNotification(Notification.CantSaveMessage));
|
||||
}
|
||||
|
||||
this.reloadDraftFolder();
|
||||
}
|
||||
|
||||
onHide() {
|
||||
// Stop autosave
|
||||
clearTimeout(this.iTimer);
|
||||
|
|
@ -748,12 +741,6 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
}
|
||||
}
|
||||
|
||||
onWarmUp() {
|
||||
if (this.modalVisibility && !this.modalVisibility()) {
|
||||
this.editor(editor => editor.modeWysiwyg());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Function} fKoValue
|
||||
* @param {Array} emails
|
||||
|
|
@ -1146,6 +1133,8 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
ro.header = dom.querySelector('.b-header');
|
||||
ro.toolbar = dom.querySelector('.b-header-toolbar');
|
||||
ro.els = [dom.querySelector('.textAreaParent'), dom.querySelector('.attachmentAreaParent')];
|
||||
|
||||
this.editor(editor => editor.modeWysiwyg());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class ContactsPopupView extends AbstractViewPopup {
|
|||
|
||||
this.bDropPageAfterDelete = false;
|
||||
|
||||
// this.saveCommandDebounce = _.debounce(this.saveCommand.bind(this), 1000);
|
||||
// this.saveCommandDebounce = this.saveCommand.bind(this).debounce(1000);
|
||||
|
||||
const
|
||||
// propertyFocused = property => !property.isValid() && !property.focused(),
|
||||
|
|
@ -382,26 +382,18 @@ class ContactsPopupView extends AbstractViewPopup {
|
|||
|
||||
deleteSelectedContacts() {
|
||||
if (this.contactsCheckedOrSelected().length) {
|
||||
Remote.contactsDelete(this.deleteResponse.bind(this), this.contactsCheckedOrSelectedUids());
|
||||
Remote.contactsDelete((iError, oData) => {
|
||||
if (500 < (!iError && oData && oData.Time ? pInt(oData.Time) : 0)) {
|
||||
this.reloadContactList(this.bDropPageAfterDelete);
|
||||
} else {
|
||||
setTimeout(() => this.reloadContactList(this.bDropPageAfterDelete), 500);
|
||||
}
|
||||
}, this.contactsCheckedOrSelectedUids());
|
||||
|
||||
this.removeCheckedOrSelectedContactsFromList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} iError
|
||||
* @param {FetchJsonDefaultResponse} oData
|
||||
*/
|
||||
deleteResponse(iError, oData) {
|
||||
if (500 < (!iError && oData && oData.Time ? pInt(oData.Time) : 0)) {
|
||||
this.reloadContactList(this.bDropPageAfterDelete);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.reloadContactList(this.bDropPageAfterDelete);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
removeProperty(oProp) {
|
||||
this.viewProperties.remove(oProp);
|
||||
delegateRunOnDestroy(oProp);
|
||||
|
|
|
|||
|
|
@ -187,7 +187,48 @@ class DomainPopupView extends AbstractViewPopup {
|
|||
this.testing(true);
|
||||
|
||||
Remote.testConnectionForDomain(
|
||||
this.onTestConnectionResponse.bind(this),
|
||||
(iError, oData) => {
|
||||
this.testing(false);
|
||||
if (iError) {
|
||||
this.testingImapError(true);
|
||||
this.testingSieveError(true);
|
||||
this.testingSmtpError(true);
|
||||
this.sieveSettings(false);
|
||||
} else {
|
||||
let bImap = false,
|
||||
bSieve = false;
|
||||
|
||||
this.testingDone(true);
|
||||
this.testingImapError(true !== oData.Result.Imap);
|
||||
this.testingSieveError(true !== oData.Result.Sieve);
|
||||
this.testingSmtpError(true !== oData.Result.Smtp);
|
||||
|
||||
if (this.testingImapError() && oData.Result.Imap) {
|
||||
bImap = true;
|
||||
this.testingImapErrorDesc('');
|
||||
this.testingImapErrorDesc(oData.Result.Imap);
|
||||
}
|
||||
|
||||
if (this.testingSieveError() && oData.Result.Sieve) {
|
||||
bSieve = true;
|
||||
this.testingSieveErrorDesc('');
|
||||
this.testingSieveErrorDesc(oData.Result.Sieve);
|
||||
}
|
||||
|
||||
if (this.testingSmtpError() && oData.Result.Smtp) {
|
||||
this.testingSmtpErrorDesc('');
|
||||
this.testingSmtpErrorDesc(oData.Result.Smtp);
|
||||
}
|
||||
|
||||
if (this.sieveSettings()) {
|
||||
if (!bSieve && bImap) {
|
||||
this.sieveSettings(false);
|
||||
}
|
||||
} else if (bSieve && !bImap) {
|
||||
this.sieveSettings(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
|
@ -205,49 +246,6 @@ class DomainPopupView extends AbstractViewPopup {
|
|||
this.clearTesting();
|
||||
}
|
||||
|
||||
onTestConnectionResponse(iError, oData) {
|
||||
this.testing(false);
|
||||
if (iError) {
|
||||
this.testingImapError(true);
|
||||
this.testingSieveError(true);
|
||||
this.testingSmtpError(true);
|
||||
this.sieveSettings(false);
|
||||
} else {
|
||||
let bImap = false,
|
||||
bSieve = false;
|
||||
|
||||
this.testingDone(true);
|
||||
this.testingImapError(true !== oData.Result.Imap);
|
||||
this.testingSieveError(true !== oData.Result.Sieve);
|
||||
this.testingSmtpError(true !== oData.Result.Smtp);
|
||||
|
||||
if (this.testingImapError() && oData.Result.Imap) {
|
||||
bImap = true;
|
||||
this.testingImapErrorDesc('');
|
||||
this.testingImapErrorDesc(oData.Result.Imap);
|
||||
}
|
||||
|
||||
if (this.testingSieveError() && oData.Result.Sieve) {
|
||||
bSieve = true;
|
||||
this.testingSieveErrorDesc('');
|
||||
this.testingSieveErrorDesc(oData.Result.Sieve);
|
||||
}
|
||||
|
||||
if (this.testingSmtpError() && oData.Result.Smtp) {
|
||||
this.testingSmtpErrorDesc('');
|
||||
this.testingSmtpErrorDesc(oData.Result.Smtp);
|
||||
}
|
||||
|
||||
if (this.sieveSettings()) {
|
||||
if (!bSieve && bImap) {
|
||||
this.sieveSettings(false);
|
||||
}
|
||||
} else if (bSieve && !bImap) {
|
||||
this.sieveSettings(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onDomainCreateOrSaveResponse(iError) {
|
||||
this.saving(false);
|
||||
if (iError) {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,7 @@ class LanguagesPopupView extends AbstractViewPopup {
|
|||
}
|
||||
|
||||
languageTooltipName(language) {
|
||||
const result = convertLangName(language, true);
|
||||
return convertLangName(language, false) === result ? '' : result;
|
||||
return convertLangName(language, true);
|
||||
}
|
||||
|
||||
setLanguageSelection() {
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ class PluginPopupView extends AbstractViewPopup {
|
|||
constructor() {
|
||||
super('Plugin');
|
||||
|
||||
this.onPluginSettingsUpdateResponse = this.onPluginSettingsUpdateResponse.bind(this);
|
||||
|
||||
this.addObservables({
|
||||
saveError: '',
|
||||
name: '',
|
||||
|
|
@ -50,15 +48,11 @@ class PluginPopupView extends AbstractViewPopup {
|
|||
});
|
||||
|
||||
this.saveError('');
|
||||
Remote.pluginSettingsUpdate(this.onPluginSettingsUpdateResponse, list);
|
||||
}
|
||||
|
||||
onPluginSettingsUpdateResponse(iError) {
|
||||
if (iError) {
|
||||
this.saveError(getNotification(iError));
|
||||
} else {
|
||||
this.cancelCommand();
|
||||
}
|
||||
Remote.pluginSettingsUpdate(iError =>
|
||||
iError
|
||||
? this.saveError(getNotification(iError))
|
||||
: this.cancelCommand()
|
||||
, list);
|
||||
}
|
||||
|
||||
onShow(oPlugin) {
|
||||
|
|
|
|||
|
|
@ -46,8 +46,6 @@ class LoginUserView extends AbstractViewCenter {
|
|||
emailError: false,
|
||||
passwordError: false,
|
||||
|
||||
formHidden: false,
|
||||
|
||||
submitRequest: false,
|
||||
submitError: '',
|
||||
submitErrorAddidional: '',
|
||||
|
|
@ -57,9 +55,6 @@ class LoginUserView extends AbstractViewCenter {
|
|||
signMeType: LoginSignMeType.Unused
|
||||
});
|
||||
|
||||
this.forgotPasswordLinkUrl = Settings.app('forgotPasswordLinkUrl');
|
||||
this.registrationLinkUrl = Settings.app('registrationLinkUrl');
|
||||
|
||||
this.formError = ko.observable(false).extend({ falseTimeout: 500 });
|
||||
|
||||
this.allowLanguagesOnLogin = !!SettingsGet('AllowLanguagesOnLogin');
|
||||
|
|
@ -127,7 +122,8 @@ class LoginUserView extends AbstractViewCenter {
|
|||
if (Notification.InvalidInputArgument == iError) {
|
||||
iError = Notification.AuthError;
|
||||
}
|
||||
this.submitError(getNotification(iError, oData.ErrorMessage, Notification.UnknownNotification));
|
||||
this.submitError(getNotification(iError, (oData ? oData.ErrorMessage : ''),
|
||||
Notification.UnknownNotification));
|
||||
this.submitErrorAddidional((oData && oData.ErrorMessageAdditional) || '');
|
||||
} else {
|
||||
rl.route.reload();
|
||||
|
|
|
|||
|
|
@ -108,16 +108,16 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
|
|||
}
|
||||
}
|
||||
|
||||
el = eqs(event, '.b-folders .e-item .e-link.selectable .inbox-star-icon');
|
||||
el = eqs(event, '.b-folders .e-item a.selectable .inbox-star-icon');
|
||||
el && fSelectFolder(el, event, !this.isInboxStarred());
|
||||
|
||||
el = eqs(event, '.b-folders .e-item .e-link.selectable');
|
||||
el = eqs(event, '.b-folders .e-item a.selectable');
|
||||
el && fSelectFolder(el, event, false);
|
||||
});
|
||||
|
||||
shortcuts.add('arrowup,arrowdown', '', Scope.FolderList, event => {
|
||||
let items = [], index = 0;
|
||||
dom.querySelectorAll('.b-folders .e-item .e-link:not(.hidden)').forEach(node => {
|
||||
dom.querySelectorAll('.b-folders .e-item a:not(.hidden)').forEach(node => {
|
||||
if (node.offsetHeight || node.getClientRects().length) {
|
||||
items.push(node);
|
||||
if (node.matches('.focused')) {
|
||||
|
|
@ -140,7 +140,7 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
|
|||
});
|
||||
|
||||
shortcuts.add('enter,open', '', Scope.FolderList, () => {
|
||||
const item = qs('.b-folders .e-item .e-link:not(.hidden).focused');
|
||||
const item = qs('.b-folders .e-item a:not(.hidden).focused');
|
||||
if (item) {
|
||||
AppUserStore.focusedState(Scope.MessageList);
|
||||
item.click();
|
||||
|
|
@ -150,7 +150,7 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
|
|||
});
|
||||
|
||||
shortcuts.add('space', '', Scope.FolderList, () => {
|
||||
const item = qs('.b-folders .e-item .e-link:not(.hidden).focused'),
|
||||
const item = qs('.b-folders .e-item a:not(.hidden).focused'),
|
||||
folder = item && ko.dataFor(item);
|
||||
if (folder) {
|
||||
const collapsed = folder.collapsed();
|
||||
|
|
@ -169,11 +169,11 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
|
|||
});
|
||||
|
||||
AppUserStore.focusedState.subscribe(value => {
|
||||
let el = qs('.b-folders .e-item .e-link.focused');
|
||||
el && qs('.b-folders .e-item .e-link.focused').classList.remove('focused');
|
||||
let el = qs('.b-folders .e-item a.focused');
|
||||
el && qs('.b-folders .e-item a.focused').classList.remove('focused');
|
||||
if (Scope.FolderList === value) {
|
||||
el = qs('.b-folders .e-item .e-link.selected');
|
||||
el && qs('.b-folders .e-item .e-link.selected').classList.add('focused');
|
||||
el = qs('.b-folders .e-item a.selected');
|
||||
el && qs('.b-folders .e-item a.selected').classList.add('focused');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
|
|||
scrollToFocused() {
|
||||
const scrollable = this.oContentScrollable;
|
||||
if (scrollable) {
|
||||
let block, focused = scrollable.querySelector('.e-item .e-link.focused');
|
||||
let block, focused = scrollable.querySelector('.e-item a.focused');
|
||||
if (focused) {
|
||||
const fRect = focused.getBoundingClientRect(),
|
||||
sRect = scrollable.getBoundingClientRect();
|
||||
|
|
|
|||
|
|
@ -263,8 +263,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
|
||||
addEventListener('mailbox.message-view.toggle-full-screen', () => this.toggleFullScreen());
|
||||
|
||||
this.attachmentPreview = this.attachmentPreview.bind(this);
|
||||
|
||||
decorateKoCommands(this, {
|
||||
closeMessageCommand: 1,
|
||||
messageEditCommand: self => self.messageVisibility(),
|
||||
|
|
@ -313,57 +311,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
this.oHeaderDom && this.viewBodyTopValue(this.message() ? this.oHeaderDom.offsetHeight : 0);
|
||||
}
|
||||
|
||||
// displayMailToPopup(sMailToUrl) {
|
||||
// sMailToUrl = sMailToUrl.replace(/\?.+$/, '');
|
||||
//
|
||||
// var
|
||||
// sResult = '',
|
||||
// aTo = [],
|
||||
// EmailModel = require('Model/Email').default,
|
||||
// fParseEmailLine = function(sLine) {
|
||||
// return sLine ? [decodeURIComponent(sLine)].map(sItem => {
|
||||
// var oEmailModel = new EmailModel();
|
||||
// oEmailModel.parse(sItem);
|
||||
// return oEmailModel.email ? oEmailModel : null;
|
||||
// }).filter(v => v) : null;
|
||||
// }
|
||||
// ;
|
||||
//
|
||||
// aTo = fParseEmailLine(sMailToUrl);
|
||||
// sResult = aTo && aTo[0] ? aTo[0].email : '';
|
||||
//
|
||||
// return sResult;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @param {Object} oAttachment
|
||||
* @returns {boolean}
|
||||
*/
|
||||
attachmentPreview(/*attachment*/) {
|
||||
/*
|
||||
if (attachment && attachment.isImage() && !attachment.isLinked && this.message() && this.message().attachments()) {
|
||||
const items = this.message().attachments.map(item => {
|
||||
if (item && !item.isLinked && item.isImage()) {
|
||||
if (item === attachment) {
|
||||
index = listIndex;
|
||||
}
|
||||
++listIndex;
|
||||
return {
|
||||
src: item.linkPreview(),
|
||||
msrc: item.linkThumbnail(),
|
||||
title: item.fileName
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}).filter(v => v);
|
||||
|
||||
if (items.length) {
|
||||
}
|
||||
}
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
onBuild(dom) {
|
||||
this.fullScreenMode.subscribe(value => value && this.message() && AppUserStore.focusedState(Scope.MessageView));
|
||||
|
||||
|
|
@ -476,33 +423,28 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
|
|||
this.initShortcuts();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
escShortcuts() {
|
||||
if (this.viewModelVisible && this.message()) {
|
||||
const preview = SettingsUserStore.usePreviewPane();
|
||||
if (this.fullScreenMode()) {
|
||||
this.fullScreenMode(false);
|
||||
|
||||
if (preview) {
|
||||
AppUserStore.focusedState(Scope.MessageList);
|
||||
}
|
||||
} else if (!preview) {
|
||||
this.message(null);
|
||||
} else {
|
||||
AppUserStore.focusedState(Scope.MessageList);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
initShortcuts() {
|
||||
// exit fullscreen, back
|
||||
shortcuts.add('escape,backspace', '', Scope.MessageView, this.escShortcuts.bind(this));
|
||||
shortcuts.add('escape,backspace', '', Scope.MessageView, () => {
|
||||
if (this.viewModelVisible && this.message()) {
|
||||
const preview = SettingsUserStore.usePreviewPane();
|
||||
if (this.fullScreenMode()) {
|
||||
this.fullScreenMode(false);
|
||||
|
||||
if (preview) {
|
||||
AppUserStore.focusedState(Scope.MessageList);
|
||||
}
|
||||
} else if (!preview) {
|
||||
this.message(null);
|
||||
} else {
|
||||
AppUserStore.focusedState(Scope.MessageList);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// fullscreen
|
||||
shortcuts.add('enter,open', '', Scope.MessageView, () => {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { Scope } from 'Common/Enums';
|
||||
import { leftPanelDisabled } from 'Common/Globals';
|
||||
import { settings, mailbox } from 'Common/Links';
|
||||
import { getFolderInboxName } from 'Common/Cache';
|
||||
|
||||
import { settingsMenuKeysHandler } from 'Knoin/Knoin';
|
||||
import { AbstractViewLeft } from 'Knoin/AbstractViews';
|
||||
|
||||
import { ThemeStore } from 'Stores/Theme';
|
||||
|
|
@ -23,12 +21,9 @@ export class MenuSettingsUserView extends AbstractViewLeft {
|
|||
onBuild(dom) {
|
||||
dom.addEventListener('click', event =>
|
||||
ThemeStore.isMobile()
|
||||
&& event.target.closestWithin('.b-settings-menu .e-item.selectable', dom)
|
||||
&& event.target.closestWithin('.b-settins-left nav a', dom)
|
||||
&& leftPanelDisabled(true)
|
||||
);
|
||||
|
||||
shortcuts.add('arrowup,arrowdown', '', Scope.Settings,
|
||||
settingsMenuKeysHandler(dom.querySelectorAll('.b-settings-menu .e-item')));
|
||||
}
|
||||
|
||||
link(route) {
|
||||
|
|
|
|||
76
dev/boot.js
76
dev/boot.js
|
|
@ -38,17 +38,10 @@ const
|
|||
}
|
||||
return win[name];
|
||||
},
|
||||
STORAGE_KEY = '__rlA',
|
||||
TIME_KEY = '__rlT',
|
||||
AUTH_KEY = 'AuthAccountHash',
|
||||
storage = Storage('session'),
|
||||
timestamp = () => Math.round(Date.now() / 1000),
|
||||
setTimestamp = () => storage.setItem(TIME_KEY, timestamp()),
|
||||
|
||||
showError = () => {
|
||||
eId('rl-loading').hidden = true;
|
||||
eId('rl-loading-error').hidden = false;
|
||||
p.end();
|
||||
},
|
||||
|
||||
loadScript = src => {
|
||||
|
|
@ -57,29 +50,12 @@ const
|
|||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = doc.createElement('script');
|
||||
script.onload = () => {
|
||||
p.set(pStep += step);
|
||||
resolve();
|
||||
};
|
||||
script.onload = () => resolve();
|
||||
script.onerror = () => reject(new Error(src));
|
||||
script.src = src;
|
||||
// script.async = true;
|
||||
doc.head.append(script);
|
||||
});
|
||||
},
|
||||
|
||||
step = 100 / 7,
|
||||
p = win.progressJs = {
|
||||
set: percent => progress.style.width = Math.min(percent, 100) + '%',
|
||||
end: () => {
|
||||
if (container) {
|
||||
p.set(100);
|
||||
setTimeout(() => {
|
||||
container.remove();
|
||||
container = progress = null;
|
||||
}, 600);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!navigator || !navigator.cookieEnabled) {
|
||||
|
|
@ -89,37 +65,15 @@ if (!navigator || !navigator.cookieEnabled) {
|
|||
const layout = getCookie('rllayout');
|
||||
doc.documentElement.classList.toggle('rl-mobile', 'mobile' === layout || (!layout && 1000 > innerWidth));
|
||||
|
||||
let pStep = 0,
|
||||
container = eId('progressjs'),
|
||||
progress = container.querySelector('.progressjs-inner'),
|
||||
|
||||
let progress = eId('progressjs'),
|
||||
RL_APP_DATA = {};
|
||||
|
||||
if (progress) {
|
||||
progress.remove();
|
||||
progress = null;
|
||||
}
|
||||
|
||||
win.rl = {
|
||||
hash: {
|
||||
// getHash
|
||||
get: () => storage.getItem(STORAGE_KEY) || null,
|
||||
// setHash
|
||||
set: () => {
|
||||
storage.setItem(STORAGE_KEY, RL_APP_DATA && RL_APP_DATA[AUTH_KEY]
|
||||
? RL_APP_DATA[AUTH_KEY] : '');
|
||||
setTimestamp();
|
||||
},
|
||||
// clearHash
|
||||
clear: () => {
|
||||
storage.setItem(STORAGE_KEY, '');
|
||||
setTimestamp();
|
||||
},
|
||||
// checkTimestamp
|
||||
check: () => {
|
||||
if (timestamp() > (parseInt(storage.getItem(TIME_KEY) || 0, 10) || 0) + 3600000) {
|
||||
// 60m
|
||||
rl.hash.clear();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
data: () => RL_APP_DATA,
|
||||
adminArea: () => admin,
|
||||
settings: {
|
||||
|
|
@ -142,15 +96,8 @@ win.rl = {
|
|||
initData: appData => {
|
||||
RL_APP_DATA = appData;
|
||||
|
||||
rl.hash.set();
|
||||
|
||||
if (appData) {
|
||||
if (appData.NewThemeLink) {
|
||||
eId('app-theme-link').href = appData.NewThemeLink;
|
||||
}
|
||||
|
||||
loadScript(appData.StaticLibJsLink)
|
||||
.then(() => Promise.all([loadScript(appData.TemplatesLink), loadScript(appData.LangLink)]))
|
||||
.then(() => loadScript(appData.StaticAppJsLink))
|
||||
.then(() => appData.PluginsLink ? loadScript(appData.PluginsLink) : Promise.resolve())
|
||||
.then(() => win.__APP_BOOT ? win.__APP_BOOT(showError) : showError())
|
||||
|
|
@ -164,16 +111,11 @@ win.rl = {
|
|||
}
|
||||
};
|
||||
|
||||
p.set(1);
|
||||
|
||||
Storage('local');
|
||||
|
||||
// init section
|
||||
setInterval(setTimestamp, 60000); // 1m
|
||||
eId('app-css').href = eId('app-css').dataset.href;
|
||||
|
||||
[eId('app-css'),eId('app-theme-link')].forEach(css => css.href = css.dataset.href);
|
||||
|
||||
loadScript(`./?/${admin ? 'Admin' : ''}AppData/${rl.hash.get() || '0'}/${Math.random().toString().substr(2)}/`)
|
||||
loadScript(`./?/${admin ? 'Admin' : ''}AppData/0/${Math.random().toString().substr(2)}/`)
|
||||
.then(() => {});
|
||||
|
||||
})(this);
|
||||
|
|
|
|||
20
dev/bootstrap.js
vendored
20
dev/bootstrap.js
vendored
|
|
@ -1,9 +1,9 @@
|
|||
import { doc, elementById, dropdownVisibility, Settings } from 'Common/Globals';
|
||||
import { doc, dropdownVisibility, Settings } from 'Common/Globals';
|
||||
import { i18n } from 'Common/Translator';
|
||||
|
||||
import { root } from 'Common/Links';
|
||||
|
||||
export default (App) => {
|
||||
export default App => {
|
||||
|
||||
addEventListener('keydown', event => {
|
||||
event = event || window.event;
|
||||
|
|
@ -135,17 +135,11 @@ export default (App) => {
|
|||
});
|
||||
};
|
||||
|
||||
window.__APP_BOOT = fErrorCallback => {
|
||||
const cb = () => setTimeout(() => {
|
||||
if (rl.TEMPLATES) {
|
||||
elementById('rl-templates').innerHTML = rl.TEMPLATES;
|
||||
setTimeout(() => App.bootstart(), 10);
|
||||
} else {
|
||||
fErrorCallback();
|
||||
}
|
||||
|
||||
window.__APP_BOOT = null;
|
||||
}, 10);
|
||||
window.__APP_BOOT = () => {
|
||||
const cb = () => {
|
||||
window.__APP_BOOT = null;
|
||||
App.bootstart();
|
||||
};
|
||||
('loading' !== doc.readyState) ? cb() : doc.addEventListener('DOMContentLoaded', cb);
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"title": "SnappyMail",
|
||||
"description": "Simple, modern & fast web-based email client",
|
||||
"private": true,
|
||||
"version": "2.5.0-rc.3",
|
||||
"version": "2.5.2",
|
||||
"homepage": "https://snappymail.eu",
|
||||
"author": {
|
||||
"name": "DJ Maze",
|
||||
|
|
@ -64,7 +64,6 @@
|
|||
"gulp-terser": "^1.4.0",
|
||||
"gulp-util": "3.0.8",
|
||||
"node-fs": "0.1.7",
|
||||
"openpgp": "2.6.2",
|
||||
"rimraf": "3.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ class AutoDomainGrabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = '',
|
||||
VERSION = '2.1',
|
||||
REQUIRED = '2.5.0',
|
||||
CATEGORY = 'General',
|
||||
DESCRIPTION = '';
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ class BlackListPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Black list',
|
||||
VERSION = '2.0',
|
||||
VERSION = '2.1',
|
||||
RELEASE = '2021-04-21',
|
||||
REQUIRED = '2.5.0',
|
||||
CATEGORY = 'Login',
|
||||
DESCRIPTION = 'Simple black list plugin (with wildcard and exceptions functionality).';
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ class CKEditorPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
const
|
||||
NAME = 'CKEditor',
|
||||
VERSION = '2.1',
|
||||
RELEASE = '2021-04-01',
|
||||
RELEASE = '2021-04-19',
|
||||
REQUIRED = '2.5.0',
|
||||
DESCRIPTION = 'Use CKEditor instead of Squire as WYSIWYG';
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ class CustomAuthExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = '',
|
||||
VERSION = '2.1',
|
||||
REQUIRED = '2.5.0',
|
||||
CATEGORY = 'Login',
|
||||
DESCRIPTION = '';
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ class CustomLoginMappingPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Custom Login Mapping',
|
||||
VERSION = '2.0',
|
||||
VERSION = '2.1',
|
||||
RELEASE = '2021-04-21',
|
||||
REQUIRED = '2.5.0',
|
||||
CATEGORY = 'Login',
|
||||
DESCRIPTION = 'Plugin which allows you to set up custom username by email address.';
|
||||
|
||||
|
|
|
|||
33
plugins/login-register/LoginRegister.js
Normal file
33
plugins/login-register/LoginRegister.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
(rl => {
|
||||
if (rl) {
|
||||
const
|
||||
forgotUrl = rl.settings.get('forgotPasswordLinkUrl'),
|
||||
registerUrl = rl.settings.get('registrationLinkUrl');
|
||||
|
||||
if (forgotUrl || registerUrl) {
|
||||
addEventListener('rl-view-model', e => {
|
||||
if (e.detail && 'Login' === e.detail.viewModelTemplateID) {
|
||||
const container = e.detail.viewModelDom.querySelector('#plugin-Login-BottomControlGroup'),
|
||||
forgot = 'LOGIN/LABEL_FORGOT_PASSWORD',
|
||||
register = 'LOGIN/LABEL_REGISTRATION';
|
||||
if (container) {
|
||||
let html = '';
|
||||
if (forgotUrl) {
|
||||
html = html + '<p class="forgot-link">'
|
||||
+ '<a href="'+forgotUrl+'" target="_blank" class="g-ui-link" data-i18n="'+forgot+'">'+rl.i18n(forgot)+'</a>'
|
||||
+ '</p>';
|
||||
}
|
||||
if (registerUrl) {
|
||||
html = html + '<p class="registration-link">'
|
||||
+ '<a href="'+registerUrl+'" target="_blank" class="g-ui-link" data-i18n="'+register+'">'+rl.i18n(register)+'</a>'
|
||||
+ '</p>';
|
||||
}
|
||||
container.append(Element.fromHTML('<div class="controls clearfix">' + html + '</div>'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
})(window.rl);
|
||||
42
plugins/login-register/index.php
Normal file
42
plugins/login-register/index.php
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
class LoginRegisterPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
const
|
||||
NAME = 'Register and Forgot',
|
||||
VERSION = '2.0',
|
||||
RELEASE = '2021-06-01',
|
||||
REQUIRED = '2.5.2',
|
||||
CATEGORY = 'Login',
|
||||
DESCRIPTION = 'Links on login screen for registration and forgot password';
|
||||
|
||||
public function Init() : void
|
||||
{
|
||||
$this->UseLangs(true);
|
||||
$this->addJs('LoginRegister.js');
|
||||
$this->addHook('filter.app-data', 'FilterAppData');
|
||||
}
|
||||
|
||||
public function configMapping() : array
|
||||
{
|
||||
return [
|
||||
\RainLoop\Plugins\Property::NewInstance("forgot_password_link_url")
|
||||
// ->SetLabel('TAB_LOGIN/LABEL_FORGOT_PASSWORD_LINK_URL')
|
||||
->SetLabel('Forgot password url')
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::URL),
|
||||
\RainLoop\Plugins\Property::NewInstance("registration_link_url")
|
||||
// ->SetLabel('TAB_LOGIN/LABEL_REGISTRATION_LINK_URL')
|
||||
->SetLabel('Register url')
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::URL),
|
||||
];
|
||||
}
|
||||
|
||||
public function FilterAppData($bAdmin, &$aResult)
|
||||
{
|
||||
if (!$bAdmin && \is_array($aResult) && empty($aResult['Auth'])) {
|
||||
$aResult['forgotPasswordLinkUrl'] = \trim($this->Config()->Get('plugin', 'forgot_password_link_url', ''));
|
||||
$aResult['registrationLinkUrl'] = \trim($this->Config()->Get('plugin', 'registration_link_url', ''));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
10
plugins/login-register/langs/de-DE.json
Normal file
10
plugins/login-register/langs/de-DE.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD": "Passwort vergessen",
|
||||
"LABEL_REGISTRATION": "Registrieren"
|
||||
},
|
||||
"TAB_LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
|
||||
"LABEL_REGISTRATION_LINK_URL": "Register url"
|
||||
}
|
||||
}
|
||||
10
plugins/login-register/langs/en.json
Normal file
10
plugins/login-register/langs/en.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD": "Forgot password",
|
||||
"LABEL_REGISTRATION": "Register"
|
||||
},
|
||||
"TAB_LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
|
||||
"LABEL_REGISTRATION_LINK_URL": "Register url"
|
||||
}
|
||||
}
|
||||
10
plugins/login-register/langs/es-ES.json
Normal file
10
plugins/login-register/langs/es-ES.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD": "Olvidé mi contraseña",
|
||||
"LABEL_REGISTRATION": "Registrarse"
|
||||
},
|
||||
"TAB_LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
|
||||
"LABEL_REGISTRATION_LINK_URL": "Register url"
|
||||
}
|
||||
}
|
||||
10
plugins/login-register/langs/fr-FR.json
Normal file
10
plugins/login-register/langs/fr-FR.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD": "Mot de passe oublié",
|
||||
"LABEL_REGISTRATION": "S'enregistrer"
|
||||
},
|
||||
"TAB_LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
|
||||
"LABEL_REGISTRATION_LINK_URL": "Register url"
|
||||
}
|
||||
}
|
||||
10
plugins/login-register/langs/hu-HU.json
Normal file
10
plugins/login-register/langs/hu-HU.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD": "Elfelejtett jelszó",
|
||||
"LABEL_REGISTRATION": "Regisztráció"
|
||||
},
|
||||
"TAB_LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
|
||||
"LABEL_REGISTRATION_LINK_URL": "Register url"
|
||||
}
|
||||
}
|
||||
10
plugins/login-register/langs/nl-NL.json
Normal file
10
plugins/login-register/langs/nl-NL.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD": "Wachtwoord vergeten",
|
||||
"LABEL_REGISTRATION": "Registreren"
|
||||
},
|
||||
"TAB_LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
|
||||
"LABEL_REGISTRATION_LINK_URL": "Register url"
|
||||
}
|
||||
}
|
||||
10
plugins/login-register/langs/sv-SE.json
Normal file
10
plugins/login-register/langs/sv-SE.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD": "Glömt lösenord",
|
||||
"LABEL_REGISTRATION": "Registrera"
|
||||
},
|
||||
"TAB_LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
|
||||
"LABEL_REGISTRATION_LINK_URL": "Register url"
|
||||
}
|
||||
}
|
||||
10
plugins/login-register/langs/zh-CN.json
Normal file
10
plugins/login-register/langs/zh-CN.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD": "忘记密码",
|
||||
"LABEL_REGISTRATION": "注册"
|
||||
},
|
||||
"TAB_LOGIN": {
|
||||
"LABEL_FORGOT_PASSWORD_LINK_URL": "Forgot password url",
|
||||
"LABEL_REGISTRATION_LINK_URL": "Register url"
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,9 @@ class OverrideSmtpCredentialsPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'Override Smtp Credentials',
|
||||
VERSION = '2.0',
|
||||
VERSION = '2.1',
|
||||
RELEASE = '2021-04-21',
|
||||
REQUIRED = '2.5.0',
|
||||
CATEGORY = 'Filters',
|
||||
DESCRIPTION = 'Plugin which allows you to override smtp credentials specified users.';
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ class ProxyauthLoginExamplePlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = '',
|
||||
VERSION = '2.1',
|
||||
REQUIRED = '2.5.0',
|
||||
CATEGORY = 'General',
|
||||
DESCRIPTION = '';
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ class WhiteListPlugin extends \RainLoop\Plugins\AbstractPlugin
|
|||
{
|
||||
const
|
||||
NAME = 'White list',
|
||||
VERSION = '2.0',
|
||||
VERSION = '2.1',
|
||||
RELEASE = '2021-04-21',
|
||||
REQUIRED = '2.5.0',
|
||||
CATEGORY = 'Login',
|
||||
DESCRIPTION = 'Simple white list plugin (with wildcard and exceptions functionality).';
|
||||
|
||||
|
|
|
|||
|
|
@ -145,6 +145,10 @@ if ($brotli = trim(`which brotli`)) {
|
|||
unlink(__DIR__ . '/snappymail/v/0.0.0/static/js/min/boot.min.js.br');
|
||||
}
|
||||
|
||||
unlink(__DIR__ . '/snappymail/v/0.0.0/static/js/openpgp.js');
|
||||
unlink(__DIR__ . '/snappymail/v/0.0.0/static/js/openpgp.js.br');
|
||||
unlink(__DIR__ . '/snappymail/v/0.0.0/static/js/openpgp.js.gz');
|
||||
|
||||
// Temporary rename folder to speed up PharData
|
||||
//if (!rename('snappymail/v/0.0.0', "snappymail/v/{$package->version}")){
|
||||
if (!rename('snappymail/v/0.0.0', "snappymail/v/{$package->version}")) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@ if (!\defined('RAINLOOP_APP_LIBRARIES_PATH'))
|
|||
|
||||
if (\class_exists('RainLoop\Api'))
|
||||
{
|
||||
if (!\SnappyMail\HTTP\SecFetch::isEntering() && !\SnappyMail\HTTP\SecFetch::isSameOrigin()) {
|
||||
\MailSo\Base\Http::StatusHeader(403);
|
||||
exit('Disallowed Sec-Fetch-Site: ' . ($_SERVER['HTTP_SEC_FETCH_SITE'] ?? ''));
|
||||
}
|
||||
|
||||
\MailSo\Base\Loader::Init();
|
||||
|
||||
if (!empty($_ENV['RAINLOOP_INCLUDE_AS_API']))
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ abstract class DateTimeHelper
|
|||
}
|
||||
|
||||
$sDateTime = \trim(\preg_replace('/ \([a-zA-Z0-9]+\)$/', '', $sDateTime));
|
||||
$oDateTime = \DateTime::createFromFormat('D, d M Y H:i:s O', $sDateTime, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject());
|
||||
$oDateTime = \DateTime::createFromFormat(\DateTime::RFC2822, $sDateTime, static::GetUtcTimeZoneObject());
|
||||
return $oDateTime ? $oDateTime->getTimestamp() : 0;
|
||||
}
|
||||
|
||||
|
|
@ -61,10 +61,10 @@ abstract class DateTimeHelper
|
|||
|
||||
if (\preg_match('/^[a-z]{2,4}, /i', $sDateTime)) // RFC2822 ~ "Thu, 10 Jun 2010 08:58:33 -0700 (PDT)"
|
||||
{
|
||||
return \MailSo\Base\DateTimeHelper::ParseRFC2822DateString($sDateTime);
|
||||
return static::ParseRFC2822DateString($sDateTime);
|
||||
}
|
||||
|
||||
$oDateTime = \DateTime::createFromFormat('d-M-Y H:i:s O', $sDateTime, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject());
|
||||
$oDateTime = \DateTime::createFromFormat('d-M-Y H:i:s O', $sDateTime, static::GetUtcTimeZoneObject());
|
||||
return $oDateTime ? $oDateTime->getTimestamp() : 0;
|
||||
}
|
||||
|
||||
|
|
@ -79,7 +79,7 @@ abstract class DateTimeHelper
|
|||
return 0;
|
||||
}
|
||||
|
||||
$oDateTime = \DateTime::createFromFormat('Y-m-d H:i:s O', $sDateTime, \MailSo\Base\DateTimeHelper::GetUtcTimeZoneObject());
|
||||
$oDateTime = \DateTime::createFromFormat('Y-m-d H:i:s O', $sDateTime, static::GetUtcTimeZoneObject());
|
||||
return $oDateTime ? $oDateTime->getTimestamp() : 0;
|
||||
}
|
||||
|
||||
|
|
@ -88,11 +88,16 @@ abstract class DateTimeHelper
|
|||
*/
|
||||
public static function TryToParseSpecEtagFormat(string $sDateTime) : int
|
||||
{
|
||||
$sDateTime = \trim(\preg_replace('/ \([a-zA-Z0-9]+\)$/', '', \trim($sDateTime)));
|
||||
$sDateTime = \trim(\preg_replace('/(:[\d]{2})\.[\d]{3}/', '$1', \trim($sDateTime)));
|
||||
$sDateTime = \trim(\preg_replace('/(-[\d]{2})T([\d]{2}:)/', '$1 $2', \trim($sDateTime)));
|
||||
$sDateTime = \trim(\preg_replace('/([\-+][\d]{2}):([\d]{2})$/', ' $1$2', \trim($sDateTime)));
|
||||
$oDateTime = \DateTime::createFromFormat(\DateTime::RFC3339_EXTENDED, $sDateTime, static::GetUtcTimeZoneObject());
|
||||
if ($oDateTime) {
|
||||
return $oDateTime->getTimestamp();
|
||||
}
|
||||
|
||||
return \MailSo\Base\DateTimeHelper::ParseDateStringType1($sDateTime);
|
||||
$sDateTime = \preg_replace('/ \([a-zA-Z0-9]+\)$/', '', \trim($sDateTime));
|
||||
$sDateTime = \preg_replace('/(:[\d]{2})\.[\d]{3}/', '$1', \trim($sDateTime));
|
||||
$sDateTime = \preg_replace('/(-[\d]{2})T([\d]{2}:)/', '$1 $2', \trim($sDateTime));
|
||||
$sDateTime = \preg_replace('/([\-+][\d]{2}):([\d]{2})$/', ' $1$2', \trim($sDateTime));
|
||||
|
||||
return static::ParseDateStringType1($sDateTime);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,66 +31,6 @@ class Http
|
|||
return $oInstance;
|
||||
}
|
||||
|
||||
public function HasQuery(string $sKey) : bool
|
||||
{
|
||||
return isset($_GET[$sKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mDefault = null
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetQuery(string $sKey, $mDefault = null)
|
||||
{
|
||||
return isset($_GET[$sKey]) ? $_GET[$sKey] : $mDefault;
|
||||
}
|
||||
|
||||
public function GetQueryAsArray() : ?array
|
||||
{
|
||||
return isset($_GET) && \is_array($_GET) ? $_GET : null;
|
||||
}
|
||||
|
||||
public function HasPost(string $sKey) : bool
|
||||
{
|
||||
return isset($_POST[$sKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mDefault = null
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetPost(string $sKey, $mDefault = null)
|
||||
{
|
||||
return isset($_POST[$sKey]) ? $_POST[$sKey] : $mDefault;
|
||||
}
|
||||
|
||||
public function GetPostAsArray() : ?array
|
||||
{
|
||||
return isset($_POST) && \is_array($_POST) ? $_POST : null;
|
||||
}
|
||||
|
||||
public function HasRequest(string $sKey) : bool
|
||||
{
|
||||
return isset($_REQUEST[$sKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mDefault = null
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetRequest(string $sKey, $mDefault = null)
|
||||
{
|
||||
return isset($_REQUEST[$sKey]) ? $_REQUEST[$sKey] : $mDefault;
|
||||
}
|
||||
|
||||
public function HasServer(string $sKey) : bool
|
||||
{
|
||||
return isset($_SERVER[$sKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mDefault = null
|
||||
*
|
||||
|
|
@ -101,26 +41,6 @@ class Http
|
|||
return isset($_SERVER[$sKey]) ? $_SERVER[$sKey] : $mDefault;
|
||||
}
|
||||
|
||||
public function HasEnv(string $sKey) : bool
|
||||
{
|
||||
return isset($_ENV[$sKey]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $mDefault = null
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function GetEnv(string $sKey, $mDefault = null)
|
||||
{
|
||||
return isset($_ENV[$sKey]) ? $_ENV[$sKey] : $mDefault;
|
||||
}
|
||||
|
||||
public function ServerProtocol() : string
|
||||
{
|
||||
return $this->GetServer('SERVER_PROTOCOL', 'HTTP/1.0');
|
||||
}
|
||||
|
||||
public function GetMethod() : string
|
||||
{
|
||||
return $this->GetServer('REQUEST_METHOD', '');
|
||||
|
|
@ -136,11 +56,6 @@ class Http
|
|||
return ('GET' === $this->GetMethod());
|
||||
}
|
||||
|
||||
public function GetQueryString() : string
|
||||
{
|
||||
return $this->GetServer('QUERY_STRING', '');
|
||||
}
|
||||
|
||||
public function CheckLocalhost(string $sServer) : bool
|
||||
{
|
||||
return \in_array(\strtolower(\trim($sServer)), array(
|
||||
|
|
@ -229,7 +144,7 @@ class Http
|
|||
|
||||
if ($bWithRemoteUserData)
|
||||
{
|
||||
$sUser = \trim($this->HasServer('REMOTE_USER') ? $this->GetServer('REMOTE_USER', '') : '');
|
||||
$sUser = \trim($this->GetServer('REMOTE_USER', ''));
|
||||
$sHost = (0 < \strlen($sUser) ? $sUser.'@' : '').$sHost;
|
||||
}
|
||||
|
||||
|
|
@ -529,7 +444,7 @@ class Http
|
|||
}
|
||||
else
|
||||
{
|
||||
$this->StatusHeader(304);
|
||||
static::StatusHeader(304);
|
||||
$bResult = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -570,9 +485,8 @@ class Http
|
|||
}
|
||||
}
|
||||
|
||||
public function StatusHeader(int $iStatus, string $sCustomStatusText = '') : void
|
||||
public static function StatusHeader(int $iStatus, string $sCustomStatusText = '') : void
|
||||
{
|
||||
$iStatus = (int) $iStatus;
|
||||
if (99 < $iStatus)
|
||||
{
|
||||
$aStatus = array(
|
||||
|
|
@ -588,11 +502,15 @@ class Http
|
|||
416 => 'Requested range not satisfiable'
|
||||
);
|
||||
|
||||
$sCustomStatusText = \trim($sCustomStatusText);
|
||||
$sHeaderHead = \ini_get('cgi.rfc2616_headers') && false !== \strpos(\strtolower(\php_sapi_name()), 'cgi') ? 'Status:' : $this->ServerProtocol();
|
||||
$sHeaderText = (0 === \strlen($sCustomStatusText) && isset($aStatus[$iStatus]) ? $aStatus[$iStatus] : $sCustomStatusText);
|
||||
|
||||
\header(\trim($sHeaderHead.' '.$iStatus.' '.$sHeaderText), true, $iStatus);
|
||||
\http_response_code($iStatus);
|
||||
if (isset($_SERVER['SERVER_PROTOCOL'])) {
|
||||
\header("{$_SERVER['SERVER_PROTOCOL']} {$iStatus} {$sHeaderText}", true, $iStatus);
|
||||
}
|
||||
if (\ini_get('cgi.rfc2616_headers') && false !== \strpos(\strtolower(\php_sapi_name()), 'cgi')) {
|
||||
\header("Status: {$iStatus} {$sHeaderText}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -602,9 +520,9 @@ class Http
|
|||
return '' === $sUrl ? '/' : '/'.$sUrl.'/';
|
||||
}
|
||||
|
||||
public function GetUrl()
|
||||
public function GetUrl() : string
|
||||
{
|
||||
return $this->GetServer('REQUEST_URI', '');
|
||||
return $_SERVER['REQUEST_URI'] ?? '';
|
||||
}
|
||||
|
||||
public function GetFullUrl() : string
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class SubStreams
|
|||
|
||||
$sHashName = \MailSo\Base\Utils::Md5Rand();
|
||||
|
||||
self::$aStreams[$sHashName] = array_map(function($mItem) {
|
||||
self::$aStreams[$sHashName] = \array_map(function ($mItem) {
|
||||
return \is_resource($mItem) ? $mItem :
|
||||
\MailSo\Base\ResourceRegistry::CreateMemoryResourceFromString($mItem);
|
||||
}, $aSubStreams);
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ class Xxtea
|
|||
return $aV;
|
||||
}
|
||||
|
||||
private static function int32(int $iN) : int
|
||||
private static function int32($iN) : int
|
||||
{
|
||||
return $iN & 0xffffffff;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace MailSo\Cache\Drivers;
|
|||
* @package Cache
|
||||
* @subpackage Drivers
|
||||
*/
|
||||
class APC implements \MailSo\Cache\DriverInterface
|
||||
class APCU implements \MailSo\Cache\DriverInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
|
|
@ -35,25 +35,25 @@ class APC implements \MailSo\Cache\DriverInterface
|
|||
|
||||
public function Set(string $sKey, string $sValue) : bool
|
||||
{
|
||||
return \apc_store($this->generateCachedKey($sKey), (string) $sValue);
|
||||
return \apcu_store($this->generateCachedKey($sKey), (string) $sValue);
|
||||
}
|
||||
|
||||
public function Get(string $sKey) : string
|
||||
{
|
||||
$sValue = \apc_fetch($this->generateCachedKey($sKey));
|
||||
$sValue = \apcu_fetch($this->generateCachedKey($sKey));
|
||||
return \is_string($sValue) ? $sValue : '';
|
||||
}
|
||||
|
||||
public function Delete(string $sKey) : void
|
||||
{
|
||||
\apc_delete($this->generateCachedKey($sKey));
|
||||
\apcu_delete($this->generateCachedKey($sKey));
|
||||
}
|
||||
|
||||
public function GC(int $iTimeToClearInHours = 24) : bool
|
||||
{
|
||||
if (0 === $iTimeToClearInHours)
|
||||
{
|
||||
return \apc_clear_cache('user');
|
||||
return \apcu_clear_cache('user');
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -104,31 +104,24 @@ class FetchResponse
|
|||
*/
|
||||
public function GetFetchValue(string $sFetchItemName)
|
||||
{
|
||||
$mReturn = null;
|
||||
$bNextIsValue = false;
|
||||
|
||||
if (Enumerations\FetchType::INDEX === $sFetchItemName)
|
||||
{
|
||||
$mReturn = $this->oImapResponse->ResponseList[1];
|
||||
if (Enumerations\FetchType::INDEX === $sFetchItemName) {
|
||||
return $this->oImapResponse->ResponseList[1];
|
||||
}
|
||||
else if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3]))
|
||||
{
|
||||
foreach ($this->oImapResponse->ResponseList[3] as $mItem)
|
||||
{
|
||||
if ($bNextIsValue)
|
||||
{
|
||||
$mReturn = $mItem;
|
||||
break;
|
||||
|
||||
if (isset($this->oImapResponse->ResponseList[3]) && \is_array($this->oImapResponse->ResponseList[3])) {
|
||||
$bNextIsValue = false;
|
||||
foreach ($this->oImapResponse->ResponseList[3] as $mItem) {
|
||||
if ($bNextIsValue) {
|
||||
return $mItem;
|
||||
}
|
||||
|
||||
if ($sFetchItemName === $mItem)
|
||||
{
|
||||
if ($sFetchItemName === $mItem) {
|
||||
$bNextIsValue = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $mReturn;
|
||||
return null;
|
||||
}
|
||||
|
||||
public function GetHeaderFieldsValue(string $sRfc822SubMimeIndex = '') : string
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
if (0 === \strpos($type, 'SCRAM-SHA-'))
|
||||
{
|
||||
$sAuthzid = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), Enumerations\ResponseType::CONTINUATION);
|
||||
$this->sendRaw($SASL->authenticate($sLogin, $sPassword/*, $sAuthzid*/));
|
||||
$this->sendRaw($SASL->authenticate($sLogin, $sPassword/*, $sAuthzid*/), true);
|
||||
$sChallenge = $SASL->challenge($this->getResponseValue($this->getResponse(), Enumerations\ResponseType::CONTINUATION));
|
||||
if ($this->oLogger) {
|
||||
$this->oLogger->AddSecret($sChallenge);
|
||||
|
|
@ -211,20 +211,31 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
$this->oLogger->AddSecret($sAuth);
|
||||
}
|
||||
if ($this->IsSupported('SASL-IR')) {
|
||||
$this->SendRequestGetResponse('AUTHENTICATE', array('PLAIN', $sAuth));
|
||||
$this->SendRequestGetResponse('AUTHENTICATE', array($type, $sAuth));
|
||||
} else {
|
||||
$this->SendRequestGetResponse('AUTHENTICATE', array('PLAIN'));
|
||||
$this->SendRequestGetResponse('AUTHENTICATE', array($type));
|
||||
$this->sendRaw($sAuth, true, '*******');
|
||||
$this->getResponse();
|
||||
}
|
||||
}
|
||||
else if ($this->IsSupported('LOGINDISABLED'))
|
||||
{
|
||||
$sB64 = $this->getResponseValue($this->SendRequestGetResponse('AUTHENTICATE', array($type)), Enumerations\ResponseType::CONTINUATION);
|
||||
$this->sendRaw($SASL->authenticate($sLogin, $sPassword, $sB64), true);
|
||||
$this->getResponse();
|
||||
$sPass = $SASL->challenge(''/*UGFzc3dvcmQ6*/);
|
||||
if ($this->oLogger) {
|
||||
$this->oLogger->AddSecret($sPass);
|
||||
}
|
||||
$this->sendRaw($sPass, true, '*******');
|
||||
$this->getResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($this->oLogger)
|
||||
{
|
||||
$this->oLogger->AddSecret($this->EscapeString($sPassword));
|
||||
}
|
||||
|
||||
$this->SendRequestGetResponse('LOGIN',
|
||||
array(
|
||||
$this->EscapeString($sLogin),
|
||||
|
|
@ -1082,6 +1093,17 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
return $oResult;
|
||||
}
|
||||
|
||||
private function skipBracketParse(?Response $oImapResponse) : bool
|
||||
{
|
||||
return $oImapResponse &&
|
||||
$oImapResponse->ResponseType === \MailSo\Imap\Enumerations\ResponseType::UNTAGGED &&
|
||||
(
|
||||
($oImapResponse->StatusOrIndex === 'STATUS' && 2 === \count($oImapResponse->ResponseList)) ||
|
||||
($oImapResponse->StatusOrIndex === 'LIST' && 4 === \count($oImapResponse->ResponseList)) ||
|
||||
($oImapResponse->StatusOrIndex === 'LSUB' && 4 === \count($oImapResponse->ResponseList))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|string
|
||||
* @throws \MailSo\Net\Exceptions\Exception
|
||||
|
|
@ -1265,8 +1287,16 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
{
|
||||
case ']' === $sChar:
|
||||
case ')' === $sChar:
|
||||
++$iPos;
|
||||
$sPreviousAtomUpperCase = null;
|
||||
if ($this->skipBracketParse($oImapResponse))
|
||||
{
|
||||
$bIsGotoDefault = true;
|
||||
$bIsGotoNotAtomBracket = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
++$iPos;
|
||||
$sPreviousAtomUpperCase = null;
|
||||
}
|
||||
break 2;
|
||||
case ' ' === $sChar:
|
||||
if ($bTreatAsAtom)
|
||||
|
|
@ -1283,12 +1313,20 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
{
|
||||
$sAtomBuilder .= $sChar;
|
||||
$bIsGotoAtomBracket = true;
|
||||
$this->iResponseBufParsedPos = ++$iPos;
|
||||
}
|
||||
else if ($this->skipBracketParse($oImapResponse))
|
||||
{
|
||||
$sOpenBracket = '';
|
||||
$sClosingBracket = '';
|
||||
$bIsGotoDefault = true;
|
||||
$bIsGotoNotAtomBracket = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$bIsGotoNotAtomBracket = true;
|
||||
$this->iResponseBufParsedPos = ++$iPos;
|
||||
}
|
||||
$this->iResponseBufParsedPos = ++$iPos;
|
||||
break;
|
||||
case '{' === $sChar:
|
||||
$bIsLiteralParsed = false;
|
||||
|
|
@ -1416,6 +1454,10 @@ class ImapClient extends \MailSo\Net\NetClient
|
|||
$sCharDef = $this->sResponseBuffer[$iPos];
|
||||
switch (true)
|
||||
{
|
||||
case ('[' === $sCharDef || ']' === $sCharDef || '(' === $sCharDef || ')' === $sCharDef) &&
|
||||
$this->skipBracketParse($oImapResponse):
|
||||
++$iPos;
|
||||
break;
|
||||
case '[' === $sCharDef:
|
||||
if (null === $sAtomBuilder)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -91,6 +91,34 @@ class ResponseCollection extends \MailSo\Base\Collection
|
|||
return $aReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|string $mName
|
||||
*/
|
||||
private function getArrayNameToStringName($mName) : string
|
||||
{
|
||||
if (\is_string($mName))
|
||||
{
|
||||
return $mName;
|
||||
}
|
||||
|
||||
if (\is_array($mName))
|
||||
{
|
||||
if (0 === \count($mName))
|
||||
{
|
||||
return '[]';
|
||||
}
|
||||
|
||||
foreach ($mName as &$mSubName)
|
||||
{
|
||||
$mSubName = "[{$this->getArrayNameToStringName($mSubName)}]";
|
||||
}
|
||||
|
||||
return \implode('', $mName);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getFoldersResult(string $sStatus, bool $bUseListStatus = false) : array
|
||||
{
|
||||
$aReturn = array();
|
||||
|
|
@ -110,15 +138,12 @@ class ResponseCollection extends \MailSo\Base\Collection
|
|||
* https://github.com/the-djmaze/snappymail/issues/1
|
||||
* https://github.com/the-djmaze/snappymail/issues/70
|
||||
*/
|
||||
$sFullNameRaw = \array_slice($oResponse->ResponseList, 4);
|
||||
foreach ($sFullNameRaw as &$name) {
|
||||
if (\is_array($name)) {
|
||||
$name = "[{$name[0]}]";
|
||||
} else if (!\is_string($name)) {
|
||||
$name = '';
|
||||
}
|
||||
$aFullNameRawList = \array_slice($oResponse->ResponseList, 4);
|
||||
foreach ($aFullNameRawList as &$sName) {
|
||||
$sName = $this->getArrayNameToStringName($sName);
|
||||
}
|
||||
$sFullNameRaw = \implode('', $sFullNameRaw);
|
||||
|
||||
$sFullNameRaw = \implode('', $aFullNameRawList);
|
||||
|
||||
$oFolder = new Folder($sFullNameRaw,
|
||||
$oResponse->ResponseList[3], $oResponse->ResponseList[2]);
|
||||
|
|
|
|||
|
|
@ -20,44 +20,32 @@ class FolderCollection extends \MailSo\Base\Collection
|
|||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $Namespace;
|
||||
public $Namespace = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $FoldersHash;
|
||||
public $FoldersHash = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $IsThreadsSupported;
|
||||
public $IsThreadsSupported = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $IsSortSupported;
|
||||
public $IsSortSupported = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $Optimized;
|
||||
public $Optimized = false;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $SystemFolders;
|
||||
|
||||
function __construct($input = array())
|
||||
{
|
||||
parent::__construct($input);
|
||||
|
||||
$this->Namespace = '';
|
||||
$this->FoldersHash = '';
|
||||
$this->SystemFolders = array();
|
||||
$this->IsThreadsSupported = false;
|
||||
$this->IsSortSupported = false;
|
||||
$this->Optimized = false;
|
||||
}
|
||||
public $SystemFolders = array();
|
||||
|
||||
public function append($oFolder, bool $bToTop = false) : void
|
||||
{
|
||||
|
|
@ -118,12 +106,7 @@ class FolderCollection extends \MailSo\Base\Collection
|
|||
$oFolder = $this[0];
|
||||
}
|
||||
|
||||
if ($oFolder)
|
||||
{
|
||||
$sDelimiter = $oFolder->Delimiter();
|
||||
}
|
||||
|
||||
return $sDelimiter;
|
||||
return $oFolder ? $oFolder->Delimiter() : '/';
|
||||
}
|
||||
|
||||
public function SetNamespace(string $sNamespace) : self
|
||||
|
|
@ -133,92 +116,18 @@ class FolderCollection extends \MailSo\Base\Collection
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function InitByUnsortedMailFolderArray(array $aUnsortedMailFolders) : void
|
||||
public function AddWithPositionSearch(Folder $oMailFolder) : void
|
||||
{
|
||||
$this->Clear();
|
||||
|
||||
$aSortedByLenImapFolders = array();
|
||||
foreach ($aUnsortedMailFolders as /* @var $oMailFolder Folder */ $oMailFolder)
|
||||
{
|
||||
$aSortedByLenImapFolders[$oMailFolder->FullNameRaw()] =& $oMailFolder;
|
||||
unset($oMailFolder);
|
||||
}
|
||||
unset($aUnsortedMailFolders);
|
||||
|
||||
$aAddedFolders = array();
|
||||
foreach ($aSortedByLenImapFolders as /* @var $oMailFolder Folder */ $oMailFolder)
|
||||
{
|
||||
$sDelimiter = $oMailFolder->Delimiter();
|
||||
$aFolderExplode = \explode($sDelimiter, $oMailFolder->FullNameRaw());
|
||||
|
||||
if (1 < \count($aFolderExplode))
|
||||
{
|
||||
\array_pop($aFolderExplode);
|
||||
|
||||
$sNonExistenFolderFullNameRaw = '';
|
||||
foreach ($aFolderExplode as $sFolderExplodeItem)
|
||||
{
|
||||
$sNonExistenFolderFullNameRaw .= (0 < \strlen($sNonExistenFolderFullNameRaw))
|
||||
? $sDelimiter.$sFolderExplodeItem : $sFolderExplodeItem;
|
||||
|
||||
if (!isset($aSortedByLenImapFolders[$sNonExistenFolderFullNameRaw]))
|
||||
{
|
||||
try
|
||||
{
|
||||
$aAddedFolders[$sNonExistenFolderFullNameRaw] =
|
||||
Folder::NewNonExistenInstance($sNonExistenFolderFullNameRaw, $sDelimiter);
|
||||
}
|
||||
catch (\Throwable $oExc)
|
||||
{
|
||||
unset($oExc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aSortedByLenImapFolders = \array_merge($aSortedByLenImapFolders, $aAddedFolders);
|
||||
unset($aAddedFolders);
|
||||
|
||||
\uasort($aSortedByLenImapFolders, function ($oFolderA, $oFolderB) {
|
||||
return \strnatcmp($oFolderA->FullNameRaw(), $oFolderB->FullNameRaw());
|
||||
});
|
||||
|
||||
foreach ($aSortedByLenImapFolders as $oMailFolder)
|
||||
{
|
||||
$this->AddWithPositionSearch($oMailFolder);
|
||||
unset($oMailFolder);
|
||||
}
|
||||
|
||||
unset($aSortedByLenImapFolders);
|
||||
}
|
||||
|
||||
public function AddWithPositionSearch(Folder $oMailFolder) : bool
|
||||
{
|
||||
$oItemFolder = null;
|
||||
$bIsAdded = false;
|
||||
|
||||
foreach ($this as $oItemFolder)
|
||||
{
|
||||
if ($oMailFolder instanceof Folder &&
|
||||
0 === \strpos($oMailFolder->FullNameRaw(), $oItemFolder->FullNameRaw().$oItemFolder->Delimiter()))
|
||||
if (0 === \strpos($oMailFolder->FullNameRaw(), $oItemFolder->FullNameRaw().$oItemFolder->Delimiter()))
|
||||
{
|
||||
if ($oItemFolder->SubFolders(true)->AddWithPositionSearch($oMailFolder))
|
||||
{
|
||||
$bIsAdded = true;
|
||||
}
|
||||
|
||||
break;
|
||||
$oItemFolder->SubFolders(true)->AddWithPositionSearch($oMailFolder);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bIsAdded && $oMailFolder instanceof Folder)
|
||||
{
|
||||
$bIsAdded = true;
|
||||
$this->append($oMailFolder);
|
||||
}
|
||||
|
||||
return $bIsAdded;
|
||||
$this->append($oMailFolder);
|
||||
}
|
||||
|
||||
public function SortByCallback(callable $fCallback) : void
|
||||
|
|
@ -241,7 +150,7 @@ class FolderCollection extends \MailSo\Base\Collection
|
|||
|
||||
public function jsonSerialize()
|
||||
{
|
||||
return array_merge(parent::jsonSerialize(), array(
|
||||
return \array_merge(parent::jsonSerialize(), array(
|
||||
'Namespace' => $this->GetNamespace(),
|
||||
'FoldersHash' => $this->FoldersHash ?: '',
|
||||
'IsThreadsSupported' => $this->IsThreadsSupported,
|
||||
|
|
|
|||
|
|
@ -281,31 +281,6 @@ class MailClient
|
|||
$oBodyStructure = null;
|
||||
$oMessage = null;
|
||||
|
||||
$aBodyPeekMimeIndexes = array();
|
||||
$aSignatureMimeIndexes = array();
|
||||
|
||||
$aFetchResponse = $this->oImapClient->Fetch(array(\MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE), $iIndex, $bIndexIsUid);
|
||||
if (0 < \count($aFetchResponse) && isset($aFetchResponse[0]))
|
||||
{
|
||||
$oBodyStructure = $aFetchResponse[0]->GetFetchBodyStructure();
|
||||
if ($oBodyStructure)
|
||||
{
|
||||
foreach ($oBodyStructure->SearchHtmlOrPlainParts() as $oPart)
|
||||
{
|
||||
$aBodyPeekMimeIndexes[] = array($oPart->PartID(), $oPart->Size());
|
||||
}
|
||||
|
||||
$aSignatureParts = $oBodyStructure->SearchByContentType('application/pgp-signature');
|
||||
if (is_array($aSignatureParts) && 0 < \count($aSignatureParts))
|
||||
{
|
||||
foreach ($aSignatureParts as $oPart)
|
||||
{
|
||||
$aSignatureMimeIndexes[] = $oPart->PartID();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$aFetchItems = array(
|
||||
\MailSo\Imap\Enumerations\FetchType::INDEX,
|
||||
\MailSo\Imap\Enumerations\FetchType::UID,
|
||||
|
|
@ -315,25 +290,31 @@ class MailClient
|
|||
$this->getEnvelopeOrHeadersRequestString()
|
||||
);
|
||||
|
||||
if (0 < \count($aBodyPeekMimeIndexes))
|
||||
$aFetchResponse = $this->oImapClient->Fetch(array(\MailSo\Imap\Enumerations\FetchType::BODYSTRUCTURE), $iIndex, $bIndexIsUid);
|
||||
if (0 < \count($aFetchResponse) && isset($aFetchResponse[0]))
|
||||
{
|
||||
foreach ($aBodyPeekMimeIndexes as $aTextMimeData)
|
||||
$oBodyStructure = $aFetchResponse[0]->GetFetchBodyStructure();
|
||||
if ($oBodyStructure)
|
||||
{
|
||||
$sLine = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$aTextMimeData[0].']';
|
||||
if (0 < $iBodyTextLimit && $iBodyTextLimit < $aTextMimeData[1])
|
||||
foreach ($oBodyStructure->SearchHtmlOrPlainParts() as $oPart)
|
||||
{
|
||||
$sLine .= "<0.{$iBodyTextLimit}>";
|
||||
$sLine = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$oPart->PartID().']';
|
||||
if (0 < $iBodyTextLimit && $iBodyTextLimit < $oPart->Size())
|
||||
{
|
||||
$sLine .= "<0.{$iBodyTextLimit}>";
|
||||
}
|
||||
|
||||
$aFetchItems[] = $sLine;
|
||||
}
|
||||
|
||||
$aFetchItems[] = $sLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (0 < \count($aSignatureMimeIndexes))
|
||||
{
|
||||
foreach ($aSignatureMimeIndexes as $sTextMimeIndex)
|
||||
{
|
||||
$aFetchItems[] = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$sTextMimeIndex.']';
|
||||
$aSignatureParts = $oBodyStructure->SearchByContentType('application/pgp-signature');
|
||||
if (is_array($aSignatureParts) && 0 < \count($aSignatureParts))
|
||||
{
|
||||
foreach ($aSignatureParts as $oPart)
|
||||
{
|
||||
$aFetchItems[] = \MailSo\Imap\Enumerations\FetchType::BODY_PEEK.'['.$oPart->PartID().']';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1439,14 +1420,12 @@ class MailClient
|
|||
if (0 < \count($aFetchResponse))
|
||||
{
|
||||
$aFetchIndexArray = array();
|
||||
$oFetchResponseItem = null;
|
||||
foreach ($aFetchResponse as /* @var $oFetchResponseItem \MailSo\Imap\FetchResponse */ $oFetchResponseItem)
|
||||
{
|
||||
$aFetchIndexArray[($bIndexAsUid)
|
||||
$aFetchIndexArray[$bIndexAsUid
|
||||
? $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::UID)
|
||||
: $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::INDEX)] =& $oFetchResponseItem;
|
||||
|
||||
unset($oFetchResponseItem);
|
||||
: $oFetchResponseItem->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::INDEX)
|
||||
] = $oFetchResponseItem;
|
||||
}
|
||||
|
||||
foreach ($aRequestIndexOrUids as $iFUid)
|
||||
|
|
@ -1979,8 +1958,6 @@ class MailClient
|
|||
|
||||
public function Folders(string $sParent = '', string $sListPattern = '*', bool $bUseListSubscribeStatus = true, int $iOptimizationLimit = 0) : ?FolderCollection
|
||||
{
|
||||
$oFolderCollection = null;
|
||||
|
||||
$aImapSubscribedFoldersHelper = null;
|
||||
if ($bUseListSubscribeStatus)
|
||||
{
|
||||
|
|
@ -2000,56 +1977,102 @@ class MailClient
|
|||
}
|
||||
|
||||
$aFolders = $this->oImapClient->FolderList($sParent, $sListPattern);
|
||||
if (!$aFolders) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($aFolders)
|
||||
$aMailFoldersHelper = array();
|
||||
foreach ($aFolders as /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder)
|
||||
{
|
||||
$aMailFoldersHelper = array();
|
||||
$aMailFoldersHelper[] = new Folder($oImapFolder,
|
||||
(null === $aImapSubscribedFoldersHelper || \in_array($oImapFolder->FullNameRaw(), $aImapSubscribedFoldersHelper)) ||
|
||||
$oImapFolder->IsInbox()
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($aFolders as /* @var $oImapFolder \MailSo\Imap\Folder */ $oImapFolder)
|
||||
$iCount = \count($aMailFoldersHelper);
|
||||
$aMailFoldersHelper = $this->folderListOptimization($aMailFoldersHelper, $iOptimizationLimit);
|
||||
if (!$aMailFoldersHelper) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$oFolderCollection = new FolderCollection;
|
||||
$oFolderCollection->IsThreadsSupported = $this->IsThreadsSupported();
|
||||
$oFolderCollection->IsSortSupported = $this->oImapClient->IsSupported('SORT');
|
||||
$oFolderCollection->Optimized = $iCount !== \count($aMailFoldersHelper);
|
||||
|
||||
$aSortedByLenImapFolders = array();
|
||||
foreach ($aMailFoldersHelper as /* @var $oMailFolder Folder */ $oMailFolder)
|
||||
{
|
||||
$aSortedByLenImapFolders[$oMailFolder->FullNameRaw()] = $oMailFolder;
|
||||
}
|
||||
unset($aMailFoldersHelper);
|
||||
|
||||
$aAddedFolders = array();
|
||||
foreach ($aSortedByLenImapFolders as /* @var $oMailFolder Folder */ $oMailFolder)
|
||||
{
|
||||
$sDelimiter = $oMailFolder->Delimiter();
|
||||
$aFolderExplode = \explode($sDelimiter, $oMailFolder->FullNameRaw());
|
||||
|
||||
if (1 < \count($aFolderExplode))
|
||||
{
|
||||
$aMailFoldersHelper[] = new Folder($oImapFolder,
|
||||
(null === $aImapSubscribedFoldersHelper || \in_array($oImapFolder->FullNameRaw(), $aImapSubscribedFoldersHelper)) ||
|
||||
$oImapFolder->IsInbox()
|
||||
);
|
||||
}
|
||||
\array_pop($aFolderExplode);
|
||||
|
||||
$iCount = \count($aMailFoldersHelper);
|
||||
$aMailFoldersHelper = $this->folderListOptimization($aMailFoldersHelper, $iOptimizationLimit);
|
||||
$sNonExistenFolderFullNameRaw = '';
|
||||
foreach ($aFolderExplode as $sFolderExplodeItem)
|
||||
{
|
||||
$sNonExistenFolderFullNameRaw .= (0 < \strlen($sNonExistenFolderFullNameRaw))
|
||||
? $sDelimiter.$sFolderExplodeItem : $sFolderExplodeItem;
|
||||
|
||||
if ($aMailFoldersHelper)
|
||||
{
|
||||
$oFolderCollection = new FolderCollection;
|
||||
$oFolderCollection->InitByUnsortedMailFolderArray($aMailFoldersHelper);
|
||||
|
||||
$oFolderCollection->Optimized = $iCount !== \count($aMailFoldersHelper);
|
||||
if (!isset($aSortedByLenImapFolders[$sNonExistenFolderFullNameRaw]))
|
||||
{
|
||||
try
|
||||
{
|
||||
$aAddedFolders[$sNonExistenFolderFullNameRaw] =
|
||||
Folder::NewNonExistenInstance($sNonExistenFolderFullNameRaw, $sDelimiter);
|
||||
}
|
||||
catch (\Throwable $oExc)
|
||||
{
|
||||
unset($oExc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($oFolderCollection)
|
||||
$aSortedByLenImapFolders = \array_merge($aSortedByLenImapFolders, $aAddedFolders);
|
||||
unset($aAddedFolders);
|
||||
|
||||
\uasort($aSortedByLenImapFolders, function ($oFolderA, $oFolderB) {
|
||||
return \strnatcmp($oFolderA->FullNameRaw(), $oFolderB->FullNameRaw());
|
||||
});
|
||||
|
||||
foreach ($aSortedByLenImapFolders as $oMailFolder)
|
||||
{
|
||||
$oFolderCollection->SortByCallback(function ($oFolderA, $oFolderB) {
|
||||
$sA = \strtoupper($oFolderA->FullNameRaw());
|
||||
$sB = \strtoupper($oFolderB->FullNameRaw());
|
||||
switch (true)
|
||||
{
|
||||
case 'INBOX' === $sA:
|
||||
return -1;
|
||||
case 'INBOX' === $sB:
|
||||
return 1;
|
||||
}
|
||||
$oFolderCollection->AddWithPositionSearch($oMailFolder);
|
||||
unset($oMailFolder);
|
||||
}
|
||||
|
||||
return \strnatcasecmp($oFolderA->FullName(), $oFolderB->FullName());
|
||||
});
|
||||
unset($aSortedByLenImapFolders);
|
||||
|
||||
$oNamespace = $this->oImapClient->GetNamespace();
|
||||
if ($oNamespace)
|
||||
$oFolderCollection->SortByCallback(function ($oFolderA, $oFolderB) {
|
||||
$sA = \strtoupper($oFolderA->FullNameRaw());
|
||||
$sB = \strtoupper($oFolderB->FullNameRaw());
|
||||
switch (true)
|
||||
{
|
||||
$oFolderCollection->SetNamespace($oNamespace->GetPersonalNamespace());
|
||||
case 'INBOX' === $sA:
|
||||
return -1;
|
||||
case 'INBOX' === $sB:
|
||||
return 1;
|
||||
}
|
||||
|
||||
$oFolderCollection->IsThreadsSupported = $this->IsThreadsSupported();
|
||||
return \strnatcasecmp($oFolderA->FullName(), $oFolderB->FullName());
|
||||
});
|
||||
|
||||
$oFolderCollection->IsSortSupported = $this->oImapClient->IsSupported('SORT');
|
||||
$oNamespace = $this->oImapClient->GetNamespace();
|
||||
if ($oNamespace)
|
||||
{
|
||||
$oFolderCollection->SetNamespace($oNamespace->GetPersonalNamespace());
|
||||
}
|
||||
|
||||
return $oFolderCollection;
|
||||
|
|
|
|||
|
|
@ -31,157 +31,61 @@ class Message implements \JsonSerializable
|
|||
$iHeaderTimeStampInUTC = 0,
|
||||
$sHeaderDate = '',
|
||||
$aFlags = [],
|
||||
$aFlagsLowerCase = [];
|
||||
$aFlagsLowerCase = [],
|
||||
|
||||
/**
|
||||
* @var \MailSo\Mime\EmailCollection
|
||||
*/
|
||||
private $oFrom;
|
||||
/**
|
||||
* @var \MailSo\Mime\EmailCollection
|
||||
*/
|
||||
$oFrom = null,
|
||||
$oSender = null,
|
||||
$oReplyTo = null,
|
||||
$oDeliveredTo = null,
|
||||
$oTo = null,
|
||||
$oCc = null,
|
||||
$oBcc = null,
|
||||
|
||||
/**
|
||||
* @var \MailSo\Mime\EmailCollection
|
||||
*/
|
||||
private $oSender;
|
||||
$sInReplyTo = '',
|
||||
|
||||
/**
|
||||
* @var \MailSo\Mime\EmailCollection
|
||||
*/
|
||||
private $oReplyTo;
|
||||
$sPlain = '',
|
||||
$sHtml = '',
|
||||
|
||||
/**
|
||||
* @var \MailSo\Mime\EmailCollection
|
||||
*/
|
||||
private $oDeliveredTo;
|
||||
/**
|
||||
* @var AttachmentCollection
|
||||
*/
|
||||
$oAttachments = null,
|
||||
|
||||
/**
|
||||
* @var \MailSo\Mime\EmailCollection
|
||||
*/
|
||||
private $oTo;
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
$aDraftInfo = null,
|
||||
|
||||
/**
|
||||
* @var \MailSo\Mime\EmailCollection
|
||||
*/
|
||||
private $oCc;
|
||||
$sReferences = '',
|
||||
|
||||
/**
|
||||
* @var \MailSo\Mime\EmailCollection
|
||||
*/
|
||||
private $oBcc;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
$iSensitivity,
|
||||
$iPriority,
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sInReplyTo;
|
||||
$sDeliveryReceipt = '',
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sPlain;
|
||||
$sReadReceipt = '',
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sHtml;
|
||||
$aUnsubsribeLinks = array(),
|
||||
|
||||
/**
|
||||
* @var AttachmentCollection
|
||||
*/
|
||||
private $oAttachments;
|
||||
$aThreads = array(),
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aDraftInfo;
|
||||
$bTextPartIsTrimmed = false,
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sReferences;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iSensitivity;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $iPriority;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sDeliveryReceipt;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sReadReceipt;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aUnsubsribeLinks;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $aThreads;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bTextPartIsTrimmed;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sPgpSignature;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bPgpSigned;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $bPgpEncrypted;
|
||||
$sPgpSignature = '',
|
||||
$sPgpSignatureMicAlg = '',
|
||||
$bPgpSigned = false,
|
||||
$bPgpEncrypted = false;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->oFrom = null;
|
||||
$this->oSender = null;
|
||||
$this->oReplyTo = null;
|
||||
$this->oDeliveredTo = null;
|
||||
$this->oTo = null;
|
||||
$this->oCc = null;
|
||||
$this->oBcc = null;
|
||||
|
||||
$this->sPlain = '';
|
||||
$this->sHtml = '';
|
||||
|
||||
$this->oAttachments = null;
|
||||
$this->aDraftInfo = null;
|
||||
|
||||
$this->sInReplyTo = '';
|
||||
$this->sReferences = '';
|
||||
$this->aUnsubsribeLinks = array();
|
||||
|
||||
$this->iSensitivity = \MailSo\Mime\Enumerations\Sensitivity::NOTHING;
|
||||
$this->iPriority = \MailSo\Mime\Enumerations\MessagePriority::NORMAL;
|
||||
$this->sDeliveryReceipt = '';
|
||||
$this->sReadReceipt = '';
|
||||
|
||||
$this->aThreads = array();
|
||||
|
||||
$this->bTextPartIsTrimmed = false;
|
||||
|
||||
$this->sPgpSignature = '';
|
||||
$this->bPgpSigned = false;
|
||||
$this->bPgpEncrypted = false;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function Plain() : string
|
||||
|
|
@ -199,6 +103,11 @@ class Message implements \JsonSerializable
|
|||
return $this->sPgpSignature;
|
||||
}
|
||||
|
||||
public function PgpSignatureMicAlg() : string
|
||||
{
|
||||
return $this->sPgpSignatureMicAlg;
|
||||
}
|
||||
|
||||
public function isPgpSigned() : bool
|
||||
{
|
||||
return $this->bPgpSigned;
|
||||
|
|
@ -611,6 +520,24 @@ class Message implements \JsonSerializable
|
|||
$this->sInReplyTo = $oFetchResponse->GetFetchEnvelopeValue(8, '');
|
||||
}
|
||||
|
||||
// Content-Type: multipart/signed; micalg="pgp-sha256"; protocol="application/pgp-signature"
|
||||
if ('multipart/signed' === \strtolower($this->sContentType)
|
||||
&& 'application/pgp-signature' === \strtolower($oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, \MailSo\Mime\Enumerations\Parameter::PROTOCOL)))
|
||||
{
|
||||
$aPgpSignatureParts = $oBodyStructure ? $oBodyStructure->SearchByContentType('application/pgp-signature') : null;
|
||||
if ($this->bPgpSigned = !empty($aPgpSignatureParts)) {
|
||||
$sPgpSignatureText = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::BODY.'['.$aPgpSignatureParts[0]->PartID().']');
|
||||
if (\is_string($sPgpSignatureText) && 0 < \strlen($sPgpSignatureText) && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE')) {
|
||||
$this->sPgpSignature = \trim($sPgpSignatureText);
|
||||
$this->sPgpSignatureMicAlg = (string) $oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, 'micalg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"
|
||||
$this->bPgpEncrypted = ('multipart/encrypted' === \strtolower($this->sContentType)
|
||||
&& 'application/pgp-encrypted' === \strtolower($oHeaders->ParameterValue(\MailSo\Mime\Enumerations\Header::CONTENT_TYPE, \MailSo\Mime\Enumerations\Parameter::PROTOCOL)));
|
||||
|
||||
$aTextParts = $oBodyStructure ? $oBodyStructure->SearchHtmlOrPlainParts() : null;
|
||||
if ($aTextParts)
|
||||
{
|
||||
|
|
@ -674,39 +601,17 @@ class Message implements \JsonSerializable
|
|||
}
|
||||
|
||||
$aMatch = array();
|
||||
if (\preg_match('/-----BEGIN PGP SIGNATURE-----(.+)-----END PGP SIGNATURE-----/ism', $this->sPlain, $aMatch) && !empty($aMatch[0]))
|
||||
if (!$this->bPgpSigned && \preg_match('/-----BEGIN PGP SIGNATURE-----(.+)-----END PGP SIGNATURE-----/ism', $this->sPlain, $aMatch) && !empty($aMatch[0]))
|
||||
{
|
||||
$this->sPgpSignature = \trim($aMatch[0]);
|
||||
$this->bPgpSigned = true;
|
||||
}
|
||||
|
||||
$aMatch = array();
|
||||
if (\preg_match('/-----BEGIN PGP MESSAGE-----/ism', $this->sPlain, $aMatch) && !empty($aMatch[0]))
|
||||
{
|
||||
$this->bPgpEncrypted = true;
|
||||
}
|
||||
$this->bPgpEncrypted = !$this->bPgpEncrypted && false !== \stripos($this->sPlain, '-----BEGIN PGP MESSAGE-----');
|
||||
|
||||
unset($aHtmlParts, $aPlainParts, $aMatch);
|
||||
}
|
||||
|
||||
// if (empty($this->sPgpSignature) && 'multipart/signed' === \strtolower($this->sContentType) &&
|
||||
// 'application/pgp-signature' === \strtolower($oHeaders->ParameterValue(
|
||||
// \MailSo\Mime\Enumerations\Header::CONTENT_TYPE,
|
||||
// \MailSo\Mime\Enumerations\Parameter::PROTOCOL
|
||||
// )))
|
||||
// {
|
||||
// $aPgpSignatureParts = $oBodyStructure ? $oBodyStructure->SearchByContentType('application/pgp-signature') : null;
|
||||
// if (\is_array($aPgpSignatureParts) && 0 < \count($aPgpSignatureParts) && isset($aPgpSignatureParts[0]))
|
||||
// {
|
||||
// $sPgpSignatureText = $oFetchResponse->GetFetchValue(\MailSo\Imap\Enumerations\FetchType::BODY.'['.$aPgpSignatureParts[0]->PartID().']');
|
||||
// if (\is_string($sPgpSignatureText) && 0 < \strlen($sPgpSignatureText) && 0 < \strpos($sPgpSignatureText, 'BEGIN PGP SIGNATURE'))
|
||||
// {
|
||||
// $this->sPgpSignature = \trim($sPgpSignatureText);
|
||||
// $this->bPgpSigned = true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if ($oBodyStructure)
|
||||
{
|
||||
$aAttachmentsParts = $oBodyStructure->SearchAttachmentsParts();
|
||||
|
|
|
|||
|
|
@ -301,9 +301,9 @@ class Message
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function SetSender(EmailCollection $oEmails) : self
|
||||
public function SetSender(Email $oEmail) : self
|
||||
{
|
||||
$this->aHeadersValue[Enumerations\Header::SENDER] = $oEmails;
|
||||
$this->aHeadersValue[Enumerations\Header::SENDER] = $oEmail;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ class Actions
|
|||
use Actions\User;
|
||||
use Actions\Raw;
|
||||
use Actions\Response;
|
||||
use Actions\Localization;
|
||||
use Actions\Themes;
|
||||
|
||||
const AUTH_TFA_SIGN_ME_TOKEN_KEY = 'rltfasmauth';
|
||||
const AUTH_SIGN_ME_TOKEN_KEY = 'rlsmauth';
|
||||
const AUTH_MAILTO_TOKEN_KEY = 'rlmailtoauth';
|
||||
const AUTH_SPEC_TOKEN_KEY = 'rlspecauth';
|
||||
|
|
@ -110,11 +111,6 @@ class Actions
|
|||
*/
|
||||
private $sSpecAuthToken;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $sUpdateAuthToken;
|
||||
|
||||
/**
|
||||
* @access private
|
||||
*/
|
||||
|
|
@ -139,7 +135,6 @@ class Actions
|
|||
$this->oSuggestionsProvider = null;
|
||||
|
||||
$this->sSpecAuthToken = '';
|
||||
$this->sUpdateAuthToken = '';
|
||||
$this->bIsJson = false;
|
||||
|
||||
$oConfig = $this->Config();
|
||||
|
|
@ -155,13 +150,6 @@ class Actions
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function SetUpdateAuthToken(string $sUpdateAuthToken): self
|
||||
{
|
||||
$this->sUpdateAuthToken = $sUpdateAuthToken;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function SetIsJson(bool $bIsJson): self
|
||||
{
|
||||
$this->bIsJson = $bIsJson;
|
||||
|
|
@ -174,11 +162,6 @@ class Actions
|
|||
return $this->sSpecAuthToken;
|
||||
}
|
||||
|
||||
public function GetUpdateAuthToken(): string
|
||||
{
|
||||
return $this->sUpdateAuthToken;
|
||||
}
|
||||
|
||||
public function GetIsJson(): bool
|
||||
{
|
||||
return $this->bIsJson;
|
||||
|
|
@ -313,9 +296,9 @@ class Actions
|
|||
}
|
||||
}
|
||||
|
||||
public function ParseQueryAuthString(): string
|
||||
public function ParseQueryString(): string
|
||||
{
|
||||
$sQuery = \trim($this->Http()->GetQueryString());
|
||||
$sQuery = \trim($_SERVER['QUERY_STRING'] ?? '');
|
||||
|
||||
$iPos = \strpos($sQuery, '&');
|
||||
if (0 < $iPos) {
|
||||
|
|
@ -324,7 +307,7 @@ class Actions
|
|||
|
||||
$sQuery = \trim(\trim($sQuery), ' /');
|
||||
|
||||
$aSubQuery = $this->Http()->GetQuery('q');
|
||||
$aSubQuery = $_GET['q'] ?? null;
|
||||
if (\is_array($aSubQuery)) {
|
||||
$aSubQuery = \array_map(function ($sS) {
|
||||
return \trim(\trim($sS), ' /');
|
||||
|
|
@ -335,16 +318,33 @@ class Actions
|
|||
}
|
||||
}
|
||||
|
||||
if ('' === $this->GetSpecAuthToken()) {
|
||||
$aPaths = \explode('/', $sQuery);
|
||||
if (!empty($aPaths[0]) && !empty($aPaths[1]) && '_' === substr($aPaths[1], 0, 1)) {
|
||||
$this->SetSpecAuthToken($aPaths[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return $sQuery;
|
||||
}
|
||||
|
||||
// rlspecauth / AuthAccountHash
|
||||
public function getAuthAccountHash() : string
|
||||
{
|
||||
if ('' === $this->sSpecAuthToken && !\strlen($this->GetSpecAuthLogoutTokenWithDeletion())) {
|
||||
$sAuthAccountHash = $this->GetSpecAuthTokenCookie() ?: $this->GetSpecAuthToken();
|
||||
if (empty($sAuthAccountHash)) {
|
||||
$oAccount = $this->GetAccountFromSignMeToken();
|
||||
if ($oAccount) try
|
||||
{
|
||||
$this->CheckMailConnection($oAccount);
|
||||
$this->AuthToken($oAccount);
|
||||
$sAuthAccountHash = $this->GetSpecAuthToken();
|
||||
}
|
||||
catch (\Throwable $oException)
|
||||
{
|
||||
$oException = null;
|
||||
$this->ClearSignMeData($oAccount);
|
||||
}
|
||||
}
|
||||
$this->SetSpecAuthToken($sAuthAccountHash);
|
||||
}
|
||||
return $this->GetSpecAuthToken();
|
||||
}
|
||||
|
||||
private function compileLogParams(string $sLine, ?Model\Account $oAccount = null, bool $bUrlEncode = false, array $aAdditionalParams = array()): string
|
||||
{
|
||||
$aClear = array();
|
||||
|
|
@ -360,7 +360,7 @@ class Actions
|
|||
|
||||
if (false !== \strpos($sLine, '{imap:') || false !== \strpos($sLine, '{smtp:')) {
|
||||
if (!$oAccount) {
|
||||
$this->ParseQueryAuthString();
|
||||
$this->getAuthAccountHash();
|
||||
$oAccount = $this->getAccountFromToken(false);
|
||||
}
|
||||
|
||||
|
|
@ -414,7 +414,7 @@ class Actions
|
|||
|
||||
if (\preg_match('/\{user:(email|login|domain)\}/i', $sLine)) {
|
||||
if (!$oAccount) {
|
||||
$this->ParseQueryAuthString();
|
||||
$this->getAuthAccountHash();
|
||||
$oAccount = $this->getAccountFromToken(false);
|
||||
}
|
||||
|
||||
|
|
@ -505,14 +505,9 @@ class Actions
|
|||
}
|
||||
}
|
||||
|
||||
public function GetSpecAuthTokenWithDeletion(): string
|
||||
public function GetSpecAuthTokenCookie(): string
|
||||
{
|
||||
$sResult = Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY, '');
|
||||
if (0 < strlen($sResult)) {
|
||||
Utils::ClearCookie(self::AUTH_SPEC_TOKEN_KEY);
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
return Utils::GetCookie(self::AUTH_SPEC_TOKEN_KEY, '');
|
||||
}
|
||||
|
||||
public function GetSpecAuthLogoutTokenWithDeletion(): string
|
||||
|
|
@ -691,9 +686,9 @@ class Actions
|
|||
$oDriver = new \MailSo\Cache\Drivers\File(APP_PRIVATE_DATA . 'cache', $sKey);
|
||||
break;
|
||||
|
||||
case ('APC' === $sDriver || 'APCU' === $sDriver) &&
|
||||
case ('APCU' === $sDriver) &&
|
||||
\MailSo\Base\Utils::FunctionExistsAndEnabled(array(
|
||||
'apc_store', 'apc_fetch', 'apc_delete', 'apc_clear_cache')):
|
||||
'apcu_store', 'apcu_fetch', 'apcu_delete', 'apcu_clear_cache')):
|
||||
|
||||
$oDriver = new \MailSo\Cache\Drivers\APC($sKey);
|
||||
break;
|
||||
|
|
@ -800,7 +795,7 @@ class Actions
|
|||
);
|
||||
|
||||
$this->oLogger->Write(
|
||||
'[APC:' . (\MailSo\Base\Utils::FunctionExistsAndEnabled('apc_fetch') ? 'on' : 'off') .
|
||||
'[APCU:' . (\MailSo\Base\Utils::FunctionExistsAndEnabled('apcu_fetch') ? 'on' : 'off') .
|
||||
'][MB:' . (\MailSo\Base\Utils::FunctionExistsAndEnabled('mb_convert_encoding') ? 'on' : 'off') .
|
||||
'][PDO:' . (\class_exists('PDO') ? (\implode(',', \Pdo::getAvailableDrivers()) ?: '~') : 'off') .
|
||||
'][Streams:' . \implode(',', \stream_get_transports()) .
|
||||
|
|
@ -995,8 +990,6 @@ class Actions
|
|||
'allowHtmlEditorSourceButton' => (bool)$oConfig->Get('labs', 'allow_html_editor_source_button', false),
|
||||
'allowHtmlEditorBitiButtons' => (bool)$oConfig->Get('labs', 'allow_html_editor_biti_buttons', false),
|
||||
'allowCtrlEnterOnCompose' => (bool)$oConfig->Get('labs', 'allow_ctrl_enter_on_compose', false),
|
||||
'forgotPasswordLinkUrl' => \trim($oConfig->Get('login', 'forgot_password_link_url', '')),
|
||||
'registrationLinkUrl' => \trim($oConfig->Get('login', 'registration_link_url', '')),
|
||||
'hideSubmitButton' => (bool)$oConfig->Get('login', 'hide_submit_button', true),
|
||||
'useImapThread' => (bool)$oConfig->Get('labs', 'use_imap_thread', false),
|
||||
'useImapSubscribe' => (bool)$oConfig->Get('labs', 'use_imap_list_subscribe', true),
|
||||
|
|
@ -1015,18 +1008,14 @@ class Actions
|
|||
) : array());
|
||||
}
|
||||
|
||||
public function AppData(bool $bAdmin, string $sAuthAccountHash = ''): array
|
||||
public function AppData(bool $bAdmin): array
|
||||
{
|
||||
if (0 < \strlen($sAuthAccountHash) && \preg_match('/[^_\-\.a-zA-Z0-9]/', $sAuthAccountHash)) {
|
||||
$sAuthAccountHash = '';
|
||||
}
|
||||
|
||||
$oAccount = null;
|
||||
$oConfig = $this->Config();
|
||||
|
||||
/*
|
||||
required by Index.html and rl.js:
|
||||
NewThemeLink TemplatesLink LangLink PluginsLink AuthAccountHash
|
||||
PluginsLink
|
||||
*/
|
||||
|
||||
$value = \ini_get('upload_max_filesize');
|
||||
|
|
@ -1041,7 +1030,6 @@ class Actions
|
|||
'Auth' => false,
|
||||
'AccountHash' => '',
|
||||
'AccountSignMe' => false,
|
||||
'AuthAccountHash' => '',
|
||||
'MailToEmail' => '',
|
||||
'Email' => '',
|
||||
'DevEmail' => '',
|
||||
|
|
@ -1088,10 +1076,6 @@ class Actions
|
|||
'UserBackgroundHash' => ''
|
||||
);
|
||||
|
||||
if (0 < \strlen($sAuthAccountHash)) {
|
||||
$aResult['AuthAccountHash'] = $sAuthAccountHash;
|
||||
}
|
||||
|
||||
$oSettings = null;
|
||||
|
||||
$passfile = APP_PRIVATE_DATA.'admin_password.txt';
|
||||
|
|
@ -1104,6 +1088,9 @@ class Actions
|
|||
$oConfig->Save();
|
||||
}
|
||||
|
||||
$sLanguage = $oConfig->Get('webmail', 'language', 'en');
|
||||
$UserLanguageRaw = $this->detectUserLanguage($bAdmin);
|
||||
|
||||
if (!$bAdmin) {
|
||||
$oAccount = $this->getAccountFromToken(false);
|
||||
if ($oAccount) {
|
||||
|
|
@ -1152,7 +1139,60 @@ class Actions
|
|||
if (!empty($aResult['StartupUrl'])) {
|
||||
$aResult['StartupUrl'] = $this->compileLogParams($aResult['StartupUrl'], $oAccount, true);
|
||||
}
|
||||
} else {
|
||||
|
||||
$aResult['ParentEmail'] = $oAccount->ParentEmail();
|
||||
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
|
||||
if ($oSettingsLocal instanceof Settings) {
|
||||
$aResult['SentFolder'] = (string)$oSettingsLocal->GetConf('SentFolder', '');
|
||||
$aResult['DraftFolder'] = (string)$oSettingsLocal->GetConf('DraftFolder', '');
|
||||
$aResult['SpamFolder'] = (string)$oSettingsLocal->GetConf('SpamFolder', '');
|
||||
$aResult['TrashFolder'] = (string)$oSettingsLocal->GetConf('TrashFolder', '');
|
||||
$aResult['ArchiveFolder'] = (string)$oSettingsLocal->GetConf('ArchiveFolder', '');
|
||||
$aResult['HideUnsubscribed'] = (bool)$oSettingsLocal->GetConf('HideUnsubscribed', $aResult['HideUnsubscribed']);
|
||||
}
|
||||
|
||||
if ($this->GetCapa(false, Enumerations\Capa::SETTINGS, $oAccount)) {
|
||||
if ($oSettings instanceof Settings) {
|
||||
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) {
|
||||
$sLanguage = (string)$oSettings->GetConf('Language', $sLanguage);
|
||||
}
|
||||
|
||||
$aResult['EditorDefaultType'] = (string)$oSettings->GetConf('EditorDefaultType', $aResult['EditorDefaultType']);
|
||||
$aResult['ShowImages'] = (bool)$oSettings->GetConf('ShowImages', $aResult['ShowImages']);
|
||||
$aResult['RemoveColors'] = (bool)$oSettings->GetConf('RemoveColors', $aResult['RemoveColors']);
|
||||
$aResult['ContactsAutosave'] = (bool)$oSettings->GetConf('ContactsAutosave', $aResult['ContactsAutosave']);
|
||||
$aResult['MPP'] = (int)$oSettings->GetConf('MPP', $aResult['MPP']);
|
||||
$aResult['SoundNotification'] = (bool)$oSettings->GetConf('SoundNotification', $aResult['SoundNotification']);
|
||||
$aResult['DesktopNotifications'] = (bool)$oSettings->GetConf('DesktopNotifications', $aResult['DesktopNotifications']);
|
||||
$aResult['UseCheckboxesInList'] = (bool)$oSettings->GetConf('UseCheckboxesInList', $aResult['UseCheckboxesInList']);
|
||||
$aResult['AllowDraftAutosave'] = (bool)$oSettings->GetConf('AllowDraftAutosave', $aResult['AllowDraftAutosave']);
|
||||
$aResult['AutoLogout'] = (int)$oSettings->GetConf('AutoLogout', $aResult['AutoLogout']);
|
||||
$aResult['Layout'] = (int)$oSettings->GetConf('Layout', $aResult['Layout']);
|
||||
|
||||
if (!$this->GetCapa(false, Enumerations\Capa::AUTOLOGOUT, $oAccount)) {
|
||||
$aResult['AutoLogout'] = 0;
|
||||
}
|
||||
|
||||
if ($this->GetCapa(false, Enumerations\Capa::USER_BACKGROUND, $oAccount)) {
|
||||
$aResult['UserBackgroundName'] = (string)$oSettings->GetConf('UserBackgroundName', $aResult['UserBackgroundName']);
|
||||
$aResult['UserBackgroundHash'] = (string)$oSettings->GetConf('UserBackgroundHash', $aResult['UserBackgroundHash']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($oSettingsLocal instanceof Settings) {
|
||||
$aResult['UseThreads'] = (bool)$oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']);
|
||||
$aResult['ReplySameFolder'] = (bool)$oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($oConfig->Get('login', 'allow_languages_on_login', true)
|
||||
&& $oConfig->Get('login', 'determine_user_language', true)) {
|
||||
$sLanguage = $this->ValidateLanguage($UserLanguageRaw, $sLanguage, false);
|
||||
}
|
||||
|
||||
$aResult['DevEmail'] = $oConfig->Get('labs', 'dev_email', '');
|
||||
$aResult['DevPassword'] = $oConfig->Get('labs', 'dev_password', '');
|
||||
|
||||
|
|
@ -1198,98 +1238,22 @@ class Actions
|
|||
|
||||
$aResult['ProjectHash'] = \md5($aResult['AccountHash'] . APP_VERSION . $this->Plugins()->Hash());
|
||||
|
||||
$sLanguage = $oConfig->Get('webmail', 'language', 'en');
|
||||
$sLanguageAdmin = $oConfig->Get('webmail', 'language_admin', 'en');
|
||||
$sTheme = $oConfig->Get('webmail', 'theme', 'Default');
|
||||
|
||||
if (!$bAdmin && $oAccount) {
|
||||
$aResult['ParentEmail'] = $oAccount->ParentEmail();
|
||||
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
|
||||
if ($oSettingsLocal instanceof Settings) {
|
||||
$aResult['SentFolder'] = (string)$oSettingsLocal->GetConf('SentFolder', '');
|
||||
$aResult['DraftFolder'] = (string)$oSettingsLocal->GetConf('DraftFolder', '');
|
||||
$aResult['SpamFolder'] = (string)$oSettingsLocal->GetConf('SpamFolder', '');
|
||||
$aResult['TrashFolder'] = (string)$oSettingsLocal->GetConf('TrashFolder', '');
|
||||
$aResult['ArchiveFolder'] = (string)$oSettingsLocal->GetConf('ArchiveFolder', '');
|
||||
$aResult['HideUnsubscribed'] = (bool)$oSettingsLocal->GetConf('HideUnsubscribed', $aResult['HideUnsubscribed']);
|
||||
}
|
||||
|
||||
if ($this->GetCapa(false, Enumerations\Capa::SETTINGS, $oAccount)) {
|
||||
if ($oSettings instanceof Settings) {
|
||||
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)) {
|
||||
$sLanguage = (string)$oSettings->GetConf('Language', $sLanguage);
|
||||
}
|
||||
|
||||
$aResult['EditorDefaultType'] = (string)$oSettings->GetConf('EditorDefaultType', $aResult['EditorDefaultType']);
|
||||
$aResult['ShowImages'] = (bool)$oSettings->GetConf('ShowImages', $aResult['ShowImages']);
|
||||
$aResult['RemoveColors'] = (bool)$oSettings->GetConf('RemoveColors', $aResult['RemoveColors']);
|
||||
$aResult['ContactsAutosave'] = (bool)$oSettings->GetConf('ContactsAutosave', $aResult['ContactsAutosave']);
|
||||
$aResult['MPP'] = (int)$oSettings->GetConf('MPP', $aResult['MPP']);
|
||||
$aResult['SoundNotification'] = (bool)$oSettings->GetConf('SoundNotification', $aResult['SoundNotification']);
|
||||
$aResult['DesktopNotifications'] = (bool)$oSettings->GetConf('DesktopNotifications', $aResult['DesktopNotifications']);
|
||||
$aResult['UseCheckboxesInList'] = (bool)$oSettings->GetConf('UseCheckboxesInList', $aResult['UseCheckboxesInList']);
|
||||
$aResult['AllowDraftAutosave'] = (bool)$oSettings->GetConf('AllowDraftAutosave', $aResult['AllowDraftAutosave']);
|
||||
$aResult['AutoLogout'] = (int)$oSettings->GetConf('AutoLogout', $aResult['AutoLogout']);
|
||||
$aResult['Layout'] = (int)$oSettings->GetConf('Layout', $aResult['Layout']);
|
||||
|
||||
if (!$this->GetCapa(false, Enumerations\Capa::AUTOLOGOUT, $oAccount)) {
|
||||
$aResult['AutoLogout'] = 0;
|
||||
}
|
||||
|
||||
if ($this->GetCapa(false, Enumerations\Capa::USER_BACKGROUND, $oAccount)) {
|
||||
$aResult['UserBackgroundName'] = (string)$oSettings->GetConf('UserBackgroundName', $aResult['UserBackgroundName']);
|
||||
$aResult['UserBackgroundHash'] = (string)$oSettings->GetConf('UserBackgroundHash', $aResult['UserBackgroundHash']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($oSettingsLocal instanceof Settings) {
|
||||
$aResult['UseThreads'] = (bool)$oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']);
|
||||
$aResult['ReplySameFolder'] = (bool)$oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']);
|
||||
|
||||
if ($this->GetCapa(false, Enumerations\Capa::THEMES, $oAccount)) {
|
||||
$sTheme = (string)$oSettingsLocal->GetConf('Theme', $sTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$aResult['Auth']) {
|
||||
if (!$bAdmin) {
|
||||
if ($oConfig->Get('login', 'allow_languages_on_login', true) &&
|
||||
$oConfig->Get('login', 'determine_user_language', true)) {
|
||||
$sLanguage = $this->ValidateLanguage(
|
||||
$this->detectUserLanguage($bAdmin), $sLanguage, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sTheme = $this->ValidateTheme($sTheme);
|
||||
$sStaticCache = $this->StaticCache();
|
||||
|
||||
$aResult['Theme'] = $sTheme;
|
||||
$aResult['NewThemeLink'] = $this->ThemeLink($sTheme, $bAdmin);
|
||||
$aResult['Theme'] = $this->GetTheme($bAdmin);
|
||||
|
||||
$aResult['Language'] = $this->ValidateLanguage($sLanguage, '', false);
|
||||
$aResult['LanguageAdmin'] = $this->ValidateLanguage($sLanguageAdmin, '', true);
|
||||
|
||||
$aResult['UserLanguageRaw'] = $this->detectUserLanguage($bAdmin);
|
||||
|
||||
$aResult['UserLanguage'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], '', false, true);
|
||||
$aResult['UserLanguageAdmin'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], '', true, true);
|
||||
$aResult['UserLanguage'] = $this->ValidateLanguage($UserLanguageRaw, '', false, true);
|
||||
if ($bAdmin) {
|
||||
$aResult['LanguageAdmin'] = $this->ValidateLanguage($oConfig->Get('webmail', 'language_admin', 'en'), '', true);
|
||||
$aResult['UserLanguageAdmin'] = $this->ValidateLanguage($UserLanguageRaw, '', true, true);
|
||||
}
|
||||
|
||||
$aResult['PluginsLink'] = '';
|
||||
if (0 < $this->Plugins()->Count() && $this->Plugins()->HaveJs($bAdmin)) {
|
||||
$aResult['PluginsLink'] = './?/Plugins/0/' . ($bAdmin ? 'Admin' : 'User') . '/' . $sStaticCache . '/';
|
||||
}
|
||||
|
||||
$aResult['LangLink'] = './?/Lang/0/' . ($bAdmin ? 'Admin' : 'App') . '/' .
|
||||
($bAdmin ? $aResult['LanguageAdmin'] : $aResult['Language']) . '/' . $sStaticCache . '/';
|
||||
|
||||
// $aResult['TemplatesLink'] = './?/Templates/0/'.($bAdmin ? 'Admin' : 'App').'/'.$sStaticCache.'/';
|
||||
$aResult['TemplatesLink'] = './?/Templates/0/' . ($bAdmin ? 'Admin' : 'App') . '/' . $sStaticCache . '/';
|
||||
|
||||
$bAppJsDebug = !!$this->Config()->Get('labs', 'use_app_debug_js', false);
|
||||
|
||||
$aResult['StaticLibJsLink'] = $this->StaticPath('js/' . ($bAppJsDebug ? '' : 'min/') .
|
||||
|
|
@ -1311,52 +1275,19 @@ class Actions
|
|||
return $aResult;
|
||||
}
|
||||
|
||||
private function getUserLanguagesFromHeader(): array
|
||||
protected function requestSleep(int $iDelay = 1): void
|
||||
{
|
||||
$aResult = $aList = array();
|
||||
$sAcceptLang = \strtolower($this->Http()->GetServer('HTTP_ACCEPT_LANGUAGE', 'en'));
|
||||
if (!empty($sAcceptLang) && \preg_match_all('/([a-z]{1,8}(?:-[a-z]{1,8})?)(?:;q=([0-9.]+))?/', $sAcceptLang, $aList)) {
|
||||
$aResult = \array_combine($aList[1], $aList[2]);
|
||||
foreach ($aResult as $n => $v) {
|
||||
$aResult[$n] = $v ? $v : 1;
|
||||
}
|
||||
|
||||
\arsort($aResult, SORT_NUMERIC);
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
public function detectUserLanguage(bool $bAdmin = false): string
|
||||
{
|
||||
$sResult = '';
|
||||
$aLangs = $this->getUserLanguagesFromHeader();
|
||||
|
||||
foreach (\array_keys($aLangs) as $sLang) {
|
||||
$sLang = $this->ValidateLanguage($sLang, '', $bAdmin, true);
|
||||
if (!empty($sLang)) {
|
||||
$sResult = $sLang;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
private function requestSleep(int $iWait = 1, int $iDelay = 1): void
|
||||
{
|
||||
if (0 < $iDelay && 0 < $iWait) {
|
||||
if ($iWait > \time() - $_SERVER['REQUEST_TIME_FLOAT']) {
|
||||
\sleep($iDelay);
|
||||
}
|
||||
$time = \microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
|
||||
if ($iDelay > $time) {
|
||||
\usleep(($iDelay - $time) * 1000000);
|
||||
}
|
||||
}
|
||||
|
||||
private function loginErrorDelay(): void
|
||||
protected function loginErrorDelay(): void
|
||||
{
|
||||
$iDelay = (int)$this->Config()->Get('labs', 'login_fault_delay', 0);
|
||||
if (0 < $iDelay) {
|
||||
$this->requestSleep(1, $iDelay);
|
||||
$this->requestSleep($iDelay);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2073,13 +2004,13 @@ class Actions
|
|||
if (!empty($sKey) && ($bForce || ($this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'http', true)))) {
|
||||
$iExpires = $this->Config()->Get('cache', 'http_expires', 3600);
|
||||
if (0 < $iExpires) {
|
||||
$this->oHttp->ServerUseCache($this->etag($sKey), 1382478804, \time() + $iExpires);
|
||||
$this->Http()->ServerUseCache($this->etag($sKey), 1382478804, \time() + $iExpires);
|
||||
$bResult = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bResult) {
|
||||
$this->oHttp->ServerNoCache();
|
||||
$this->Http()->ServerNoCache();
|
||||
}
|
||||
|
||||
return $bResult;
|
||||
|
|
@ -2090,7 +2021,7 @@ class Actions
|
|||
if (!empty($sKey) && ($bForce || $this->Config()->Get('cache', 'enable', true) && $this->Config()->Get('cache', 'http', true))) {
|
||||
$sIfNoneMatch = $this->Http()->GetHeader('If-None-Match', '');
|
||||
if ($this->etag($sKey) === $sIfNoneMatch) {
|
||||
$this->Http()->StatusHeader(304);
|
||||
\MailSo\Base\Http::StatusHeader(304);
|
||||
$this->cacheByKey($sKey);
|
||||
exit(0);
|
||||
}
|
||||
|
|
@ -2132,123 +2063,11 @@ class Actions
|
|||
return $sCache;
|
||||
}
|
||||
|
||||
public function ThemeLink(string $sTheme, bool $bAdmin): string
|
||||
{
|
||||
return './?/Css/0/' . ($bAdmin ? 'Admin' : 'User') . '/-/' . $sTheme . '/-/' . $this->StaticCache() . '/Hash/-/';
|
||||
}
|
||||
|
||||
public function ValidateTheme(string $sTheme): string
|
||||
{
|
||||
return \in_array($sTheme, $this->GetThemes()) ?
|
||||
$sTheme : $this->Config()->Get('themes', 'default', 'Default');
|
||||
}
|
||||
|
||||
public function ValidateLanguage(string $sLanguage, string $sDefault = '', bool $bAdmin = false, bool $bAllowEmptyResult = false): string
|
||||
{
|
||||
$aLang = \SnappyMail\L10n::getLanguages($bAdmin);
|
||||
|
||||
$aHelper = array(
|
||||
'ar' => 'ar-SA',
|
||||
'cs' => 'cs-CZ',
|
||||
'no' => 'nb-NO',
|
||||
'ua' => 'uk-UA',
|
||||
'cn' => 'zh-CN',
|
||||
'zh' => 'zh-CN',
|
||||
'tw' => 'zh-TW',
|
||||
'fa' => 'fa-IR'
|
||||
);
|
||||
|
||||
$sLanguage = isset($aHelper[$sLanguage]) ? $aHelper[$sLanguage] : \strtr($sLanguage, '_', '-');
|
||||
$sDefault = isset($aHelper[$sDefault]) ? $aHelper[$sDefault] : \strtr($sDefault, '_', '-');
|
||||
|
||||
if (\in_array($sLanguage, $aLang)) {
|
||||
return $sLanguage;
|
||||
}
|
||||
|
||||
$sLangCountry = \preg_replace_callback('/-([a-zA-Z]{2})$/', function ($aData) {
|
||||
return \strtoupper($aData[0]);
|
||||
}, $sLanguage);
|
||||
if (\in_array($sLangCountry, $aLang)) {
|
||||
return $sLangCountry;
|
||||
}
|
||||
|
||||
if (\in_array($sDefault, $aLang)) {
|
||||
return $sDefault;
|
||||
}
|
||||
|
||||
if ($bAllowEmptyResult) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$sResult = $this->Config()->Get('webmail', $bAdmin ? 'language_admin' : 'language', 'en');
|
||||
return \in_array($sResult, $aLang) ? $sResult : 'en';
|
||||
}
|
||||
|
||||
public function ValidateContactPdoType(string $sType): string
|
||||
{
|
||||
return \in_array($sType, \RainLoop\Common\PdoAbstract::getAvailableDrivers()) ? $sType : 'sqlite';
|
||||
}
|
||||
|
||||
/**
|
||||
* @staticvar array $aCache
|
||||
*/
|
||||
public function GetThemes(): array
|
||||
{
|
||||
static $aCache = array();
|
||||
if ($aCache) {
|
||||
return $aCache;
|
||||
}
|
||||
|
||||
$bClear = false;
|
||||
$bDefault = false;
|
||||
$aCache = array();
|
||||
$sDir = APP_VERSION_ROOT_PATH . 'themes';
|
||||
if (\is_dir($sDir)) {
|
||||
$rDirH = \opendir($sDir);
|
||||
if ($rDirH) {
|
||||
while (($sFile = \readdir($rDirH)) !== false) {
|
||||
if ('.' !== $sFile[0] && \is_dir($sDir . '/' . $sFile) && \file_exists($sDir . '/' . $sFile . '/styles.less')) {
|
||||
if ('Default' === $sFile) {
|
||||
$bDefault = true;
|
||||
} else if ('Clear' === $sFile) {
|
||||
$bClear = true;
|
||||
} else {
|
||||
$aCache[] = $sFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir($rDirH);
|
||||
}
|
||||
}
|
||||
|
||||
$sDir = APP_INDEX_ROOT_PATH . 'themes'; // custom user themes
|
||||
if (\is_dir($sDir)) {
|
||||
$rDirH = \opendir($sDir);
|
||||
if ($rDirH) {
|
||||
while (($sFile = \readdir($rDirH)) !== false) {
|
||||
if ('.' !== $sFile[0] && \is_dir($sDir . '/' . $sFile) && \file_exists($sDir . '/' . $sFile . '/styles.less')) {
|
||||
$aCache[] = $sFile . '@custom';
|
||||
}
|
||||
}
|
||||
|
||||
\closedir($rDirH);
|
||||
}
|
||||
}
|
||||
|
||||
$aCache = \array_unique($aCache);
|
||||
\sort($aCache);
|
||||
|
||||
if ($bDefault) {
|
||||
\array_unshift($aCache, 'Default');
|
||||
}
|
||||
|
||||
if ($bClear) {
|
||||
\array_push($aCache, 'Clear');
|
||||
}
|
||||
|
||||
return $aCache;
|
||||
}
|
||||
|
||||
public function ProcessTemplate(string $sName, string $sHtml): string
|
||||
{
|
||||
$sHtml = \preg_replace('/<script/i', '<x-script', $sHtml);
|
||||
|
|
@ -2296,55 +2115,6 @@ class Actions
|
|||
\header('Location: ' . $sUrl);
|
||||
}
|
||||
|
||||
public function GetLanguageAndTheme(bool $bAdmin = false): array
|
||||
{
|
||||
$sTheme = $this->Config()->Get('webmail', 'theme', 'Default');
|
||||
|
||||
if ($bAdmin) {
|
||||
$sLanguage = $this->Config()->Get('webmail', 'language_admin', 'en');
|
||||
} else {
|
||||
$oAccount = $this->GetAccount();
|
||||
|
||||
$sLanguage = $this->Config()->Get('webmail', 'language', 'en');
|
||||
|
||||
if ($oAccount) {
|
||||
$oSettings = $this->SettingsProvider()->Load($oAccount);
|
||||
if ($oSettings instanceof Settings) {
|
||||
$sLanguage = $oSettings->GetConf('Language', $sLanguage);
|
||||
}
|
||||
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
if ($oSettingsLocal instanceof Settings) {
|
||||
$sTheme = $oSettingsLocal->GetConf('Theme', $sTheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sLanguage = $this->ValidateLanguage($sLanguage, '', $bAdmin);
|
||||
$sTheme = $this->ValidateTheme($sTheme);
|
||||
|
||||
return array($sLanguage, $sTheme);
|
||||
}
|
||||
|
||||
public function StaticI18N(string $sKey): string
|
||||
{
|
||||
static $sLang = null;
|
||||
static $aLang = null;
|
||||
|
||||
if (null === $sLang) {
|
||||
$aList = $this->GetLanguageAndTheme();
|
||||
$sLang = $aList[0];
|
||||
}
|
||||
|
||||
if (null === $aLang) {
|
||||
$sLang = $this->ValidateLanguage($sLang, 'en');
|
||||
$aLang = \SnappyMail\L10n::load($sLang, 'static');
|
||||
$this->Plugins()->ReadLang($sLang, $aLang);
|
||||
}
|
||||
|
||||
return $aLang[$aKey] ?? $sKey;
|
||||
}
|
||||
|
||||
public function StaticPath(string $sPath): string
|
||||
{
|
||||
return Utils::WebStaticPath() . $sPath;
|
||||
|
|
|
|||
|
|
@ -30,14 +30,6 @@ trait Folders
|
|||
{
|
||||
$this->Plugins()->RunHook('filter.folders-post', array($oAccount, $oFolderCollection));
|
||||
|
||||
$aFolders = $oFolderCollection->getArrayCopy();
|
||||
foreach ($aFolders as $i => $oFolder) {
|
||||
if (!$oFolder->IsSelectable()) {
|
||||
unset($aFolders[$i]);
|
||||
}
|
||||
}
|
||||
$oFolderCollection->exchangeArray(\array_values($aFolders));
|
||||
|
||||
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
|
||||
|
||||
$aSystemFolders = array();
|
||||
|
|
@ -351,7 +343,7 @@ trait Folders
|
|||
$aFlagsFilteredUids = array();
|
||||
if (0 < strlen($sFlagsUids))
|
||||
{
|
||||
$aFlagsUids = explode(',', $sFlagsUids);
|
||||
$aFlagsUids = \explode(',', $sFlagsUids);
|
||||
$aFlagsFilteredUids = \array_filter(\array_map('intval', $aFlagsUids));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
|
||||
namespace RainLoop\Actions;
|
||||
|
||||
trait Localization
|
||||
{
|
||||
public function GetLanguage(bool $bAdmin = false): string
|
||||
{
|
||||
$oConfig = $this->Config();
|
||||
if ($bAdmin) {
|
||||
$sLanguage = $oConfig->Get('webmail', 'language_admin', 'en');
|
||||
} else {
|
||||
$sLanguage = $oConfig->Get('webmail', 'language', 'en');
|
||||
if ($oAccount = $this->getAccountFromToken(false)) {
|
||||
if ($oConfig->Get('webmail', 'allow_languages_on_settings', true)
|
||||
&& $this->GetCapa(false, \RainLoop\Enumerations\Capa::SETTINGS, $oAccount)
|
||||
&& ($oSettings = $this->SettingsProvider()->Load($oAccount))) {
|
||||
$sLanguage = $oSettings->GetConf('Language', $sLanguage);
|
||||
}
|
||||
} else if ($oConfig->Get('login', 'allow_languages_on_login', true) && $oConfig->Get('login', 'determine_user_language', true)) {
|
||||
$sLanguage = $this->ValidateLanguage($this->detectUserLanguage($bAdmin), $sLanguage, false);
|
||||
}
|
||||
}
|
||||
return $this->ValidateLanguage($sLanguage, '', $bAdmin) ?: 'en';
|
||||
}
|
||||
|
||||
public function ValidateLanguage(string $sLanguage, string $sDefault = '', bool $bAdmin = false, bool $bAllowEmptyResult = false): string
|
||||
{
|
||||
$aLang = \SnappyMail\L10n::getLanguages($bAdmin);
|
||||
|
||||
$aHelper = array(
|
||||
'ar' => 'ar-SA',
|
||||
'cs' => 'cs-CZ',
|
||||
'no' => 'nb-NO',
|
||||
'ua' => 'uk-UA',
|
||||
'cn' => 'zh-CN',
|
||||
'zh' => 'zh-CN',
|
||||
'tw' => 'zh-TW',
|
||||
'fa' => 'fa-IR'
|
||||
);
|
||||
|
||||
$sLanguage = isset($aHelper[$sLanguage]) ? $aHelper[$sLanguage] : \strtr($sLanguage, '_', '-');
|
||||
$sDefault = isset($aHelper[$sDefault]) ? $aHelper[$sDefault] : \strtr($sDefault, '_', '-');
|
||||
|
||||
if (\in_array($sLanguage, $aLang)) {
|
||||
return $sLanguage;
|
||||
}
|
||||
|
||||
$sLanguage = \preg_replace('/^([a-zA-Z]{2})$/', '\1-\1', $sLanguage);
|
||||
|
||||
$sLangCountry = \preg_replace_callback('/-([a-zA-Z]{2})$/', function ($aData) {
|
||||
return \strtoupper($aData[0]);
|
||||
}, $sLanguage);
|
||||
if (\in_array($sLangCountry, $aLang)) {
|
||||
return $sLangCountry;
|
||||
}
|
||||
|
||||
if (\in_array($sDefault, $aLang)) {
|
||||
return $sDefault;
|
||||
}
|
||||
|
||||
if ($bAllowEmptyResult) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$sResult = $this->Config()->Get('webmail', $bAdmin ? 'language_admin' : 'language', 'en');
|
||||
return \in_array($sResult, $aLang) ? $sResult : 'en';
|
||||
}
|
||||
|
||||
private function getUserLanguagesFromHeader(): array
|
||||
{
|
||||
$aResult = $aList = array();
|
||||
$sAcceptLang = \strtolower($this->Http()->GetServer('HTTP_ACCEPT_LANGUAGE', 'en'));
|
||||
if (!empty($sAcceptLang) && \preg_match_all('/([a-z]{1,8}(?:-[a-z]{1,8})?)(?:;q=([0-9.]+))?/', $sAcceptLang, $aList)) {
|
||||
$aResult = \array_combine($aList[1], $aList[2]);
|
||||
foreach ($aResult as $n => $v) {
|
||||
$aResult[$n] = $v ? $v : 1;
|
||||
}
|
||||
|
||||
\arsort($aResult, SORT_NUMERIC);
|
||||
}
|
||||
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
public function detectUserLanguage(bool $bAdmin): string
|
||||
{
|
||||
$sResult = '';
|
||||
$aLangs = $this->getUserLanguagesFromHeader();
|
||||
|
||||
foreach (\array_keys($aLangs) as $sLang) {
|
||||
$sLang = $this->ValidateLanguage($sLang, '', $bAdmin, true);
|
||||
if (!empty($sLang)) {
|
||||
$sResult = $sLang;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $sResult;
|
||||
}
|
||||
|
||||
public function StaticI18N(string $sKey): string
|
||||
{
|
||||
static $sLang = null;
|
||||
static $aLang = null;
|
||||
|
||||
if (null === $sLang) {
|
||||
$sLang = $this->GetLanguage();
|
||||
}
|
||||
|
||||
if (null === $aLang) {
|
||||
$sLang = $this->ValidateLanguage($sLang, 'en');
|
||||
$aLang = \SnappyMail\L10n::load($sLang, 'static');
|
||||
$this->Plugins()->ReadLang($sLang, $aLang);
|
||||
}
|
||||
|
||||
return $aLang[$aKey] ?? $sKey;
|
||||
}
|
||||
|
||||
public function compileLanguage(string $sLanguage, bool $bAdmin = false) : string
|
||||
{
|
||||
$sLanguage = \strtr($sLanguage, '_', '-');
|
||||
|
||||
$aResultLang = \json_decode(\file_get_contents(APP_VERSION_ROOT_PATH.'app/localization/langs.json'), true);
|
||||
$langs = \array_flip(\SnappyMail\L10n::getLanguages($bAdmin));
|
||||
$aResultLang['LANGS_NAMES'] = \array_intersect_key($aResultLang['LANGS_NAMES'], $langs);
|
||||
$aResultLang['LANGS_NAMES_EN'] = \array_intersect_key($aResultLang['LANGS_NAMES_EN'], $langs);
|
||||
|
||||
$aResultLang = \array_replace_recursive(
|
||||
$aResultLang,
|
||||
\SnappyMail\L10n::load($sLanguage, ($bAdmin ? 'admin' : 'user'))
|
||||
);
|
||||
|
||||
$this->Plugins()->ReadLang($sLanguage, $aResultLang);
|
||||
|
||||
$sResult = \json_encode($aResultLang, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK);
|
||||
|
||||
$sTimeFormat = '';
|
||||
$options = [$sLanguage, \substr($sLanguage, 0, 2), 'en'];
|
||||
foreach ($options as $lang) {
|
||||
$sFileName = APP_VERSION_ROOT_PATH.'app/localization/'.$lang.'/relativetimeformat.js';
|
||||
if (\is_file($sFileName)) {
|
||||
$sTimeFormat = \preg_replace('/^\\s+/', '', \file_get_contents($sFileName));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return "document.documentElement.lang = '{$sLanguage}';\nrl.I18N={$sResult};\nrl.relativeTime = {$sTimeFormat};";
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue