mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Merge branch 'the-djmaze:master' into master
This commit is contained in:
commit
00a507ff97
246 changed files with 4471 additions and 9935 deletions
|
|
@ -316,5 +316,5 @@ dev_email = ""
|
||||||
dev_password = ""
|
dev_password = ""
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
current = "2.26.1"
|
current = "2.26.4"
|
||||||
saved = "Sun, 18 Dec 2022 22:10:48 +0000"
|
saved = "Sun, 18 Dec 2022 22:10:48 +0000"
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@
|
||||||
# Header set Strict-Transport-Security "max-age=31536000"
|
# Header set Strict-Transport-Security "max-age=31536000"
|
||||||
Header set imagetoolbar "no"
|
Header set imagetoolbar "no"
|
||||||
# Header set X-Content-Type-Options "nosniff"
|
# Header set X-Content-Type-Options "nosniff"
|
||||||
# Header set X-Frame-Options "DENY"
|
|
||||||
# Header set X-XSS-Protection "1; mode=block"
|
# Header set X-XSS-Protection "1; mode=block"
|
||||||
Header set Service-Worker-Allowed "/"
|
Header set Service-Worker-Allowed "/"
|
||||||
|
|
||||||
|
|
|
||||||
101
CHANGELOG.md
101
CHANGELOG.md
|
|
@ -1,3 +1,104 @@
|
||||||
|
## 2.26.4 – 2023-02-24
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Add CSP frame-ancestors for
|
||||||
|
[#537](https://github.com/the-djmaze/snappymail/pull/537)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Reduce/simplify CSS footprint
|
||||||
|
- Use the System/Browser font by default by @HeySora
|
||||||
|
[#988](https://github.com/the-djmaze/snappymail/pull/988)
|
||||||
|
- Make layout fully responsive using matchMedia('(max-width: 799px)')
|
||||||
|
- Move brotli and gzip compress option to application.ini
|
||||||
|
- After page refreshes in background whilst editing a draft, the space bar stops working
|
||||||
|
[#860](https://github.com/the-djmaze/snappymail/pull/860)
|
||||||
|
- Updated Portuguese by @ner00
|
||||||
|
[#984](https://github.com/the-djmaze/snappymail/pull/984)
|
||||||
|
- Updated French by @hguilbert
|
||||||
|
[#985](https://github.com/the-djmaze/snappymail/pull/985)
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- unset border-box for message body
|
||||||
|
[#990](https://github.com/the-djmaze/snappymail/pull/990)
|
||||||
|
- Unread email count badge shows -1
|
||||||
|
[#989](https://github.com/the-djmaze/snappymail/pull/989)
|
||||||
|
- unicode mailto: addresses not decoded
|
||||||
|
- Unicode email/url address matching
|
||||||
|
[#955](https://github.com/the-djmaze/snappymail/pull/955)
|
||||||
|
- Invalid Token if run in IFrame
|
||||||
|
[#537](https://github.com/the-djmaze/snappymail/pull/537)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- rl.initData
|
||||||
|
|
||||||
|
|
||||||
|
## 2.26.3 – 2023-02-21
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Translate "Are you sure you want to exit?"
|
||||||
|
- stderr as Logger
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Moved rl.fetch and rl.fetchJSON to boot.js so that AppData can be fetched as JSON
|
||||||
|
- Many AppData properties to JavaScript camelCase
|
||||||
|
- Cleanup Identity handling
|
||||||
|
- Merge GnuPG and OpenPGP.js passphrases
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Notification enum conflicts with window.Notification
|
||||||
|
- language selector didn't show current language in green
|
||||||
|
- Threads indicator got lost with new message cache handling
|
||||||
|
- messagesBodiesDom never cached previous messages
|
||||||
|
- Nextcloud File picker doesn't insert multiple links
|
||||||
|
[#981](https://github.com/the-djmaze/snappymail/pull/981)
|
||||||
|
- Call to protected method RainLoop\Enumerations\UploadError::getUserMessage()
|
||||||
|
[#982](https://github.com/the-djmaze/snappymail/pull/982)
|
||||||
|
- Issue with the top logo and text of a certain e-mail
|
||||||
|
[#953](https://github.com/the-djmaze/snappymail/pull/953)
|
||||||
|
- Facebook logo missing from emails sent out by it
|
||||||
|
[#954](https://github.com/the-djmaze/snappymail/pull/954)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Unused Squire code
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 2.26.2 – 2023-02-17
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Remove CSS white-space from messages to prevent annoying side scrolling
|
||||||
|
- Show error when trying to send empty message
|
||||||
|
[#974](https://github.com/the-djmaze/snappymail/pull/974)
|
||||||
|
- max_sys_loadavg as setting
|
||||||
|
[#971](https://github.com/the-djmaze/snappymail/pull/971)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Speedup and improved cleanHtml() and cleanCSS() handling
|
||||||
|
- Better handling of upload .eml files to a mailbox
|
||||||
|
- Rename Folder Hash to ETag as it is the more obvious name
|
||||||
|
- Moved AllowDraftAutosave option from Settings -> Security to Settings -> General -> Compose
|
||||||
|
- SmtpClient->Connect() use $oSettings->Ehlo
|
||||||
|
- Improved handling of `<style>` elements in messages
|
||||||
|
- French updated by @hguilbert
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Remove SVG elements properly from messages
|
||||||
|
[#972](https://github.com/the-djmaze/snappymail/pull/972)
|
||||||
|
- Sending large HTML messages slow/fail
|
||||||
|
[#962](https://github.com/the-djmaze/snappymail/pull/962)
|
||||||
|
- Nextcloud login failed
|
||||||
|
[#969](https://github.com/the-djmaze/snappymail/pull/969)
|
||||||
|
- DoMessageList() hash check was incorrect
|
||||||
|
- "Move to folder" button overlays folder sidebar on mobile
|
||||||
|
[#961](https://github.com/the-djmaze/snappymail/pull/961)
|
||||||
|
- Spanish translation SPAM and NOT SPAM strings are reversed
|
||||||
|
[#964](https://github.com/the-djmaze/snappymail/pull/964)
|
||||||
|
- Can't open Thunderbird PGP keys from decrypted message
|
||||||
|
[#958](https://github.com/the-djmaze/snappymail/pull/958)
|
||||||
|
- Can't close Sieve dialog
|
||||||
|
[#960](https://github.com/the-djmaze/snappymail/pull/960)
|
||||||
|
|
||||||
|
|
||||||
## 2.26.1 – 2023-02-14
|
## 2.26.1 – 2023-02-14
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
|
||||||
40
README.md
40
README.md
|
|
@ -141,28 +141,28 @@ RainLoop 1.17 vs SnappyMail
|
||||||
|
|
||||||
|js/* |RainLoop |Snappy |
|
|js/* |RainLoop |Snappy |
|
||||||
|--------------- |--------: |--------: |
|
|--------------- |--------: |--------: |
|
||||||
|admin.js |2.170.153 | 81.159 |
|
|admin.js |2.170.153 | 78.971 |
|
||||||
|app.js |4.207.787 | 414.949 |
|
|app.js |4.207.787 | 409.340 |
|
||||||
|boot.js | 868.735 | 2.711 |
|
|boot.js | 868.735 | 4.142 |
|
||||||
|libs.js | 658.812 | 192.392 |
|
|libs.js | 658.812 | 185.881 |
|
||||||
|sieve.js | 0 | 85.597 |
|
|sieve.js | 0 | 85.599 |
|
||||||
|polyfills.js | 334.608 | 0 |
|
|polyfills.js | 334.608 | 0 |
|
||||||
|serviceworker.js | 0 | 285 |
|
|serviceworker.js | 0 | 285 |
|
||||||
|TOTAL |8.240.095 | 777.093 |
|
|TOTAL |8.240.095 | 764.218 |
|
||||||
|
|
||||||
|js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
|js/min/* |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
||||||
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|
|--------------- |--------: |--------: |------: |------: |--------: |--------: |
|
||||||
|admin.min.js | 256.831 | 40.198 | 73.606 | 13.281 | 60.877 | 11.916 |
|
|admin.min.js | 256.831 | 38.936 | 73.606 | 13.036 | 60.877 | 11.694 |
|
||||||
|app.min.js | 515.367 | 190.317 |139.456 | 63.879 |110.485 | 54.792 |
|
|app.min.js | 515.367 | 186.883 |139.456 | 63.102 |110.485 | 54.200 |
|
||||||
|boot.min.js | 84.659 | 1.600 | 26.998 | 940 | 23.643 | 767 |
|
|boot.min.js | 84.659 | 2.084 | 26.998 | 1.202 | 23.643 | 1.003 |
|
||||||
|libs.min.js | 584.772 | 91.434 |180.901 | 33.893 |155.182 | 30.383 |
|
|libs.min.js | 584.772 | 90.445 |180.901 | 33.592 |155.182 | 30.108 |
|
||||||
|sieve.min.js | 0 | 41.566 | 0 | 10.427 | 0 | 9.413 |
|
|sieve.min.js | 0 | 41.525 | 0 | 10.435 | 0 | 9.401 |
|
||||||
|polyfills.min.js | 32.837 | 0 | 11.406 | 0 | 10.175 | 0 |
|
|polyfills.min.js | 32.837 | 0 | 11.406 | 0 | 10.175 | 0 |
|
||||||
|TOTAL user |1.217.635 | 283.351 |358.761 | 98.712 |299.485 | 85.942 |
|
|TOTAL user |1.217.635 | 279.412 |358.761 | 97.896 |299.485 | 85.311 |
|
||||||
|TOTAL user+sieve |1.217.635 | 324.917 |358.761 |109.139 |299.485 | 95.355 |
|
|TOTAL user+sieve |1.217.635 | 320.937 |358.761 |108.331 |299.485 | 94.712 |
|
||||||
|TOTAL admin | 959.099 | 133.232 |292.911 | 48.114 |249.877 | 43.066 |
|
|TOTAL admin | 959.099 | 131.465 |292.911 | 47.830 |249.877 | 42.805 |
|
||||||
|
|
||||||
For a user its around 70% smaller and faster than traditional RainLoop.
|
For a user it is around 72% smaller and faster than traditional RainLoop.
|
||||||
|
|
||||||
### CSS changes
|
### CSS changes
|
||||||
|
|
||||||
|
|
@ -189,12 +189,12 @@ For a user its around 70% smaller and faster than traditional RainLoop.
|
||||||
|
|
||||||
|css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli |
|
|css/* |RainLoop |Snappy |RL gzip |SM gzip |SM brotli |
|
||||||
|------------ |-------: |------: |------: |------: |--------: |
|
|------------ |-------: |------: |------: |------: |--------: |
|
||||||
|app.css | 340.331 | 82.134 | 46.946 | 17.067 | 14.688 |
|
|app.css | 340.331 | 81.526 | 46.946 | 16.960 | 14.620 |
|
||||||
|app.min.css | 274.947 | 66.337 | 39.647 | 15.095 | 13.285 |
|
|app.min.css | 274.947 | 65.398 | 39.647 | 15.032 | 13.119 |
|
||||||
|boot.css | | 1.326 | | 664 | 545 |
|
|boot.css | | 1.326 | | 664 | 545 |
|
||||||
|boot.min.css | | 1.071 | | 590 | 474 |
|
|boot.min.css | | 1.071 | | 590 | 474 |
|
||||||
|admin.css | | 30.554 | | 6.984 | 6.085 |
|
|admin.css | | 30.296 | | 6.939 | 6.066 |
|
||||||
|admin.min.css | | 24.752 | | 6.339 | 5.606 |
|
|admin.min.css | | 24.420 | | 6.272 | 5.531 |
|
||||||
|
|
||||||
### PGP
|
### PGP
|
||||||
RainLoop uses the old OpenPGP.js v2
|
RainLoop uses the old OpenPGP.js v2
|
||||||
|
|
@ -208,7 +208,7 @@ See https://github.com/the-djmaze/openpgpjs for development
|
||||||
|
|
||||||
|OpenPGP |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
|OpenPGP |RainLoop |Snappy |RL gzip |SM gzip |RL brotli |SM brotli |
|
||||||
|--------------- |--------: |--------: |------: |-------: |--------: |--------: |
|
|--------------- |--------: |--------: |------: |-------: |--------: |--------: |
|
||||||
|openpgp.min.js | 330.742 | 539.988 |102.388 | 167.371 | 84.241 | 137.413 |
|
|openpgp.min.js | 330.742 | 541.176 |102.388 | 168.266 | 84.241 | 138.278 |
|
||||||
|openpgp.worker | 1.499 | | 824 | | 695 | |
|
|openpgp.worker | 1.499 | | 824 | | 695 | |
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
10
_include.php
10
_include.php
|
|
@ -4,16 +4,6 @@
|
||||||
|
|
||||||
//header('Strict-Transport-Security: max-age=31536000');
|
//header('Strict-Transport-Security: max-age=31536000');
|
||||||
|
|
||||||
/**
|
|
||||||
* Uncomment to use gzip compressed output
|
|
||||||
*/
|
|
||||||
//define('USE_GZIP', 1);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uncomment to use brotli compressed output
|
|
||||||
*/
|
|
||||||
//define('USE_BROTLI', 1);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uncomment to enable multiple domain installation.
|
* Uncomment to enable multiple domain installation.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -160,11 +160,6 @@ $tar->addFromString('index.php', $index);
|
||||||
$zip->addFile('README.md');
|
$zip->addFile('README.md');
|
||||||
$tar->addFile('README.md');
|
$tar->addFile('README.md');
|
||||||
|
|
||||||
$data = file_get_contents('dev/serviceworker.js');
|
|
||||||
//$data = file_get_contents('snappymail/v/0.0.0/static/js/min/serviceworker.min.js');
|
|
||||||
$zip->addFromString('serviceworker.js', $data);
|
|
||||||
$tar->addFromString('serviceworker.js', $data);
|
|
||||||
|
|
||||||
$zip->close();
|
$zip->close();
|
||||||
|
|
||||||
$tar->compress(Phar::GZ);
|
$tar->compress(Phar::GZ);
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ export class AdminApp extends AbstractApp {
|
||||||
rl.route.root();
|
rl.route.root();
|
||||||
setTimeout(() => location.href = '/', 1);
|
setTimeout(() => location.href = '/', 1);
|
||||||
} else if (SettingsGet('Auth')) {
|
} else if (SettingsGet('Auth')) {
|
||||||
this.weakPassword(SettingsGet('WeakPassword'));
|
this.weakPassword(SettingsGet('weakPassword'));
|
||||||
startScreens([SettingsAdminScreen]);
|
startScreens([SettingsAdminScreen]);
|
||||||
} else {
|
} else {
|
||||||
startScreens([LoginAdminScreen]);
|
startScreens([LoginAdminScreen]);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import 'External/User/ko';
|
import 'External/User/ko';
|
||||||
|
|
||||||
import { SMAudio } from 'Common/Audio';
|
import { SMAudio } from 'Common/Audio';
|
||||||
import { isArray, pString, pInt } from 'Common/Utils';
|
import { isArray, pInt } from 'Common/Utils';
|
||||||
import { mailToHelper, setLayoutResizer, dropdownsDetectVisibility } from 'Common/UtilsUser';
|
import { mailToHelper, setLayoutResizer, dropdownsDetectVisibility } from 'Common/UtilsUser';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
@ -156,22 +156,11 @@ export class AppUser extends AbstractApp {
|
||||||
? items.map(oValue => new AccountModel(oValue.email, oValue.name))
|
? items.map(oValue => new AccountModel(oValue.email, oValue.name))
|
||||||
: []
|
: []
|
||||||
);
|
);
|
||||||
AccountUserStore.unshift(new AccountModel(SettingsGet('MainEmail'), '', false));
|
AccountUserStore.unshift(new AccountModel(SettingsGet('mainEmail'), '', false));
|
||||||
|
|
||||||
items = oData.Result.Identities;
|
items = oData.Result.Identities;
|
||||||
IdentityUserStore(isArray(items)
|
IdentityUserStore(isArray(items)
|
||||||
? items.map(identityData => {
|
? items.map(identityData => IdentityModel.reviveFromJson(identityData))
|
||||||
const identity = new IdentityModel(
|
|
||||||
pString(identityData.Id),
|
|
||||||
pString(identityData.Email)
|
|
||||||
);
|
|
||||||
identity.name(pString(identityData.Name));
|
|
||||||
identity.replyTo(pString(identityData.ReplyTo));
|
|
||||||
identity.bcc(pString(identityData.Bcc));
|
|
||||||
identity.signature(pString(identityData.Signature));
|
|
||||||
identity.signatureInsertBefore(!!identityData.SignatureInsertBefore);
|
|
||||||
return identity;
|
|
||||||
})
|
|
||||||
: []
|
: []
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -193,18 +182,17 @@ export class AppUser extends AbstractApp {
|
||||||
bootstart() {
|
bootstart() {
|
||||||
super.bootstart();
|
super.bootstart();
|
||||||
|
|
||||||
addEventListener('resize', () => leftPanelDisabled(ThemeStore.isMobile() || 1000 > innerWidth));
|
|
||||||
addEventListener('beforeunload', event => {
|
addEventListener('beforeunload', event => {
|
||||||
if (arePopupsVisible() || (ThemeStore.isMobile() && MessageUserStore.message())) {
|
if (arePopupsVisible() || (!SettingsUserStore.layout() && MessageUserStore.message())) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return event.returnValue = "Are you sure you want to exit?";
|
return event.returnValue = i18n('POPUPS_ASK/EXIT_ARE_YOU_SURE');
|
||||||
}
|
}
|
||||||
}, {capture: true});
|
}, {capture: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
ThemeStore.populate();
|
ThemeStore.populate();
|
||||||
LanguageStore.language(SettingsGet('Language'));
|
LanguageStore.language(SettingsGet('language'));
|
||||||
changeTheme(SettingsGet('Theme'));
|
changeTheme(SettingsGet('Theme'));
|
||||||
this.start();
|
this.start();
|
||||||
}
|
}
|
||||||
|
|
@ -263,18 +251,15 @@ export class AppUser extends AbstractApp {
|
||||||
|
|
||||||
PgpUserStore.init();
|
PgpUserStore.init();
|
||||||
|
|
||||||
|
setTimeout(() => mailToHelper(SettingsGet('mailToEmail')), 500);
|
||||||
|
|
||||||
// When auto-login is active
|
// When auto-login is active
|
||||||
try {
|
|
||||||
navigator.registerProtocolHandler?.(
|
navigator.registerProtocolHandler?.(
|
||||||
'mailto',
|
'mailto',
|
||||||
location.protocol + '//' + location.host + location.pathname + '?mailto&to=%s',
|
location.protocol + '//' + location.host + location.pathname + '?mailto&to=%s',
|
||||||
(SettingsGet('Title') || 'SnappyMail')
|
(SettingsGet('title') || 'SnappyMail')
|
||||||
);
|
);
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => mailToHelper(SettingsGet('MailToEmail')), 500);
|
|
||||||
} else {
|
} else {
|
||||||
this.logout();
|
this.logout();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
import { MessageSetAction } from 'Common/EnumsUser';
|
let FOLDERS_CACHE = new Map,
|
||||||
import { isArray } from 'Common/Utils';
|
FOLDERS_HASH_MAP = new Map,
|
||||||
|
|
||||||
let FOLDERS_CACHE = {},
|
|
||||||
FOLDERS_NAME_CACHE = {},
|
|
||||||
MESSAGE_FLAGS_CACHE = {},
|
|
||||||
inboxFolderName = 'INBOX';
|
inboxFolderName = 'INBOX';
|
||||||
|
|
||||||
export const
|
export const
|
||||||
|
|
@ -11,18 +7,10 @@ export const
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
clearCache = () => {
|
clearCache = () => {
|
||||||
FOLDERS_CACHE = {};
|
FOLDERS_CACHE.clear();
|
||||||
FOLDERS_NAME_CACHE = {};
|
FOLDERS_HASH_MAP.clear();
|
||||||
MESSAGE_FLAGS_CACHE = {};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} folderFullName
|
|
||||||
* @param {string} uid
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
getMessageKey = (folderFullName, uid) => folderFullName + '#' + uid,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
|
|
@ -34,143 +22,35 @@ export const
|
||||||
setFolderInboxName = name => inboxFolderName = name,
|
setFolderInboxName = name => inboxFolderName = name,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} folderHash
|
* @param {string} fullNameHash
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
getFolderFullName = folderHash => (folderHash && FOLDERS_NAME_CACHE[folderHash]) || '',
|
getFolderFromHashMap = fullNameHash => getFolderFromCacheList(FOLDERS_HASH_MAP.get(fullNameHash)),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} folderHash
|
|
||||||
* @param {string} folderFullName
|
|
||||||
* @param {?FolderModel} folder
|
* @param {?FolderModel} folder
|
||||||
*/
|
*/
|
||||||
setFolder = folder => {
|
setFolder = folder => {
|
||||||
folder.hash = '';
|
folder.etag = '';
|
||||||
FOLDERS_CACHE[folder.fullName] = folder;
|
FOLDERS_CACHE.set(folder.fullName, folder);
|
||||||
FOLDERS_NAME_CACHE[folder.fullNameHash] = folder.fullName;
|
FOLDERS_HASH_MAP.set(folder.fullNameHash, folder.fullName);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} folderFullName
|
* @param {string} folderFullName
|
||||||
* @param {string} folderHash
|
* @param {string} folderETag
|
||||||
*/
|
*/
|
||||||
setFolderHash = (folderFullName, folderHash) =>
|
setFolderETag = (folderFullName, folderETag) =>
|
||||||
FOLDERS_CACHE[folderFullName] && (FOLDERS_CACHE[folderFullName].hash = folderHash),
|
FOLDERS_CACHE.has(folderFullName) && (FOLDERS_CACHE.get(folderFullName).etag = folderETag),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} folderFullName
|
* @param {string} folderFullName
|
||||||
* @returns {?FolderModel}
|
* @returns {?FolderModel}
|
||||||
*/
|
*/
|
||||||
getFolderFromCacheList = folderFullName =>
|
getFolderFromCacheList = folderFullName =>
|
||||||
FOLDERS_CACHE[folderFullName] || null,
|
FOLDERS_CACHE.get(folderFullName),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} folderFullName
|
* @param {string} folderFullName
|
||||||
*/
|
*/
|
||||||
removeFolderFromCacheList = folderFullName => delete FOLDERS_CACHE[folderFullName];
|
removeFolderFromCacheList = folderFullName => FOLDERS_CACHE.delete(folderFullName);
|
||||||
|
|
||||||
export class MessageFlagsCache
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* @param {string} folderFullName
|
|
||||||
* @param {string} uid
|
|
||||||
* @param {string} flag
|
|
||||||
* @returns {bool}
|
|
||||||
*/
|
|
||||||
static hasFlag(folderFullName, uid, flag) {
|
|
||||||
return MESSAGE_FLAGS_CACHE[folderFullName]?.[uid]?.includes(flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} folderFullName
|
|
||||||
* @param {string} uid
|
|
||||||
* @returns {?Array}
|
|
||||||
*/
|
|
||||||
static getFor(folderFullName, uid) {
|
|
||||||
return MESSAGE_FLAGS_CACHE[folderFullName]?.[uid];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} folderFullName
|
|
||||||
* @param {string} uid
|
|
||||||
* @param {Array} flagsCache
|
|
||||||
*/
|
|
||||||
static setFor(folderFullName, uid, flags) {
|
|
||||||
if (isArray(flags)) {
|
|
||||||
if (!MESSAGE_FLAGS_CACHE[folderFullName]) {
|
|
||||||
MESSAGE_FLAGS_CACHE[folderFullName] = {};
|
|
||||||
}
|
|
||||||
MESSAGE_FLAGS_CACHE[folderFullName][uid] = flags;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} folderFullName
|
|
||||||
*/
|
|
||||||
static clearFolder(folderFullName) {
|
|
||||||
MESSAGE_FLAGS_CACHE[folderFullName] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {(MessageModel|null)} message
|
|
||||||
*/
|
|
||||||
static initMessage(message) {
|
|
||||||
if (message) {
|
|
||||||
const uid = message.uid,
|
|
||||||
flags = this.getFor(message.folder, uid),
|
|
||||||
thread = message.threads();
|
|
||||||
|
|
||||||
isArray(flags) && message.flags(flags);
|
|
||||||
|
|
||||||
if (thread.length) {
|
|
||||||
message.hasUnseenSubMessage(!!thread.find(iSubUid =>
|
|
||||||
(uid !== iSubUid) && !this.hasFlag(message.folder, iSubUid, '\\seen')
|
|
||||||
));
|
|
||||||
message.hasFlaggedSubMessage(!!thread.find(iSubUid =>
|
|
||||||
(uid !== iSubUid) && this.hasFlag(message.folder, iSubUid, '\\flagged')
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {(MessageModel|null)} message
|
|
||||||
*/
|
|
||||||
static store(message) {
|
|
||||||
message && this.setFor(message.folder, message.uid, message.flags());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} folder
|
|
||||||
* @param {string} uid
|
|
||||||
* @param {number} setAction
|
|
||||||
*/
|
|
||||||
static storeBySetAction(folder, uid, setAction) {
|
|
||||||
let flags = this.getFor(folder, uid) || [];
|
|
||||||
const
|
|
||||||
unread = flags.includes('\\seen') ? 0 : 1,
|
|
||||||
add = item => flags.includes(item) || flags.push(item),
|
|
||||||
remove = item => flags = flags.filter(flag => flag != item);
|
|
||||||
|
|
||||||
switch (setAction) {
|
|
||||||
case MessageSetAction.SetSeen:
|
|
||||||
add('\\seen');
|
|
||||||
break;
|
|
||||||
case MessageSetAction.UnsetSeen:
|
|
||||||
remove('\\seen');
|
|
||||||
break;
|
|
||||||
case MessageSetAction.SetFlag:
|
|
||||||
add('\\flagged');
|
|
||||||
break;
|
|
||||||
case MessageSetAction.UnsetFlag:
|
|
||||||
remove('\\flagged');
|
|
||||||
break;
|
|
||||||
// no default
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setFor(folder, uid, flags);
|
|
||||||
|
|
||||||
return unread;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ SaveSettingStatus = {
|
||||||
/**
|
/**
|
||||||
* @enum {number}
|
* @enum {number}
|
||||||
*/
|
*/
|
||||||
Notification = {
|
Notifications = {
|
||||||
RequestError: 1,
|
RequestError: 1,
|
||||||
RequestAborted: 2,
|
RequestAborted: 2,
|
||||||
RequestTimeout: 3,
|
RequestTimeout: 3,
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export const EditorDefaultType = {
|
||||||
* @enum {number}
|
* @enum {number}
|
||||||
*/
|
*/
|
||||||
export const Layout = {
|
export const Layout = {
|
||||||
NoPreview: 0,
|
// NoPreview: 0,
|
||||||
SidePreview: 1,
|
SidePreview: 1,
|
||||||
BottomPreview: 2
|
BottomPreview: 2
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { isArray, arrayLength } from 'Common/Utils';
|
import { isArray, arrayLength } from 'Common/Utils';
|
||||||
import {
|
import {
|
||||||
MessageFlagsCache,
|
setFolderETag,
|
||||||
setFolderHash,
|
|
||||||
getFolderInboxName,
|
getFolderInboxName,
|
||||||
getFolderFromCacheList
|
getFolderFromCacheList
|
||||||
} from 'Common/Cache';
|
} from 'Common/Cache';
|
||||||
|
|
@ -9,8 +8,6 @@ import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
import { FolderUserStore } from 'Stores/User/Folder';
|
import { FolderUserStore } from 'Stores/User/Folder';
|
||||||
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||||
import { getNotification } from 'Common/Translator';
|
import { getNotification } from 'Common/Translator';
|
||||||
import { Settings } from 'Common/Globals';
|
|
||||||
import { serverRequest } from 'Common/Links';
|
|
||||||
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
|
|
@ -114,8 +111,8 @@ folderInformation = (folder, list) => {
|
||||||
|
|
||||||
if (arrayLength(list)) {
|
if (arrayLength(list)) {
|
||||||
list.forEach(messageListItem => {
|
list.forEach(messageListItem => {
|
||||||
MessageFlagsCache.getFor(folder, messageListItem.uid) || uids.push(messageListItem.uid);
|
uids.push(messageListItem.uid);
|
||||||
messageListItem.threads.forEach(uid => MessageFlagsCache.getFor(folder, uid) || uids.push(uid));
|
messageListItem.threads.forEach(uid => uids.push(uid));
|
||||||
});
|
});
|
||||||
count = uids.length;
|
count = uids.length;
|
||||||
}
|
}
|
||||||
|
|
@ -124,31 +121,21 @@ folderInformation = (folder, list) => {
|
||||||
Remote.request('FolderInformation', (iError, data) => {
|
Remote.request('FolderInformation', (iError, data) => {
|
||||||
if (!iError && data.Result) {
|
if (!iError && data.Result) {
|
||||||
const result = data.Result,
|
const result = data.Result,
|
||||||
folderFromCache = getFolderFromCacheList(result.folder);
|
folderFromCache = getFolderFromCacheList(result.name);
|
||||||
if (folderFromCache) {
|
if (folderFromCache) {
|
||||||
const oldHash = folderFromCache.hash,
|
const oldHash = folderFromCache.etag,
|
||||||
unreadCountChange = (folderFromCache.unreadEmails() !== result.unreadEmails);
|
unreadCountChange = (folderFromCache.unreadEmails() !== result.unreadEmails);
|
||||||
|
|
||||||
// folderFromCache.revivePropertiesFromJson(result);
|
// folderFromCache.revivePropertiesFromJson(result);
|
||||||
folderFromCache.expires = Date.now();
|
folderFromCache.expires = Date.now();
|
||||||
folderFromCache.uidNext = result.uidNext;
|
folderFromCache.uidNext = result.uidNext;
|
||||||
folderFromCache.hash = result.hash;
|
folderFromCache.etag = result.etag;
|
||||||
folderFromCache.totalEmails(result.totalEmails);
|
folderFromCache.totalEmails(result.totalEmails);
|
||||||
folderFromCache.unreadEmails(result.unreadEmails);
|
folderFromCache.unreadEmails(result.unreadEmails);
|
||||||
|
|
||||||
unreadCountChange && MessageFlagsCache.clearFolder(folderFromCache.fullName);
|
|
||||||
|
|
||||||
if (result.messagesFlags.length) {
|
|
||||||
result.messagesFlags.forEach(message =>
|
|
||||||
MessageFlagsCache.setFor(folderFromCache.fullName, message.uid.toString(), message.flags)
|
|
||||||
);
|
|
||||||
|
|
||||||
MessagelistUserStore.reloadFlagsAndCachedMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
MessagelistUserStore.notifyNewMessages(folderFromCache.fullName, result.newMessages);
|
MessagelistUserStore.notifyNewMessages(folderFromCache.fullName, result.newMessages);
|
||||||
|
|
||||||
if (!oldHash || unreadCountChange || result.hash !== oldHash) {
|
if (!oldHash || unreadCountChange || result.etag !== oldHash) {
|
||||||
if (folderFromCache.fullName === FolderUserStore.currentFolderFullName()) {
|
if (folderFromCache.fullName === FolderUserStore.currentFolderFullName()) {
|
||||||
MessagelistUserStore.reload();
|
MessagelistUserStore.reload();
|
||||||
/*
|
/*
|
||||||
|
|
@ -165,8 +152,6 @@ folderInformation = (folder, list) => {
|
||||||
flagsUids: uids,
|
flagsUids: uids,
|
||||||
uidNext: getFolderFromCacheList(folder)?.uidNext || 0 // Used to check for new messages
|
uidNext: getFolderFromCacheList(folder)?.uidNext || 0 // Used to check for new messages
|
||||||
});
|
});
|
||||||
} else if (SettingsUserStore.useThreads()) {
|
|
||||||
MessagelistUserStore.reloadFlagsAndCachedMessage();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -181,21 +166,18 @@ folderInformationMultiply = (boot = false) => {
|
||||||
if (!iError && arrayLength(oData.Result)) {
|
if (!iError && arrayLength(oData.Result)) {
|
||||||
const utc = Date.now();
|
const utc = Date.now();
|
||||||
oData.Result.forEach(item => {
|
oData.Result.forEach(item => {
|
||||||
const folder = getFolderFromCacheList(item.folder);
|
const folder = getFolderFromCacheList(item.name);
|
||||||
|
|
||||||
if (folder) {
|
if (folder) {
|
||||||
const oldHash = folder.hash,
|
const oldHash = folder.etag,
|
||||||
unreadCountChange = folder.unreadEmails() !== item.unreadEmails;
|
unreadCountChange = folder.unreadEmails() !== item.unreadEmails;
|
||||||
|
|
||||||
// folder.revivePropertiesFromJson(item);
|
// folder.revivePropertiesFromJson(item);
|
||||||
folder.expires = utc;
|
folder.expires = utc;
|
||||||
folder.hash = item.hash;
|
folder.etag = item.etag;
|
||||||
folder.totalEmails(item.totalEmails);
|
folder.totalEmails(item.totalEmails);
|
||||||
folder.unreadEmails(item.unreadEmails);
|
folder.unreadEmails(item.unreadEmails);
|
||||||
|
|
||||||
unreadCountChange && MessageFlagsCache.clearFolder(folder.fullName);
|
if (!oldHash || item.etag !== oldHash) {
|
||||||
|
|
||||||
if (!oldHash || item.hash !== oldHash) {
|
|
||||||
if (folder.fullName === FolderUserStore.currentFolderFullName()) {
|
if (folder.fullName === FolderUserStore.currentFolderFullName()) {
|
||||||
MessagelistUserStore.reload();
|
MessagelistUserStore.reload();
|
||||||
}
|
}
|
||||||
|
|
@ -217,13 +199,13 @@ folderInformationMultiply = (boot = false) => {
|
||||||
|
|
||||||
moveOrDeleteResponseHelper = (iError, oData) => {
|
moveOrDeleteResponseHelper = (iError, oData) => {
|
||||||
if (iError) {
|
if (iError) {
|
||||||
setFolderHash(FolderUserStore.currentFolderFullName(), '');
|
setFolderETag(FolderUserStore.currentFolderFullName(), '');
|
||||||
alert(getNotification(iError));
|
alert(getNotification(iError));
|
||||||
} else if (FolderUserStore.currentFolder()) {
|
} else if (FolderUserStore.currentFolder()) {
|
||||||
if (2 === arrayLength(oData.Result)) {
|
if (2 === arrayLength(oData.Result)) {
|
||||||
setFolderHash(oData.Result[0], oData.Result[1]);
|
setFolderETag(oData.Result[0], oData.Result[1]);
|
||||||
} else {
|
} else {
|
||||||
setFolderHash(FolderUserStore.currentFolderFullName(), '');
|
setFolderETag(FolderUserStore.currentFolderFullName(), '');
|
||||||
}
|
}
|
||||||
MessagelistUserStore.reload(!MessagelistUserStore.length);
|
MessagelistUserStore.reload(!MessagelistUserStore.length);
|
||||||
}
|
}
|
||||||
|
|
@ -286,28 +268,20 @@ moveMessagesToFolder = (sFromFolderFullName, oUids, sToFolderFullName, bCopy) =>
|
||||||
},
|
},
|
||||||
|
|
||||||
dropFilesInFolder = (sFolderFullName, files) => {
|
dropFilesInFolder = (sFolderFullName, files) => {
|
||||||
let count = 0,
|
let count = files.length;
|
||||||
fn = () => 0 == --count
|
|
||||||
&& FolderUserStore.currentFolderFullName() == sFolderFullName
|
|
||||||
&& MessagelistUserStore.reload(true, true);
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
if ('message/rfc822' === file.type) {
|
if ('message/rfc822' === file.type) {
|
||||||
++count;
|
|
||||||
let data = new FormData;
|
let data = new FormData;
|
||||||
data.append('folder', sFolderFullName);
|
data.append('folder', sFolderFullName);
|
||||||
data.append('appendFile', file);
|
data.append('appendFile', file);
|
||||||
data.XToken = Settings.app('token');
|
Remote.request('FolderAppend', (iError, data)=>{
|
||||||
fetch(serverRequest('Append'), {
|
iError && console.error(data.ErrorMessage);
|
||||||
method: 'POST',
|
0 == --count
|
||||||
mode: 'same-origin',
|
&& FolderUserStore.currentFolderFullName() == sFolderFullName
|
||||||
cache: 'no-cache',
|
&& MessagelistUserStore.reload(true, true);
|
||||||
redirect: 'error',
|
}, data);
|
||||||
referrerPolicy: 'no-referrer',
|
} else {
|
||||||
credentials: 'same-origin',
|
--count;
|
||||||
body: data
|
|
||||||
})
|
|
||||||
.then(fn)
|
|
||||||
.catch(fn);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,19 @@
|
||||||
import { doc, elementById, $htmlCL } from 'Common/Globals';
|
import { doc, appEl, $htmlCL } from 'Common/Globals';
|
||||||
|
|
||||||
// Fullscreen must be on app, else other popups fail
|
// Fullscreen must be on app, else other popups fail
|
||||||
export const
|
export const
|
||||||
app = elementById('rl-app'),
|
appFullscreen = () => (doc.fullscreenElement || doc.webkitFullscreenElement) === appEl,
|
||||||
appFullscreen = () => (doc.fullscreenElement || doc.webkitFullscreenElement) === app,
|
|
||||||
exitFullscreen = () => appFullscreen() && (doc.exitFullscreen || doc.webkitExitFullscreen).call(doc),
|
exitFullscreen = () => appFullscreen() && (doc.exitFullscreen || doc.webkitExitFullscreen).call(doc),
|
||||||
isFullscreen = ko.observable(false),
|
isFullscreen = ko.observable(false),
|
||||||
toggleFullscreen = () => isFullscreen() ? exitFullscreen() : app.requestFullscreen();
|
toggleFullscreen = () => isFullscreen() ? exitFullscreen() : appEl.requestFullscreen();
|
||||||
|
|
||||||
if (app) {
|
if (appEl) {
|
||||||
let event = 'fullscreenchange';
|
let event = 'fullscreenchange';
|
||||||
if (!app.requestFullscreen && app.webkitRequestFullscreen) {
|
if (!appEl.requestFullscreen && appEl.webkitRequestFullscreen) {
|
||||||
app.requestFullscreen = app.webkitRequestFullscreen;
|
appEl.requestFullscreen = appEl.webkitRequestFullscreen;
|
||||||
event = 'webkit'+event;
|
event = 'webkit'+event;
|
||||||
}
|
}
|
||||||
if (app.requestFullscreen) {
|
if (appEl.requestFullscreen) {
|
||||||
doc.addEventListener(event, () => {
|
doc.addEventListener(event, () => {
|
||||||
isFullscreen(appFullscreen());
|
isFullscreen(appFullscreen());
|
||||||
$htmlCL.toggle('rl-fullscreen', appFullscreen());
|
$htmlCL.toggle('rl-fullscreen', appFullscreen());
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@ export const
|
||||||
|
|
||||||
elementById = id => doc.getElementById(id),
|
elementById = id => doc.getElementById(id),
|
||||||
|
|
||||||
|
appEl = elementById('rl-app'),
|
||||||
|
|
||||||
Settings = rl.settings,
|
Settings = rl.settings,
|
||||||
SettingsGet = Settings.get,
|
SettingsGet = Settings.get,
|
||||||
SettingsCapa = name => name && !!(SettingsGet('Capa') || {})[name],
|
SettingsCapa = name => name && !!(SettingsGet('Capa') || {})[name],
|
||||||
|
|
@ -31,6 +33,11 @@ export const
|
||||||
new CustomEvent(name, {detail:detail, cancelable: !!cancelable})
|
new CustomEvent(name, {detail:detail, cancelable: !!cancelable})
|
||||||
),
|
),
|
||||||
|
|
||||||
|
stopEvent = event => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
},
|
||||||
|
|
||||||
formFieldFocused = () => doc.activeElement?.matches('input,textarea'),
|
formFieldFocused = () => doc.activeElement?.matches('input,textarea'),
|
||||||
|
|
||||||
addShortcut = (...args) => shortcuts.add(...args),
|
addShortcut = (...args) => shortcuts.add(...args),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { forEachObjectEntry, pInt } from 'Common/Utils';
|
||||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
|
|
||||||
const
|
const
|
||||||
tpl = createElement('template'),
|
tmpl = createElement('template'),
|
||||||
htmlre = /[&<>"']/g,
|
htmlre = /[&<>"']/g,
|
||||||
httpre = /^(https?:)?\/\//i,
|
httpre = /^(https?:)?\/\//i,
|
||||||
htmlmap = {
|
htmlmap = {
|
||||||
|
|
@ -14,10 +14,18 @@ const
|
||||||
"'": '''
|
"'": '''
|
||||||
},
|
},
|
||||||
|
|
||||||
|
disallowedTags = [
|
||||||
|
'svg','script','title','link','base','meta',
|
||||||
|
'input','output','select','button','textarea',
|
||||||
|
'bgsound','keygen','source','object','embed','applet','iframe','frame','frameset','video','audio','area','map'
|
||||||
|
// not supported by <template> element
|
||||||
|
// ,'html','head','body'
|
||||||
|
].join(','),
|
||||||
|
|
||||||
blockquoteSwitcher = () => {
|
blockquoteSwitcher = () => {
|
||||||
SettingsUserStore.collapseBlockquotes() &&
|
SettingsUserStore.collapseBlockquotes() &&
|
||||||
// tpl.content.querySelectorAll('blockquote').forEach(node => {
|
// tmpl.content.querySelectorAll('blockquote').forEach(node => {
|
||||||
[...tpl.content.querySelectorAll('blockquote')].reverse().forEach(node => {
|
[...tmpl.content.querySelectorAll('blockquote')].reverse().forEach(node => {
|
||||||
const el = createElement('details', {class:'sm-bq-switcher'});
|
const el = createElement('details', {class:'sm-bq-switcher'});
|
||||||
el.innerHTML = '<summary>•••</summary>';
|
el.innerHTML = '<summary>•••</summary>';
|
||||||
node.replaceWith(el);
|
node.replaceWith(el);
|
||||||
|
|
@ -27,11 +35,11 @@ const
|
||||||
|
|
||||||
replaceWithChildren = node => node.replaceWith(...[...node.childNodes]),
|
replaceWithChildren = node => node.replaceWith(...[...node.childNodes]),
|
||||||
|
|
||||||
url = /(^|\s|\n|\/?>)(https?:\/\/[-A-Z0-9+&#/%?=()~_|!:,.;]*[-A-Z0-9+&#/%=~()_|])/gi,
|
urlRegExp = /https?:\/\/[^\p{C}\p{Z}]+[^\p{C}\p{Z}.]/gu,
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
email = /(^|\s|\n|\/?>)((?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x21\x23-\x5b\x5d-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x21-\x5a\x53-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])+)\]))/gi,
|
email = /(^|\r|\n|\p{C}\p{Z})((?:[^"(),.:;<>@[\]\\\p{C}\p{Z}]+(?:\.[^"(),.:;<>@[\]\\\p{C}\p{Z}]+)*|"(?:\\?[^"\\\p{C}\p{Z}])*")@[^@\p{C}\p{Z}]+[^@\p{C}\p{Z}.])/gui,
|
||||||
// rfc3966
|
// rfc3966
|
||||||
tel = /(^|\s|\n|\/?>)(tel:(\+[0-9().-]+|[0-9*#().-]+(;phone-context=\+[0-9+().-]+)?))/g,
|
tel = /(tel:(\+[0-9().-]+|[0-9*#().-]+(;phone-context=\+[0-9+().-]+)?))/g,
|
||||||
|
|
||||||
// Strip tracking
|
// Strip tracking
|
||||||
/** TODO: implement other url strippers like from
|
/** TODO: implement other url strippers like from
|
||||||
|
|
@ -90,6 +98,13 @@ const
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
cleanCSS = source =>
|
||||||
|
source.trim().replace(/-(ms|webkit)-[^;]+(;|$)/g, '')
|
||||||
|
.replace(/white-space[^;]+(;|$)/g, '')
|
||||||
|
// Drop Microsoft Office style properties
|
||||||
|
// .replace(/mso-[^:;]+:[^;]+/gi, '')
|
||||||
|
,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Parses given css string, and returns css object
|
Parses given css string, and returns css object
|
||||||
keys as selectors and values are css rules
|
keys as selectors and values are css rules
|
||||||
|
|
@ -161,7 +176,7 @@ const
|
||||||
// we have standard css
|
// we have standard css
|
||||||
css.push({
|
css.push({
|
||||||
selector: selector,
|
selector: selector,
|
||||||
rules: arr[6]
|
rules: cleanCSS(arr[6])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -239,13 +254,6 @@ export const
|
||||||
// td
|
// td
|
||||||
'colspan', 'rowspan', 'headers'
|
'colspan', 'rowspan', 'headers'
|
||||||
],
|
],
|
||||||
disallowedTags = [
|
|
||||||
'SVG','SCRIPT','TITLE','LINK','BASE','META',
|
|
||||||
'INPUT','OUTPUT','SELECT','BUTTON','TEXTAREA',
|
|
||||||
'BGSOUND','KEYGEN','SOURCE','OBJECT','EMBED','APPLET','IFRAME','FRAME','FRAMESET','VIDEO','AUDIO','AREA','MAP'
|
|
||||||
// Not supported by <template> element
|
|
||||||
// ,'HTML','HEAD','BODY'
|
|
||||||
],
|
|
||||||
nonEmptyTags = [
|
nonEmptyTags = [
|
||||||
'A','B','EM','I','SPAN','STRONG'
|
'A','B','EM','I','SPAN','STRONG'
|
||||||
];
|
];
|
||||||
|
|
@ -256,9 +264,9 @@ export const
|
||||||
msgId = 0;
|
msgId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
tpl.innerHTML = html
|
tmpl.innerHTML = html
|
||||||
// Strip Microsoft comments
|
// Strip Microsoft comments
|
||||||
.replace(/<!--\[if[\s\S]*?endif\]-->/gi, '')
|
.replace(/<!--\[if[\s\S]*?-->/gi, '')
|
||||||
// .replace(/<pre[^>]*>[\s\S]*?<\/pre>/gi, pre => pre.replace(/\n/g, '\n<br>'))
|
// .replace(/<pre[^>]*>[\s\S]*?<\/pre>/gi, pre => pre.replace(/\n/g, '\n<br>'))
|
||||||
// Not supported by <template> element
|
// Not supported by <template> element
|
||||||
// .replace(/<!doctype[^>]*>/gi, '')
|
// .replace(/<!doctype[^>]*>/gi, '')
|
||||||
|
|
@ -274,16 +282,17 @@ export const
|
||||||
|
|
||||||
// \MailSo\Base\HtmlUtils::ClearComments()
|
// \MailSo\Base\HtmlUtils::ClearComments()
|
||||||
// https://github.com/the-djmaze/snappymail/issues/187
|
// https://github.com/the-djmaze/snappymail/issues/187
|
||||||
const nodeIterator = document.createNodeIterator(tpl.content, NodeFilter.SHOW_COMMENT);
|
const nodeIterator = document.createNodeIterator(tmpl.content, NodeFilter.SHOW_COMMENT);
|
||||||
while (nodeIterator.nextNode()) {
|
while (nodeIterator.nextNode()) {
|
||||||
nodeIterator.referenceNode.remove();
|
nodeIterator.referenceNode.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (0 < bqLevel) {
|
tmpl.content.querySelectorAll(
|
||||||
tpl.content.querySelectorAll(new Array(1 + bqLevel).fill('blockquote').join(' ')).forEach(node => node.remove());
|
disallowedTags
|
||||||
}
|
+ (0 < bqLevel ? ',' + (new Array(1 + bqLevel).fill('blockquote').join(' ')) : '')
|
||||||
|
).forEach(oElement => oElement.remove());
|
||||||
|
|
||||||
tpl.content.querySelectorAll('*').forEach(oElement => {
|
[...tmpl.content.querySelectorAll('*')].forEach(oElement => {
|
||||||
const name = oElement.tagName,
|
const name = oElement.tagName,
|
||||||
oStyle = oElement.style;
|
oStyle = oElement.style;
|
||||||
|
|
||||||
|
|
@ -303,8 +312,7 @@ export const
|
||||||
}
|
}
|
||||||
|
|
||||||
// \MailSo\Base\HtmlUtils::ClearTags()
|
// \MailSo\Base\HtmlUtils::ClearTags()
|
||||||
if (disallowedTags.includes(name)
|
if ('none' == oStyle.display
|
||||||
|| 'none' == oStyle.display
|
|
||||||
|| 'hidden' == oStyle.visibility
|
|| 'hidden' == oStyle.visibility
|
||||||
// || (oStyle.lineHeight && 1 > parseFloat(oStyle.lineHeight)
|
// || (oStyle.lineHeight && 1 > parseFloat(oStyle.lineHeight)
|
||||||
// || (oStyle.maxHeight && 1 > parseFloat(oStyle.maxHeight)
|
// || (oStyle.maxHeight && 1 > parseFloat(oStyle.maxHeight)
|
||||||
|
|
@ -320,15 +328,16 @@ export const
|
||||||
hasAttribute = name => oElement.hasAttribute(name),
|
hasAttribute = name => oElement.hasAttribute(name),
|
||||||
getAttribute = name => hasAttribute(name) ? oElement.getAttribute(name).trim() : '',
|
getAttribute = name => hasAttribute(name) ? oElement.getAttribute(name).trim() : '',
|
||||||
setAttribute = (name, value) => oElement.setAttribute(name, value),
|
setAttribute = (name, value) => oElement.setAttribute(name, value),
|
||||||
delAttribute = name => oElement.removeAttribute(name);
|
delAttribute = name => {
|
||||||
|
let value = getAttribute(name);
|
||||||
|
oElement.removeAttribute(name);
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
if ('mail-body' === className) {
|
if ('mail-body' === className) {
|
||||||
forEachObjectEntry(tasks, (name, cb) => {
|
forEachObjectEntry(tasks, (name, cb) =>
|
||||||
if (hasAttribute(name)) {
|
hasAttribute(name) && cb(delAttribute(name), oElement)
|
||||||
cb(getAttribute(name), oElement);
|
);
|
||||||
delAttribute(name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (msgId && className) {
|
} else if (msgId && className) {
|
||||||
oElement.className = className.replace(/(^|\s+)/g, '$1msg-');
|
oElement.className = className.replace(/(^|\s+)/g, '$1msg-');
|
||||||
}
|
}
|
||||||
|
|
@ -350,21 +359,21 @@ export const
|
||||||
if (!oStyle.backgroundImage) {
|
if (!oStyle.backgroundImage) {
|
||||||
if ('TD' !== name && 'TH' !== name) {
|
if ('TD' !== name && 'TH' !== name) {
|
||||||
// Make width responsive
|
// Make width responsive
|
||||||
if (hasAttribute('width')) {
|
if (hasAttribute('width') && !oStyle.width) {
|
||||||
value = getAttribute('width');
|
value = delAttribute('width');
|
||||||
oStyle.width = value.includes('%') ? value : value + 'px';
|
oStyle.width = value.includes('%') ? value : value + 'px';
|
||||||
delAttribute('width');
|
|
||||||
}
|
}
|
||||||
value = oStyle.removeProperty('width');
|
value = oStyle.width;
|
||||||
if (parseInt(value,10) && !oStyle.maxWidth) {
|
if (100 < parseInt(value,10) && !oStyle.maxWidth) {
|
||||||
oStyle.maxWidth = value;
|
oStyle.maxWidth = value;
|
||||||
oStyle.width = '100%';
|
oStyle.width = '100%';
|
||||||
|
} else if (!value?.includes('%')) {
|
||||||
|
oStyle.removeProperty('width');
|
||||||
}
|
}
|
||||||
// Make height responsive
|
// Make height responsive
|
||||||
if (hasAttribute('height')) {
|
if (hasAttribute('height')) {
|
||||||
value = getAttribute('height');
|
value = delAttribute('height');
|
||||||
oStyle.height = value.includes('%') ? value : value + 'px';
|
oStyle.height = value.includes('%') ? value : value + 'px';
|
||||||
delAttribute('height');
|
|
||||||
}
|
}
|
||||||
value = oStyle.removeProperty('height');
|
value = oStyle.removeProperty('height');
|
||||||
if (value && !oStyle.maxHeight) {
|
if (value && !oStyle.maxHeight) {
|
||||||
|
|
@ -402,8 +411,7 @@ export const
|
||||||
|
|
||||||
let skipStyle = false;
|
let skipStyle = false;
|
||||||
if (hasAttribute('src')) {
|
if (hasAttribute('src')) {
|
||||||
value = stripTracking(getAttribute('src'));
|
value = stripTracking(delAttribute('src'));
|
||||||
delAttribute('src');
|
|
||||||
|
|
||||||
if ('IMG' === name) {
|
if ('IMG' === name) {
|
||||||
oElement.loading = 'lazy';
|
oElement.loading = 'lazy';
|
||||||
|
|
@ -464,18 +472,15 @@ export const
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAttribute('background')) {
|
if (hasAttribute('background')) {
|
||||||
oStyle.backgroundImage = 'url("' + getAttribute('background') + '")';
|
oStyle.backgroundImage = 'url("' + delAttribute('background') + '")';
|
||||||
delAttribute('background');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAttribute('bgcolor')) {
|
if (hasAttribute('bgcolor')) {
|
||||||
oStyle.backgroundColor = getAttribute('bgcolor');
|
oStyle.backgroundColor = delAttribute('bgcolor');
|
||||||
delAttribute('bgcolor');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasAttribute('color')) {
|
if (hasAttribute('color')) {
|
||||||
oStyle.color = getAttribute('color');
|
oStyle.color = delAttribute('color');
|
||||||
delAttribute('color');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!skipStyle) {
|
if (!skipStyle) {
|
||||||
|
|
@ -540,8 +545,7 @@ export const
|
||||||
oStyle.removeProperty('color');
|
oStyle.removeProperty('color');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop Microsoft Office style properties
|
oStyle.cssText = cleanCSS(oStyle.cssText);
|
||||||
// oStyle.cssText = oStyle.cssText.replace(/mso-[^:;]+:[^;]+/gi, '');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (debug && aAttrsForRemove.length) {
|
if (debug && aAttrsForRemove.length) {
|
||||||
|
|
@ -551,8 +555,8 @@ export const
|
||||||
|
|
||||||
blockquoteSwitcher();
|
blockquoteSwitcher();
|
||||||
|
|
||||||
// return tpl.content.firstChild;
|
// return tmpl.content.firstChild;
|
||||||
result.html = tpl.innerHTML.trim();
|
result.html = tmpl.innerHTML.trim();
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -563,7 +567,7 @@ export const
|
||||||
htmlToPlain = html => {
|
htmlToPlain = html => {
|
||||||
const
|
const
|
||||||
hr = '⎯'.repeat(64),
|
hr = '⎯'.repeat(64),
|
||||||
forEach = (selector, fn) => tpl.content.querySelectorAll(selector).forEach(fn),
|
forEach = (selector, fn) => tmpl.content.querySelectorAll(selector).forEach(fn),
|
||||||
blockquotes = node => {
|
blockquotes = node => {
|
||||||
let bq;
|
let bq;
|
||||||
while ((bq = node.querySelector('blockquote'))) {
|
while ((bq = node.querySelector('blockquote'))) {
|
||||||
|
|
@ -591,7 +595,7 @@ export const
|
||||||
html = html.replace(/\n*<\/(div|tr)(\s[\s\S]*?)?>\n*/gi, '\n');
|
html = html.replace(/\n*<\/(div|tr)(\s[\s\S]*?)?>\n*/gi, '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
tpl.innerHTML = html
|
tmpl.innerHTML = html
|
||||||
.replace(/<t[dh](\s[\s\S]*?)?>/gi, '\t')
|
.replace(/<t[dh](\s[\s\S]*?)?>/gi, '\t')
|
||||||
.replace(/<\/tr(\s[\s\S]*?)?>/gi, '\n');
|
.replace(/<\/tr(\s[\s\S]*?)?>/gi, '\n');
|
||||||
|
|
||||||
|
|
@ -641,16 +645,16 @@ export const
|
||||||
forEach('i,em', i => i.replaceWith(`*${i.textContent}*`));
|
forEach('i,em', i => i.replaceWith(`*${i.textContent}*`));
|
||||||
|
|
||||||
// Convert line-breaks
|
// Convert line-breaks
|
||||||
tpl.innerHTML = tpl.innerHTML
|
tmpl.innerHTML = tmpl.innerHTML
|
||||||
.replace(/\n{3,}/gm, '\n\n')
|
.replace(/\n{3,}/gm, '\n\n')
|
||||||
.replace(/\n<br[^>]*>/g, '\n')
|
.replace(/\n<br[^>]*>/g, '\n')
|
||||||
.replace(/<br[^>]*>\n/g, '\n');
|
.replace(/<br[^>]*>\n/g, '\n');
|
||||||
forEach('br', br => br.replaceWith('\n'));
|
forEach('br', br => br.replaceWith('\n'));
|
||||||
|
|
||||||
// Blockquotes must be last
|
// Blockquotes must be last
|
||||||
blockquotes(tpl.content);
|
blockquotes(tmpl.content);
|
||||||
|
|
||||||
return (tpl.content.textContent || '').trim();
|
return (tmpl.content.textContent || '').trim();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -704,22 +708,22 @@ export const
|
||||||
aText = aNextText;
|
aText = aNextText;
|
||||||
} while (bDo);
|
} while (bDo);
|
||||||
|
|
||||||
tpl.innerHTML = aText.join('\n')
|
tmpl.innerHTML = aText.join('\n')
|
||||||
// .replace(/~~~\/blockquote~~~\n~~~blockquote~~~/g, '\n')
|
// .replace(/~~~\/blockquote~~~\n~~~blockquote~~~/g, '\n')
|
||||||
.replace(/&/g, '&')
|
.replace(/&/g, '&')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(url, (...m) => {
|
.replace(urlRegExp, (...m) => {
|
||||||
m[2] = stripTracking(m[2]);
|
m[0] = stripTracking(m[0]);
|
||||||
return `${m[1]}<a href="${m[2]}" target="_blank">${m[2]}</a>`;
|
return `<a href="${m[0]}" target="_blank">${m[0]}</a>`;
|
||||||
})
|
})
|
||||||
.replace(email, '$1<a href="mailto:$2">$2</a>')
|
.replace(email, '$1<a href="mailto:$2">$2</a>')
|
||||||
.replace(tel, '$1<a href="$2">$2</a>')
|
.replace(tel, '<a href="$1">$1</a>')
|
||||||
.replace(/~~~blockquote~~~\s*/g, '<blockquote>')
|
.replace(/~~~blockquote~~~\s*/g, '<blockquote>')
|
||||||
.replace(/\s*~~~\/blockquote~~~/g, '</blockquote>')
|
.replace(/\s*~~~\/blockquote~~~/g, '</blockquote>')
|
||||||
.replace(/\n/g, '<br>');
|
.replace(/\n/g, '<br>');
|
||||||
blockquoteSwitcher();
|
blockquoteSwitcher();
|
||||||
return tpl.innerHTML.trim();
|
return tmpl.innerHTML.trim();
|
||||||
},
|
},
|
||||||
|
|
||||||
WYSIWYGS = ko.observableArray();
|
WYSIWYGS = ko.observableArray();
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export const
|
||||||
*/
|
*/
|
||||||
serverRequestRaw = (type, hash) =>
|
serverRequestRaw = (type, hash) =>
|
||||||
BASE + '?/Raw/' + SUB_QUERY_PREFIX + '/'
|
BASE + '?/Raw/' + SUB_QUERY_PREFIX + '/'
|
||||||
+ '0/' // Settings.get('AccountHash') ?
|
+ '0/' // Settings.get('accountHash') ?
|
||||||
+ (type
|
+ (type
|
||||||
? type + '/' + (hash ? SUB_QUERY_PREFIX + '/' + hash : '')
|
? type + '/' + (hash ? SUB_QUERY_PREFIX + '/' + hash : '')
|
||||||
: ''),
|
: ''),
|
||||||
|
|
|
||||||
|
|
@ -87,8 +87,7 @@ export class Selector {
|
||||||
* Below code is used to keep checked/focused/selected states when array is refreshed.
|
* Below code is used to keep checked/focused/selected states when array is refreshed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let aCache = [],
|
let aCheckedCache = [],
|
||||||
aCheckedCache = [],
|
|
||||||
mFocused = null,
|
mFocused = null,
|
||||||
mSelected = null;
|
mSelected = null;
|
||||||
|
|
||||||
|
|
@ -99,7 +98,6 @@ export class Selector {
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
const uid = this.getItemUid(item);
|
const uid = this.getItemUid(item);
|
||||||
if (uid) {
|
if (uid) {
|
||||||
aCache.push(uid);
|
|
||||||
item.checked() && aCheckedCache.push(uid);
|
item.checked() && aCheckedCache.push(uid);
|
||||||
if (!mFocused && item.focused()) {
|
if (!mFocused && item.focused()) {
|
||||||
mFocused = uid;
|
mFocused = uid;
|
||||||
|
|
@ -118,8 +116,7 @@ export class Selector {
|
||||||
koList.subscribe(aItems => {
|
koList.subscribe(aItems => {
|
||||||
selectedItemUseCallback = false;
|
selectedItemUseCallback = false;
|
||||||
|
|
||||||
koFocusedItem(null);
|
this.unselect();
|
||||||
koSelectedItem(null);
|
|
||||||
|
|
||||||
if (isArray(aItems)) {
|
if (isArray(aItems)) {
|
||||||
let temp,
|
let temp,
|
||||||
|
|
@ -182,7 +179,6 @@ export class Selector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aCache = [];
|
|
||||||
aCheckedCache = [];
|
aCheckedCache = [];
|
||||||
mFocused = null;
|
mFocused = null;
|
||||||
mSelected = null;
|
mSelected = null;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
import { Notification, UploadErrorCode } from 'Common/Enums';
|
import { Notifications, UploadErrorCode } from 'Common/Enums';
|
||||||
import { langLink } from 'Common/Links';
|
import { langLink } from 'Common/Links';
|
||||||
import { doc, createElement } from 'Common/Globals';
|
import { doc, createElement } from 'Common/Globals';
|
||||||
import { getKeyByValue, forEachObjectEntry } from 'Common/Utils';
|
import { getKeyByValue, forEachObjectEntry } from 'Common/Utils';
|
||||||
|
|
@ -20,7 +20,7 @@ const
|
||||||
i18nKey = key => key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(),
|
i18nKey = key => key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(),
|
||||||
|
|
||||||
getNotificationMessage = code => {
|
getNotificationMessage = code => {
|
||||||
let key = getKeyByValue(Notification, code);
|
let key = getKeyByValue(Notifications, code);
|
||||||
return key ? I18N_DATA.NOTIFICATIONS[i18nKey(key).replace('_NOTIFICATION', '_ERROR')] : '';
|
return key ? I18N_DATA.NOTIFICATIONS[i18nKey(key).replace('_NOTIFICATION', '_ERROR')] : '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -176,7 +176,7 @@ export const
|
||||||
*/
|
*/
|
||||||
getNotification = (code, message = '', defCode = 0) => {
|
getNotification = (code, message = '', defCode = 0) => {
|
||||||
code = pInt(code);
|
code = pInt(code);
|
||||||
if (Notification.ClientViewError === code && message) {
|
if (Notifications.ClientViewError === code && message) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,15 @@ export const
|
||||||
item && undefined !== item.disabled && domItem?.classList.toggle('disabled', domItem.disabled = item.disabled),
|
item && undefined !== item.disabled && domItem?.classList.toggle('disabled', domItem.disabled = item.disabled),
|
||||||
|
|
||||||
// unescape(encodeURIComponent()) makes the UTF-16 DOMString to an UTF-8 string
|
// unescape(encodeURIComponent()) makes the UTF-16 DOMString to an UTF-8 string
|
||||||
b64EncodeJSON = data => btoa(unescape(encodeURIComponent(JSON.stringify(data)))),
|
b64Encode = data => btoa(unescape(encodeURIComponent(data))),
|
||||||
/* // Without deprecated 'unescape':
|
/* // Without deprecated 'unescape':
|
||||||
b64EncodeJSON = data => btoa(encodeURIComponent(JSON.stringify(data)).replace(
|
b64Encode = data => btoa(encodeURIComponent(data).replace(
|
||||||
/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode('0x' + p1)
|
/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode('0x' + p1)
|
||||||
)),
|
)),
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
b64EncodeJSON = data => b64Encode(JSON.stringify(data)),
|
||||||
|
|
||||||
b64EncodeJSONSafe = data => b64EncodeJSON(data).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''),
|
b64EncodeJSONSafe = data => b64EncodeJSON(data).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''),
|
||||||
|
|
||||||
getKeyByValue = (o, v) => Object.keys(o).find(key => o[key] === v);
|
getKeyByValue = (o, v) => Object.keys(o).find(key => o[key] === v);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { MessageFlagsCache } from 'Common/Cache';
|
import { Notifications } from 'Common/Enums';
|
||||||
import { Notification } from 'Common/Enums';
|
|
||||||
import { MessageSetAction, ComposeType/*, FolderType*/ } from 'Common/EnumsUser';
|
import { MessageSetAction, ComposeType/*, FolderType*/ } from 'Common/EnumsUser';
|
||||||
import { doc, createElement, elementById, dropdowns, dropdownVisibility, SettingsGet, leftPanelDisabled } from 'Common/Globals';
|
import { doc, createElement, elementById, dropdowns, dropdownVisibility, SettingsGet, leftPanelDisabled } from 'Common/Globals';
|
||||||
import { plainToHtml } from 'Common/Html';
|
import { plainToHtml } from 'Common/Html';
|
||||||
import { getNotification } from 'Common/Translator';
|
import { getNotification } from 'Common/Translator';
|
||||||
import { EmailCollectionModel } from 'Model/EmailCollection';
|
import { EmailCollectionModel } from 'Model/EmailCollection';
|
||||||
import { MessageModel } from 'Model/Message';
|
|
||||||
import { MessageUserStore } from 'Stores/User/Message';
|
import { MessageUserStore } from 'Stores/User/Message';
|
||||||
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
|
|
@ -43,10 +41,11 @@ download = (link, name = "") => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
downloadZip = (hashes, onError, fTrigger, folder) => {
|
downloadZip = (name, hashes, onError, fTrigger, folder) => {
|
||||||
if (hashes.length) {
|
if (hashes.length) {
|
||||||
let params = {
|
let params = {
|
||||||
target: 'zip',
|
target: 'zip',
|
||||||
|
filename: name,
|
||||||
hashes: hashes
|
hashes: hashes
|
||||||
};
|
};
|
||||||
if (!onError) {
|
if (!onError) {
|
||||||
|
|
@ -156,7 +155,7 @@ mailToHelper = mailToUrl => {
|
||||||
mailToUrl = mailToUrl.slice(7).split('?');
|
mailToUrl = mailToUrl.slice(7).split('?');
|
||||||
|
|
||||||
const
|
const
|
||||||
email = mailToUrl[0],
|
email = decodeURIComponent(mailToUrl[0]),
|
||||||
params = new URLSearchParams(mailToUrl[1]),
|
params = new URLSearchParams(mailToUrl[1]),
|
||||||
to = params.get('to'),
|
to = params.get('to'),
|
||||||
toEmailModel = value => EmailCollectionModel.fromString(value);
|
toEmailModel = value => EmailCollectionModel.fromString(value);
|
||||||
|
|
@ -246,20 +245,61 @@ setLayoutResizer = (source, sClientSideKeyName, mode) =>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
viewMessage = (oMessage, popup) => {
|
||||||
|
if (popup) {
|
||||||
|
oMessage.viewPopupMessage();
|
||||||
|
} else {
|
||||||
|
MessageUserStore.error('');
|
||||||
|
let id = 'rl-msg-' + oMessage.hash,
|
||||||
|
body = oMessage.body || elementById(id);
|
||||||
|
if (!body) {
|
||||||
|
body = createElement('div',{
|
||||||
|
id:id,
|
||||||
|
hidden:'',
|
||||||
|
class:'b-text-part'
|
||||||
|
+ (oMessage.pgpSigned() ? ' openpgp-signed' : '')
|
||||||
|
+ (oMessage.pgpEncrypted() ? ' openpgp-encrypted' : '')
|
||||||
|
});
|
||||||
|
MessageUserStore.purgeCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
body.message = oMessage;
|
||||||
|
oMessage.body = body;
|
||||||
|
|
||||||
|
if (!SettingsUserStore.viewHTML() || !oMessage.viewHtml()) {
|
||||||
|
oMessage.viewPlain();
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageUserStore.bodiesDom().append(body);
|
||||||
|
|
||||||
|
MessageUserStore.loading(false);
|
||||||
|
oMessage.body.hidden = false;
|
||||||
|
|
||||||
|
if (oMessage.isUnseen()) {
|
||||||
|
MessageUserStore.MessageSeenTimer = setTimeout(
|
||||||
|
() => MessagelistUserStore.setAction(oMessage.folder, MessageSetAction.SetSeen, [oMessage]),
|
||||||
|
SettingsUserStore.messageReadDelay() * 1000 // seconds
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
populateMessageBody = (oMessage, popup) => {
|
populateMessageBody = (oMessage, popup) => {
|
||||||
if (oMessage) {
|
if (oMessage) {
|
||||||
popup || MessageUserStore.message(oMessage);
|
popup || MessageUserStore.message(oMessage);
|
||||||
|
if (oMessage.body) {
|
||||||
|
viewMessage(oMessage, popup);
|
||||||
|
} else {
|
||||||
popup || MessageUserStore.loading(true);
|
popup || MessageUserStore.loading(true);
|
||||||
Remote.message((iError, oData/*, bCached*/) => {
|
Remote.message((iError, oData/*, bCached*/) => {
|
||||||
if (iError) {
|
if (iError) {
|
||||||
if (Notification.RequestAborted !== iError && !popup) {
|
if (Notifications.RequestAborted !== iError && !popup) {
|
||||||
MessageUserStore.message(null);
|
MessageUserStore.message(null);
|
||||||
MessageUserStore.error(getNotification(iError));
|
MessageUserStore.error(getNotification(iError));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let json = oData?.Result;
|
let json = oData?.Result;
|
||||||
if (json
|
if (json
|
||||||
&& MessageModel.validJson(json)
|
|
||||||
&& oMessage.hash === json.hash
|
&& oMessage.hash === json.hash
|
||||||
// && oMessage.folder === json.folder
|
// && oMessage.folder === json.folder
|
||||||
// && oMessage.uid == json.uid
|
// && oMessage.uid == json.uid
|
||||||
|
|
@ -269,53 +309,15 @@ populateMessageBody = (oMessage, popup) => {
|
||||||
if (bCached) {
|
if (bCached) {
|
||||||
delete json.flags;
|
delete json.flags;
|
||||||
}
|
}
|
||||||
|
oMessage.body.remove();
|
||||||
*/
|
*/
|
||||||
if (popup) {
|
viewMessage(oMessage, popup);
|
||||||
oMessage.viewPopupMessage();
|
|
||||||
} else {
|
|
||||||
MessageUserStore.error('');
|
|
||||||
const messagesDom = MessageUserStore.bodiesDom();
|
|
||||||
if (messagesDom) {
|
|
||||||
let id = 'rl-msg-' + oMessage.hash,
|
|
||||||
body = elementById(id);
|
|
||||||
if (body) {
|
|
||||||
oMessage.body = body;
|
|
||||||
oMessage.isHtml(body.classList.contains('html'));
|
|
||||||
oMessage.hasImages(body.rlHasImages);
|
|
||||||
} else {
|
|
||||||
body = createElement('div',{
|
|
||||||
id:id,
|
|
||||||
hidden:'',
|
|
||||||
class:'b-text-part'
|
|
||||||
+ (oMessage.pgpSigned() ? ' openpgp-signed' : '')
|
|
||||||
+ (oMessage.pgpEncrypted() ? ' openpgp-encrypted' : '')
|
|
||||||
});
|
|
||||||
oMessage.body = body;
|
|
||||||
if (!SettingsUserStore.viewHTML() || !oMessage.viewHtml()) {
|
|
||||||
oMessage.viewPlain();
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageUserStore.purgeMessageBodyCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
messagesDom.append(body);
|
|
||||||
|
|
||||||
oMessage.body.hidden = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageFlagsCache.initMessage(oMessage);
|
|
||||||
if (oMessage.isUnseen()) {
|
|
||||||
MessageUserStore.MessageSeenTimer = setTimeout(
|
|
||||||
() => MessagelistUserStore.setAction(oMessage.folder, MessageSetAction.SetSeen, [oMessage]),
|
|
||||||
SettingsUserStore.messageReadDelay() * 1000 // seconds
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
popup || MessageUserStore.loading(false);
|
popup || MessageUserStore.loading(false);
|
||||||
}, oMessage.folder, oMessage.uid);
|
}, oMessage.folder, oMessage.uid);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
leftPanelDisabled.subscribe(value => value && moveAction(false));
|
leftPanelDisabled.subscribe(value => value && moveAction(false));
|
||||||
|
|
|
||||||
13
dev/External/SquireUI.js
vendored
13
dev/External/SquireUI.js
vendored
|
|
@ -27,11 +27,6 @@ const
|
||||||
|
|
||||||
SquireDefaultConfig = {
|
SquireDefaultConfig = {
|
||||||
/*
|
/*
|
||||||
blockTag: 'P',
|
|
||||||
undo: {
|
|
||||||
documentSizeThreshold: -1, // -1 means no threshold
|
|
||||||
undoLimit: -1 // -1 means no limit
|
|
||||||
},
|
|
||||||
addLinks: true // allow_smart_html_links
|
addLinks: true // allow_smart_html_links
|
||||||
*/
|
*/
|
||||||
sanitizeToDOMFragment: (html, isPaste/*, squire*/) => {
|
sanitizeToDOMFragment: (html, isPaste/*, squire*/) => {
|
||||||
|
|
@ -216,7 +211,7 @@ class SquireUI
|
||||||
cmd: () => {
|
cmd: () => {
|
||||||
let node = squire.getSelectionClosest('IMG'),
|
let node = squire.getSelectionClosest('IMG'),
|
||||||
src = prompt("Image", node?.src || "https://");
|
src = prompt("Image", node?.src || "https://");
|
||||||
src.length ? squire.insertImage(src) : (node && squire.detach(node));
|
src?.length ? squire.insertImage(src) : (node && squire.detach(node));
|
||||||
},
|
},
|
||||||
hint: 'Image URL'
|
hint: 'Image URL'
|
||||||
},
|
},
|
||||||
|
|
@ -288,11 +283,6 @@ class SquireUI
|
||||||
toolbar.className = 'squire-toolbar btn-toolbar';
|
toolbar.className = 'squire-toolbar btn-toolbar';
|
||||||
let group, action/*, touchTap*/;
|
let group, action/*, touchTap*/;
|
||||||
for (group in actions) {
|
for (group in actions) {
|
||||||
/*
|
|
||||||
if ('bidi' == group && !rl.settings.app('allowHtmlEditorBitiButtons')) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
let toolgroup = createElement('div');
|
let toolgroup = createElement('div');
|
||||||
toolgroup.className = 'btn-group';
|
toolgroup.className = 'btn-group';
|
||||||
toolgroup.id = 'squire-toolgroup-'+group;
|
toolgroup.id = 'squire-toolgroup-'+group;
|
||||||
|
|
@ -379,7 +369,6 @@ class SquireUI
|
||||||
squire.addEventListener('cursor', );
|
squire.addEventListener('cursor', );
|
||||||
squire.addEventListener('select', );
|
squire.addEventListener('select', );
|
||||||
squire.addEventListener('input', );
|
squire.addEventListener('input', );
|
||||||
squire.addEventListener('willPaste', );
|
|
||||||
squire.addEventListener( 'keydown keyup', monitorShiftKey )
|
squire.addEventListener( 'keydown keyup', monitorShiftKey )
|
||||||
squire.addEventListener( 'keydown', onKey )
|
squire.addEventListener( 'keydown', onKey )
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
47
dev/External/ko.js
vendored
47
dev/External/ko.js
vendored
|
|
@ -28,6 +28,18 @@ export const
|
||||||
|
|
||||||
dispose = disposable => isFunction(disposable?.dispose) && disposable.dispose(),
|
dispose = disposable => isFunction(disposable?.dispose) && disposable.dispose(),
|
||||||
|
|
||||||
|
onKey = (key, element, fValueAccessor, fAllBindings, model) => {
|
||||||
|
let fn = event => {
|
||||||
|
if (key == event.key) {
|
||||||
|
// stopEvent(event);
|
||||||
|
// element.dispatchEvent(new Event('change'));
|
||||||
|
fValueAccessor().call(model);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
element.addEventListener('keydown', fn);
|
||||||
|
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keydown', fn));
|
||||||
|
},
|
||||||
|
|
||||||
// With this we don't need delegateRunOnDestroy
|
// With this we don't need delegateRunOnDestroy
|
||||||
koArrayWithDestroy = data => {
|
koArrayWithDestroy = data => {
|
||||||
data = ko.observableArray(data);
|
data = ko.observableArray(data);
|
||||||
|
|
@ -56,41 +68,18 @@ Object.assign(ko.bindingHandlers, {
|
||||||
},
|
},
|
||||||
|
|
||||||
onEnter: {
|
onEnter: {
|
||||||
init: (element, fValueAccessor, fAllBindings, viewModel) => {
|
init: (element, fValueAccessor, fAllBindings, model) =>
|
||||||
let fn = event => {
|
onKey('Enter', element, fValueAccessor, fAllBindings, model)
|
||||||
if ('Enter' == event.key) {
|
|
||||||
element.dispatchEvent(new Event('change'));
|
|
||||||
fValueAccessor().call(viewModel);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
element.addEventListener('keydown', fn);
|
|
||||||
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keydown', fn));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onEsc: {
|
onEsc: {
|
||||||
init: (element, fValueAccessor, fAllBindings, viewModel) => {
|
init: (element, fValueAccessor, fAllBindings, model) =>
|
||||||
let fn = event => {
|
onKey('Escape', element, fValueAccessor, fAllBindings, model)
|
||||||
if ('Escape' == event.key) {
|
|
||||||
element.dispatchEvent(new Event('change'));
|
|
||||||
fValueAccessor().call(viewModel);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
element.addEventListener('keyup', fn);
|
|
||||||
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keyup', fn));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
onSpace: {
|
onSpace: {
|
||||||
init: (element, fValueAccessor, fAllBindings, viewModel) => {
|
init: (element, fValueAccessor, fAllBindings, model) =>
|
||||||
let fn = event => {
|
onKey(' ', element, fValueAccessor, fAllBindings, model)
|
||||||
if (' ' == event.key) {
|
|
||||||
fValueAccessor().call(viewModel, event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
element.addEventListener('keyup', fn);
|
|
||||||
ko.utils.domNodeDisposal.addDisposeCallback(element, () => element.removeEventListener('keyup', fn));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
i18nUpdate: {
|
i18nUpdate: {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { doc, $htmlCL, elementById, createElement, fireEvent } from 'Common/Glob
|
||||||
import { forEachObjectEntry } from 'Common/Utils';
|
import { forEachObjectEntry } from 'Common/Utils';
|
||||||
import { i18nToNodes } from 'Common/Translator';
|
import { i18nToNodes } from 'Common/Translator';
|
||||||
|
|
||||||
|
import { leftPanelDisabled } from 'Common/Globals';
|
||||||
|
import { ThemeStore } from 'Stores/Theme';
|
||||||
|
|
||||||
let
|
let
|
||||||
currentScreen = null,
|
currentScreen = null,
|
||||||
defaultScreenName = '';
|
defaultScreenName = '';
|
||||||
|
|
@ -151,6 +154,7 @@ const
|
||||||
vm.onHide?.();
|
vm.onHide?.();
|
||||||
destroy && vm.viewModelDom.remove();
|
destroy && vm.viewModelDom.remove();
|
||||||
});
|
});
|
||||||
|
ThemeStore.isMobile() && leftPanelDisabled(true);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//import { b64Encode } from 'Common/Utils';
|
||||||
|
|
||||||
const
|
const
|
||||||
// RFC2045
|
// RFC2045
|
||||||
|
|
@ -68,6 +69,7 @@ export function ParseMime(text)
|
||||||
body = QPDecode(body);
|
body = QPDecode(body);
|
||||||
}
|
}
|
||||||
body = btoa(body);
|
body = btoa(body);
|
||||||
|
// body = b64Encode(body);
|
||||||
}
|
}
|
||||||
return 'data:' + this.headerValue('content-type') + ';base64,' + body;
|
return 'data:' + this.headerValue('content-type') + ';base64,' + body;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,9 +35,7 @@ export function MimeToMessage(data, message)
|
||||||
attachment.url = part.dataUrl;
|
attachment.url = part.dataUrl;
|
||||||
attachment.estimatedSize = part.body.length;
|
attachment.estimatedSize = part.body.length;
|
||||||
/*
|
/*
|
||||||
attachment.isThumbnail = false;
|
|
||||||
attachment.contentLocation = '';
|
attachment.contentLocation = '';
|
||||||
attachment.download = '';
|
|
||||||
attachment.folder = '';
|
attachment.folder = '';
|
||||||
attachment.uid = '';
|
attachment.uid = '';
|
||||||
attachment.mimeIndex = part.id;
|
attachment.mimeIndex = part.id;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import { FileInfo, FileType } from 'Common/File';
|
import { FileInfo, FileType } from 'Common/File';
|
||||||
|
import { stopEvent, SettingsGet, SettingsCapa } from 'Common/Globals';
|
||||||
|
import { b64EncodeJSONSafe } from 'Common/Utils';
|
||||||
import {
|
import {
|
||||||
attachmentDownload,
|
attachmentDownload,
|
||||||
serverRequestRaw
|
serverRequestRaw
|
||||||
|
|
@ -22,10 +24,8 @@ export class AttachmentModel extends AbstractModel {
|
||||||
this.fileName = '';
|
this.fileName = '';
|
||||||
this.fileNameExt = '';
|
this.fileNameExt = '';
|
||||||
this.fileType = FileType.Unknown;
|
this.fileType = FileType.Unknown;
|
||||||
this.isThumbnail = false;
|
|
||||||
this.cId = '';
|
this.cId = '';
|
||||||
this.contentLocation = '';
|
this.contentLocation = '';
|
||||||
this.download = '';
|
|
||||||
this.folder = '';
|
this.folder = '';
|
||||||
this.uid = '';
|
this.uid = '';
|
||||||
this.url = '';
|
this.url = '';
|
||||||
|
|
@ -53,7 +53,7 @@ export class AttachmentModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleChecked(self, event) {
|
toggleChecked(self, event) {
|
||||||
event.stopPropagation();
|
stopEvent(event);
|
||||||
self.checked(!self.checked());
|
self.checked(!self.checked());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,6 +125,17 @@ export class AttachmentModel extends AbstractModel {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get download() {
|
||||||
|
return b64EncodeJSONSafe({
|
||||||
|
folder: this.folder,
|
||||||
|
uid: this.uid,
|
||||||
|
mimeIndex: this.mimeIndex,
|
||||||
|
mimeType: this.mimeType,
|
||||||
|
fileName: this.fileName,
|
||||||
|
accountHash: SettingsGet('accountHash')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
|
|
@ -143,7 +154,7 @@ export class AttachmentModel extends AbstractModel {
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
hasThumbnail() {
|
hasThumbnail() {
|
||||||
return this.isThumbnail && !this.isLinked();
|
return SettingsCapa('AttachmentThumbnails') && this.isImage() && !this.isLinked();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ import { AbstractCollectionModel } from 'Model/AbstractCollection';
|
||||||
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
||||||
import { isArray, getKeyByValue, forEachObjectEntry, b64EncodeJSONSafe } from 'Common/Utils';
|
import { isArray, getKeyByValue, forEachObjectEntry, b64EncodeJSONSafe } from 'Common/Utils';
|
||||||
import { ClientSideKeyNameExpandedFolders, FolderType, FolderMetadataKeys } from 'Common/EnumsUser';
|
import { ClientSideKeyNameExpandedFolders, FolderType, FolderMetadataKeys } from 'Common/EnumsUser';
|
||||||
import { getFolderFromCacheList, setFolder, setFolderInboxName } from 'Common/Cache';
|
import { clearCache, getFolderFromCacheList, setFolder, setFolderInboxName, removeFolderFromCacheList } from 'Common/Cache';
|
||||||
import { Settings, SettingsGet, fireEvent } from 'Common/Globals';
|
import { Settings, SettingsGet, fireEvent } from 'Common/Globals';
|
||||||
|
import { Notifications } from 'Common/Enums';
|
||||||
|
|
||||||
import * as Local from 'Storage/Client';
|
import * as Local from 'Storage/Client';
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
|
|
||||||
import { sortFolders } from 'Common/Folders';
|
import { sortFolders } from 'Common/Folders';
|
||||||
import { i18n, translateTrigger } from 'Common/Translator';
|
import { i18n, translateTrigger, getNotification } from 'Common/Translator';
|
||||||
|
|
||||||
import { AbstractModel } from 'Knoin/AbstractModel';
|
import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
|
|
||||||
|
|
@ -91,6 +92,7 @@ export const
|
||||||
Remote.abort('Folders')
|
Remote.abort('Folders')
|
||||||
.post('Folders', FolderUserStore.foldersLoading)
|
.post('Folders', FolderUserStore.foldersLoading)
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
clearCache();
|
||||||
FolderCollectionModel.reviveFromJson(data.Result)?.storeIt();
|
FolderCollectionModel.reviveFromJson(data.Result)?.storeIt();
|
||||||
fCallback?.(true);
|
fCallback?.(true);
|
||||||
// Repeat every 15 minutes?
|
// Repeat every 15 minutes?
|
||||||
|
|
@ -127,8 +129,8 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
||||||
let oCacheFolder = getFolderFromCacheList(oFolder.fullName);
|
let oCacheFolder = getFolderFromCacheList(oFolder.fullName);
|
||||||
if (oCacheFolder) {
|
if (oCacheFolder) {
|
||||||
// oCacheFolder.revivePropertiesFromJson(oFolder);
|
// oCacheFolder.revivePropertiesFromJson(oFolder);
|
||||||
if (oFolder.hash) {
|
if (oFolder.etag) {
|
||||||
oCacheFolder.hash = oFolder.hash;
|
oCacheFolder.etag = oFolder.etag;
|
||||||
}
|
}
|
||||||
if (null != oFolder.totalEmails) {
|
if (null != oFolder.totalEmails) {
|
||||||
oCacheFolder.totalEmails(oFolder.totalEmails);
|
oCacheFolder.totalEmails(oFolder.totalEmails);
|
||||||
|
|
@ -167,22 +169,22 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// Flags
|
// Flags
|
||||||
if (oFolder.flags.includes('\\sentmail')) {
|
if (oFolder.attributes.includes('\\sentmail')) {
|
||||||
role = 'sent';
|
role = 'sent';
|
||||||
}
|
}
|
||||||
if (oFolder.flags.includes('\\spam')) {
|
if (oFolder.attributes.includes('\\spam')) {
|
||||||
role = 'junk';
|
role = 'junk';
|
||||||
}
|
}
|
||||||
if (oFolder.flags.includes('\\bin')) {
|
if (oFolder.attributes.includes('\\bin')) {
|
||||||
role = 'trash';
|
role = 'trash';
|
||||||
}
|
}
|
||||||
if (oFolder.flags.includes('\\important')) {
|
if (oFolder.attributes.includes('\\important')) {
|
||||||
role = 'important';
|
role = 'important';
|
||||||
}
|
}
|
||||||
if (oFolder.flags.includes('\\starred')) {
|
if (oFolder.attributes.includes('\\starred')) {
|
||||||
role = 'flagged';
|
role = 'flagged';
|
||||||
}
|
}
|
||||||
if (oFolder.flags.includes('\\all') || oFolder.flags.includes('\\allmail')) {
|
if (oFolder.attributes.includes('\\all') || oFolder.flags.includes('\\allmail')) {
|
||||||
role = 'all';
|
role = 'all';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -226,9 +228,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
||||||
name: name,
|
name: name,
|
||||||
fullName: parentName,
|
fullName: parentName,
|
||||||
delimiter: delimiter,
|
delimiter: delimiter,
|
||||||
exists: false,
|
attributes: ['\\nonexistent']
|
||||||
isSubscribed: false,
|
|
||||||
flags: ['\\nonexistent']
|
|
||||||
});
|
});
|
||||||
setFolder(pfolder);
|
setFolder(pfolder);
|
||||||
result.splice(i, 0, pfolder);
|
result.splice(i, 0, pfolder);
|
||||||
|
|
@ -300,7 +300,7 @@ export class FolderModel extends AbstractModel {
|
||||||
|
|
||||||
this.exists = true;
|
this.exists = true;
|
||||||
|
|
||||||
this.hash = '';
|
this.etag = '';
|
||||||
this.id = 0;
|
this.id = 0;
|
||||||
this.uidNext = 0;
|
this.uidNext = 0;
|
||||||
|
|
||||||
|
|
@ -330,7 +330,8 @@ export class FolderModel extends AbstractModel {
|
||||||
tagsAllowed: false
|
tagsAllowed: false
|
||||||
});
|
});
|
||||||
|
|
||||||
this.flags = ko.observableArray();
|
this.attributes = ko.observableArray();
|
||||||
|
// For messages
|
||||||
this.permanentFlags = ko.observableArray();
|
this.permanentFlags = ko.observableArray();
|
||||||
|
|
||||||
this.addSubscribables({
|
this.addSubscribables({
|
||||||
|
|
@ -379,6 +380,8 @@ export class FolderModel extends AbstractModel {
|
||||||
isFlagged: () => FolderUserStore.currentFolder() === this
|
isFlagged: () => FolderUserStore.currentFolder() === this
|
||||||
&& MessagelistUserStore.listSearch().includes('flagged'),
|
&& MessagelistUserStore.listSearch().includes('flagged'),
|
||||||
|
|
||||||
|
// isSubscribed: () => this.attributes().includes('\\subscribed'),
|
||||||
|
|
||||||
hasVisibleSubfolders: () => !!this.subFolders().find(folder => folder.visible()),
|
hasVisibleSubfolders: () => !!this.subFolders().find(folder => folder.visible()),
|
||||||
|
|
||||||
hasSubscriptions: () => this.isSubscribed() | !!this.subFolders().find(
|
hasSubscriptions: () => this.isSubscribed() | !!this.subFolders().find(
|
||||||
|
|
@ -465,6 +468,49 @@ export class FolderModel extends AbstractModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
edit() {
|
||||||
|
this.canBeEdited() && this.editing(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
unedit() {
|
||||||
|
this.editing(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
rename() {
|
||||||
|
const folder = this,
|
||||||
|
nameToEdit = folder.nameForEdit().trim();
|
||||||
|
if (nameToEdit && folder.name() !== nameToEdit) {
|
||||||
|
Remote.abort('Folders').post('FolderRename', FolderUserStore.foldersRenaming, {
|
||||||
|
folder: folder.fullName,
|
||||||
|
newFolderName: nameToEdit,
|
||||||
|
subscribe: folder.isSubscribed() ? 1 : 0
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
folder.name(nameToEdit/*data.name*/);
|
||||||
|
if (folder.subFolders.length) {
|
||||||
|
Remote.setTrigger(FolderUserStore.foldersLoading, true);
|
||||||
|
// clearTimeout(Remote.foldersTimeout);
|
||||||
|
// Remote.foldersTimeout = setTimeout(loadFolders, 500);
|
||||||
|
setTimeout(loadFolders, 500);
|
||||||
|
// TODO: rename all subfolders with folder.delimiter to prevent reload?
|
||||||
|
} else {
|
||||||
|
removeFolderFromCacheList(folder.fullName);
|
||||||
|
folder.fullName = data.Result.fullName;
|
||||||
|
setFolder(folder);
|
||||||
|
const parent = getFolderFromCacheList(folder.parentName);
|
||||||
|
sortFolders(parent ? parent.subFolders : FolderUserStore.folderList);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
FolderUserStore.folderListError(
|
||||||
|
getNotification(error.code, '', Notifications.CantRenameFolder)
|
||||||
|
+ '.\n' + error.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
folder.editing(false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For url safe '/#/mailbox/...' path
|
* For url safe '/#/mailbox/...' path
|
||||||
*/
|
*/
|
||||||
|
|
@ -491,6 +537,10 @@ export class FolderModel extends AbstractModel {
|
||||||
path.pop();
|
path.pop();
|
||||||
folder.parentName = path.join(folder.delimiter);
|
folder.parentName = path.join(folder.delimiter);
|
||||||
|
|
||||||
|
folder.isSubscribed(folder.attributes.includes('\\subscribed'));
|
||||||
|
folder.exists = !folder.attributes.includes('\\nonexistent');
|
||||||
|
folder.selectable(folder.exists && !folder.attributes.includes('\\noselect'));
|
||||||
|
|
||||||
type && 'mail' != type && folder.kolabType(type);
|
type && 'mail' != type && folder.kolabType(type);
|
||||||
}
|
}
|
||||||
return folder;
|
return folder;
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,12 @@ export class IdentityModel extends AbstractModel {
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* @param {string} email
|
* @param {string} email
|
||||||
*/
|
*/
|
||||||
constructor(id, email) {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
addObservablesTo(this, {
|
addObservablesTo(this, {
|
||||||
id: id || '',
|
id: '',
|
||||||
email: email,
|
email: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
|
||||||
replyTo: '',
|
replyTo: '',
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { i18n } from 'Common/Translator';
|
||||||
|
|
||||||
import { doc, SettingsGet } from 'Common/Globals';
|
import { doc, SettingsGet } from 'Common/Globals';
|
||||||
import { encodeHtml, plainToHtml, htmlToPlain, cleanHtml } from 'Common/Html';
|
import { encodeHtml, plainToHtml, htmlToPlain, cleanHtml } from 'Common/Html';
|
||||||
import { forEachObjectEntry } from 'Common/Utils';
|
import { forEachObjectEntry, b64EncodeJSONSafe } from 'Common/Utils';
|
||||||
import { serverRequestRaw, proxy } from 'Common/Links';
|
import { serverRequestRaw, proxy } from 'Common/Links';
|
||||||
import { addObservablesTo, addComputablesTo } from 'External/ko';
|
import { addObservablesTo, addComputablesTo } from 'External/ko';
|
||||||
|
|
||||||
|
|
@ -21,7 +21,6 @@ import PreviewHTML from 'Html/PreviewMessage.html';
|
||||||
|
|
||||||
import { LanguageStore } from 'Stores/Language';
|
import { LanguageStore } from 'Stores/Language';
|
||||||
|
|
||||||
//import { MessageFlagsCache } from 'Common/Cache';
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
@ -34,7 +33,6 @@ const
|
||||||
Remote.request('MessageSetKeyword', iError => {
|
Remote.request('MessageSetKeyword', iError => {
|
||||||
if (!iError) {
|
if (!iError) {
|
||||||
isSet ? flags.remove(lower) : flags.push(lower);
|
isSet ? flags.remove(lower) : flags.push(lower);
|
||||||
// MessageFlagsCache.setFor(message.folder, message.uid, flags());
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
folder: message.folder,
|
folder: message.folder,
|
||||||
|
|
@ -61,7 +59,6 @@ export class MessageModel extends AbstractModel {
|
||||||
this.folder = '';
|
this.folder = '';
|
||||||
this.uid = 0;
|
this.uid = 0;
|
||||||
this.hash = '';
|
this.hash = '';
|
||||||
this.requestHash = '';
|
|
||||||
this.from = new EmailCollectionModel;
|
this.from = new EmailCollectionModel;
|
||||||
this.to = new EmailCollectionModel;
|
this.to = new EmailCollectionModel;
|
||||||
this.cc = new EmailCollectionModel;
|
this.cc = new EmailCollectionModel;
|
||||||
|
|
@ -88,7 +85,8 @@ export class MessageModel extends AbstractModel {
|
||||||
spamResult: '',
|
spamResult: '',
|
||||||
isSpam: false,
|
isSpam: false,
|
||||||
hasVirus: null, // or boolean when scanned
|
hasVirus: null, // or boolean when scanned
|
||||||
dateTimeStampInUTC: 0,
|
dateTimestamp: 0,
|
||||||
|
internalTimestamp: 0,
|
||||||
priority: MessagePriority.Normal,
|
priority: MessagePriority.Normal,
|
||||||
|
|
||||||
senderEmailsString: '',
|
senderEmailsString: '',
|
||||||
|
|
@ -180,6 +178,16 @@ export class MessageModel extends AbstractModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get requestHash() {
|
||||||
|
return b64EncodeJSONSafe({
|
||||||
|
folder: this.folder,
|
||||||
|
uid: this.uid,
|
||||||
|
mimeType: 'message/rfc822',
|
||||||
|
fileName: (this.subject() || 'message-' + this.hash) + '.eml',
|
||||||
|
accountHash: SettingsGet('accountHash')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
toggleTag(keyword) {
|
toggleTag(keyword) {
|
||||||
toggleTag(this, keyword);
|
toggleTag(this, keyword);
|
||||||
}
|
}
|
||||||
|
|
@ -290,18 +298,14 @@ export class MessageModel extends AbstractModel {
|
||||||
return [[...toResult.values()], [...ccResult.values()]];
|
return [[...toResult.values()], [...ccResult.values()]];
|
||||||
}
|
}
|
||||||
|
|
||||||
viewHtml() {
|
viewBody(html) {
|
||||||
const body = this.body;
|
const body = this.body;
|
||||||
if (body && this.html()) {
|
if (body) {
|
||||||
|
if (html) {
|
||||||
let result = msgHtml(this);
|
let result = msgHtml(this);
|
||||||
this.hasExternals(result.hasExternals);
|
this.hasExternals(result.hasExternals);
|
||||||
this.hasImages(body.rlHasImages = !!result.hasExternals);
|
this.hasImages(!!result.hasExternals);
|
||||||
|
|
||||||
body.innerHTML = result.html;
|
body.innerHTML = result.html;
|
||||||
|
|
||||||
body.classList.toggle('html', 1);
|
|
||||||
body.classList.toggle('plain', 0);
|
|
||||||
|
|
||||||
if (!this.isSpam() && FolderUserStore.spamFolder() != this.folder) {
|
if (!this.isSpam() && FolderUserStore.spamFolder() != this.folder) {
|
||||||
if ('always' === SettingsUserStore.viewImages()) {
|
if ('always' === SettingsUserStore.viewImages()) {
|
||||||
this.showExternalImages();
|
this.showExternalImages();
|
||||||
|
|
@ -310,34 +314,35 @@ export class MessageModel extends AbstractModel {
|
||||||
this.showExternalImages(1);
|
this.showExternalImages(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
this.isHtml(true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewPlain() {
|
|
||||||
const body = this.body;
|
|
||||||
if (body) {
|
|
||||||
body.classList.toggle('html', 0);
|
|
||||||
body.classList.toggle('plain', 1);
|
|
||||||
body.innerHTML = plainToHtml(
|
body.innerHTML = plainToHtml(
|
||||||
(this.plain()
|
(this.plain()
|
||||||
? this.plain()
|
? this.plain()
|
||||||
.replace(/-----BEGIN PGP (SIGNED MESSAGE-----(\r?\n[a-z][^\r\n]+)+|SIGNATURE-----[\s\S]*)/, '')
|
.replace(/-----BEGIN PGP (SIGNED MESSAGE-----(\r?\n[^\r\n]+)+|SIGNATURE-----[\s\S]*)/sg, '')
|
||||||
.trim()
|
.trim()
|
||||||
: htmlToPlain(body.innerHTML)
|
: htmlToPlain(body.innerHTML)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
this.isHtml(false);
|
|
||||||
this.hasImages(false);
|
this.hasImages(false);
|
||||||
|
}
|
||||||
|
body.classList.toggle('html', html);
|
||||||
|
body.classList.toggle('plain', !html);
|
||||||
|
this.isHtml(html);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewHtml() {
|
||||||
|
return this.html() && this.viewBody(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
viewPlain() {
|
||||||
|
return this.viewBody(false);
|
||||||
|
}
|
||||||
|
|
||||||
viewPopupMessage(print) {
|
viewPopupMessage(print) {
|
||||||
const
|
const
|
||||||
timeStampInUTC = this.dateTimeStampInUTC() || 0,
|
timeStampInUTC = this.dateTimestamp() || 0,
|
||||||
ccLine = this.cc.toString(),
|
ccLine = this.cc.toString(),
|
||||||
bccLine = this.bcc.toString(),
|
bccLine = this.bcc.toString(),
|
||||||
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
|
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
|
||||||
|
|
@ -430,7 +435,7 @@ export class MessageModel extends AbstractModel {
|
||||||
hasImages = true;
|
hasImages = true;
|
||||||
},
|
},
|
||||||
attr = 'data-x-src',
|
attr = 'data-x-src',
|
||||||
src, useProxy = !!SettingsGet('UseLocalProxyForExternalImages');
|
src, useProxy = !!SettingsGet('useLocalProxyForExternalImages');
|
||||||
body.querySelectorAll('img[' + attr + ']').forEach(node => {
|
body.querySelectorAll('img[' + attr + ']').forEach(node => {
|
||||||
src = node.getAttribute(attr);
|
src = node.getAttribute(attr);
|
||||||
if (isValid(src)) {
|
if (isValid(src)) {
|
||||||
|
|
@ -447,7 +452,6 @@ export class MessageModel extends AbstractModel {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.hasImages(hasImages);
|
this.hasImages(hasImages);
|
||||||
body.rlHasImages = hasImages;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,6 @@ import { AbstractCollectionModel } from 'Model/AbstractCollection';
|
||||||
import { MessageModel } from 'Model/Message';
|
import { MessageModel } from 'Model/Message';
|
||||||
import { MessageUserStore } from 'Stores/User/Message';
|
import { MessageUserStore } from 'Stores/User/Message';
|
||||||
|
|
||||||
import {
|
|
||||||
MessageFlagsCache
|
|
||||||
} from 'Common/Cache';
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export class MessageCollectionModel extends AbstractCollectionModel
|
export class MessageCollectionModel extends AbstractCollectionModel
|
||||||
|
|
@ -13,17 +9,16 @@ export class MessageCollectionModel extends AbstractCollectionModel
|
||||||
/*
|
/*
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.Filtered
|
this.filtered
|
||||||
this.folder
|
this.folder
|
||||||
this.folderHash
|
|
||||||
this.folderInfo
|
|
||||||
this.totalEmails
|
this.totalEmails
|
||||||
this.unreadEmails
|
this.totalThreads
|
||||||
this.threadUid
|
this.threadUid
|
||||||
this.newMessages
|
this.newMessages
|
||||||
this.offset
|
this.offset
|
||||||
this.limit
|
this.limit
|
||||||
this.search
|
this.search
|
||||||
|
this.limited
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -31,7 +26,7 @@ export class MessageCollectionModel extends AbstractCollectionModel
|
||||||
* @param {?Object} json
|
* @param {?Object} json
|
||||||
* @returns {MessageCollectionModel}
|
* @returns {MessageCollectionModel}
|
||||||
*/
|
*/
|
||||||
static reviveFromJson(object, cached) {
|
static reviveFromJson(object/*, cached*/) {
|
||||||
let msg = MessageUserStore.message();
|
let msg = MessageUserStore.message();
|
||||||
return super.reviveFromJson(object, message => {
|
return super.reviveFromJson(object, message => {
|
||||||
// If message is currently viewed, use that.
|
// If message is currently viewed, use that.
|
||||||
|
|
@ -39,7 +34,6 @@ export class MessageCollectionModel extends AbstractCollectionModel
|
||||||
message = (msg && msg.hash === message.hash) ? msg : MessageModel.reviveFromJson(message);
|
message = (msg && msg.hash === message.hash) ? msg : MessageModel.reviveFromJson(message);
|
||||||
if (message) {
|
if (message) {
|
||||||
message.deleted(false);
|
message.deleted(false);
|
||||||
cached ? MessageFlagsCache.initMessage(message) : MessageFlagsCache.store(message);
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Notification } from 'Common/Enums';
|
import { Notifications } from 'Common/Enums';
|
||||||
import { isArray, pInt, pString } from 'Common/Utils';
|
import { isArray, pInt, pString } from 'Common/Utils';
|
||||||
import { serverRequest } from 'Common/Links';
|
import { serverRequest } from 'Common/Links';
|
||||||
import { getNotification } from 'Common/Translator';
|
import { getNotification } from 'Common/Translator';
|
||||||
|
|
@ -9,18 +9,18 @@ const getURL = (add = '') => serverRequest('Json') + pString(add),
|
||||||
|
|
||||||
checkResponseError = data => {
|
checkResponseError = data => {
|
||||||
const err = data ? data.ErrorCode : null;
|
const err = data ? data.ErrorCode : null;
|
||||||
if (Notification.InvalidToken === err) {
|
if (Notifications.InvalidToken === err) {
|
||||||
console.error(getNotification(err));
|
console.error(getNotification(err));
|
||||||
// alert(getNotification(err));
|
// alert(getNotification(err));
|
||||||
rl.logoutReload();
|
rl.logoutReload();
|
||||||
} else if ([
|
} else if ([
|
||||||
Notification.AuthError,
|
Notifications.AuthError,
|
||||||
Notification.ConnectionError,
|
Notifications.ConnectionError,
|
||||||
Notification.DomainNotAllowed,
|
Notifications.DomainNotAllowed,
|
||||||
Notification.AccountNotAllowed,
|
Notifications.AccountNotAllowed,
|
||||||
Notification.MailServerError,
|
Notifications.MailServerError,
|
||||||
Notification.UnknownNotification,
|
Notifications.UnknownNotification,
|
||||||
Notification.UnknownError
|
Notifications.UnknownError
|
||||||
].includes(err)
|
].includes(err)
|
||||||
) {
|
) {
|
||||||
if (7 < ++iJsonErrorCount) {
|
if (7 < ++iJsonErrorCount) {
|
||||||
|
|
@ -65,7 +65,7 @@ class FetchError extends Error
|
||||||
{
|
{
|
||||||
constructor(code, message) {
|
constructor(code, message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.code = code || Notification.JsonFalse;
|
this.code = code || Notifications.JsonFalse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,7 +145,7 @@ export class AbstractFetchRemote
|
||||||
iJsonErrorCount = 0;
|
iJsonErrorCount = 0;
|
||||||
} else {
|
} else {
|
||||||
checkResponseError(data);
|
checkResponseError(data);
|
||||||
iError = data.ErrorCode || Notification.UnknownError
|
iError = data.ErrorCode || Notifications.UnknownError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,7 +197,7 @@ export class AbstractFetchRemote
|
||||||
abort(action, 0, 1);
|
abort(action, 0, 1);
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return Promise.reject(new FetchError(Notification.JsonParse));
|
return Promise.reject(new FetchError(Notifications.JsonParse));
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
let isCached = false, type = '';
|
let isCached = false, type = '';
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
||||||
const
|
const
|
||||||
// folder = getFolderFromCacheList(params.folder.fullName),
|
// folder = getFolderFromCacheList(params.folder.fullName),
|
||||||
folder = getFolderFromCacheList(params.folder),
|
folder = getFolderFromCacheList(params.folder),
|
||||||
folderHash = folder?.hash || '';
|
folderETag = folder?.etag || '';
|
||||||
|
|
||||||
params = Object.assign({
|
params = Object.assign({
|
||||||
offset: 0,
|
offset: 0,
|
||||||
|
|
@ -40,8 +40,8 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
||||||
}
|
}
|
||||||
|
|
||||||
let sGetAdd = '';
|
let sGetAdd = '';
|
||||||
if (folderHash) {
|
if (folderETag) {
|
||||||
params.hash = folderHash + '-' + SettingsGet('AccountHash');
|
params.hash = folderETag + '-' + SettingsGet('accountHash');
|
||||||
sGetAdd = 'MessageList/' + SUB_QUERY_PREFIX + '/' + b64EncodeJSONSafe(params);
|
sGetAdd = 'MessageList/' + SUB_QUERY_PREFIX + '/' + b64EncodeJSONSafe(params);
|
||||||
params = {};
|
params = {};
|
||||||
}
|
}
|
||||||
|
|
@ -77,7 +77,7 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
||||||
sFolderFullName,
|
sFolderFullName,
|
||||||
iUid,
|
iUid,
|
||||||
AppUserStore.threadsAllowed() && SettingsUserStore.useThreads() ? 1 : 0,
|
AppUserStore.threadsAllowed() && SettingsUserStore.useThreads() ? 1 : 0,
|
||||||
SettingsGet('AccountHash')
|
SettingsGet('accountHash')
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import ko from 'ko';
|
||||||
|
|
||||||
import { pString } from 'Common/Utils';
|
import { pString } from 'Common/Utils';
|
||||||
import { settings } from 'Common/Links';
|
import { settings } from 'Common/Links';
|
||||||
import { createElement, elementById } from 'Common/Globals';
|
import { createElement/*, elementById*/ } from 'Common/Globals';
|
||||||
|
|
||||||
import { AbstractScreen } from 'Knoin/AbstractScreen';
|
import { AbstractScreen } from 'Knoin/AbstractScreen';
|
||||||
import { i18nToNodes } from 'Common/Translator';
|
import { i18nToNodes } from 'Common/Translator';
|
||||||
|
|
@ -29,7 +29,8 @@ export class AbstractSettingsScreen extends AbstractScreen {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (RoutedSettingsViewModel) {
|
if (RoutedSettingsViewModel) {
|
||||||
const vmPlace = elementById('rl-settings-subscreen');
|
// const vmPlace = elementById('V-SettingsPane') || elementById('V-AdminPane);
|
||||||
|
const vmPlace = this.viewModels[1].__dom;
|
||||||
if (RoutedSettingsViewModel.__vm) {
|
if (RoutedSettingsViewModel.__vm) {
|
||||||
settingsScreen = RoutedSettingsViewModel.__vm;
|
settingsScreen = RoutedSettingsViewModel.__vm;
|
||||||
} else if (vmPlace) {
|
} else if (vmPlace) {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,14 @@
|
||||||
import { Scope } from 'Common/Enums';
|
import { Scope } from 'Common/Enums';
|
||||||
import { doc, createElement, leftPanelDisabled, Settings } from 'Common/Globals';
|
import { doc, createElement, Settings } from 'Common/Globals';
|
||||||
import { pString, pInt } from 'Common/Utils';
|
import { pString, pInt } from 'Common/Utils';
|
||||||
import { moveAction } from 'Common/UtilsUser';
|
import { moveAction } from 'Common/UtilsUser';
|
||||||
import { getFolderFromCacheList, getFolderFullName, getFolderInboxName } from 'Common/Cache';
|
import { getFolderFromHashMap, getFolderInboxName } from 'Common/Cache';
|
||||||
import { i18n, initOnStartOrLangChange } from 'Common/Translator';
|
import { i18n, initOnStartOrLangChange } from 'Common/Translator';
|
||||||
|
|
||||||
import { AppUserStore } from 'Stores/User/App';
|
import { AppUserStore } from 'Stores/User/App';
|
||||||
import { AccountUserStore } from 'Stores/User/Account';
|
import { AccountUserStore } from 'Stores/User/Account';
|
||||||
import { FolderUserStore } from 'Stores/User/Folder';
|
import { FolderUserStore } from 'Stores/User/Folder';
|
||||||
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||||
import { ThemeStore } from 'Stores/Theme';
|
|
||||||
|
|
||||||
import { SystemDropDownUserView } from 'View/User/SystemDropDown';
|
import { SystemDropDownUserView } from 'View/User/SystemDropDown';
|
||||||
import { MailFolderList } from 'View/User/MailBox/FolderList';
|
import { MailFolderList } from 'View/User/MailBox/FolderList';
|
||||||
|
|
@ -57,11 +56,8 @@ export class MailBoxUserScreen extends AbstractScreen {
|
||||||
*/
|
*/
|
||||||
onShow() {
|
onShow() {
|
||||||
this.updateWindowTitle();
|
this.updateWindowTitle();
|
||||||
|
|
||||||
AppUserStore.focusedState('none');
|
AppUserStore.focusedState('none');
|
||||||
AppUserStore.focusedState(Scope.MessageList);
|
AppUserStore.focusedState(Scope.MessageList);
|
||||||
|
|
||||||
ThemeStore.isMobile() && leftPanelDisabled(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -71,7 +67,7 @@ export class MailBoxUserScreen extends AbstractScreen {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
onRoute(folderHash, page, search, messageUid) {
|
onRoute(folderHash, page, search, messageUid) {
|
||||||
const folder = getFolderFromCacheList(getFolderFullName(folderHash.replace(/~([\d]+)$/, '')));
|
const folder = getFolderFromHashMap(folderHash.replace(/~([\d]+)$/, ''));
|
||||||
if (folder) {
|
if (folder) {
|
||||||
FolderUserStore.currentFolder(folder);
|
FolderUserStore.currentFolder(folder);
|
||||||
MessagelistUserStore.page(1 > page ? 1 : page);
|
MessagelistUserStore.page(1 > page ? 1 : page);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
import { Scope } from 'Common/Enums';
|
import { Scope } from 'Common/Enums';
|
||||||
import { keyScope, leftPanelDisabled, SettingsCapa } from 'Common/Globals';
|
import { keyScope, SettingsCapa } from 'Common/Globals';
|
||||||
import { runSettingsViewModelHooks } from 'Common/Plugins';
|
import { runSettingsViewModelHooks } from 'Common/Plugins';
|
||||||
import { initOnStartOrLangChange, i18n } from 'Common/Translator';
|
import { initOnStartOrLangChange, i18n } from 'Common/Translator';
|
||||||
|
|
||||||
import { AppUserStore } from 'Stores/User/App';
|
import { AppUserStore } from 'Stores/User/App';
|
||||||
import { AccountUserStore } from 'Stores/User/Account';
|
import { AccountUserStore } from 'Stores/User/Account';
|
||||||
import { ThemeStore } from 'Stores/Theme';
|
|
||||||
|
|
||||||
import { AbstractSettingsScreen, settingsAddViewModel } from 'Screen/AbstractSettings';
|
import { AbstractSettingsScreen, settingsAddViewModel } from 'Screen/AbstractSettings';
|
||||||
|
|
||||||
|
|
@ -23,7 +22,7 @@ import { SettingsPaneUserView } from 'View/User/Settings/Pane';
|
||||||
|
|
||||||
export class SettingsUserScreen extends AbstractSettingsScreen {
|
export class SettingsUserScreen extends AbstractSettingsScreen {
|
||||||
constructor() {
|
constructor() {
|
||||||
super([SystemDropDownUserView, SettingsMenuUserView, SettingsPaneUserView]);
|
super([SettingsMenuUserView, SettingsPaneUserView, SystemDropDownUserView]);
|
||||||
|
|
||||||
const views = [
|
const views = [
|
||||||
UserSettingsGeneral
|
UserSettingsGeneral
|
||||||
|
|
@ -67,7 +66,6 @@ export class SettingsUserScreen extends AbstractSettingsScreen {
|
||||||
onShow() {
|
onShow() {
|
||||||
this.setSettingsTitle();
|
this.setSettingsTitle();
|
||||||
keyScope(Scope.Settings);
|
keyScope(Scope.Settings);
|
||||||
ThemeStore.isMobile() && leftPanelDisabled(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSettingsTitle() {
|
setSettingsTitle() {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ export class AdminSettingsAbout /*extends AbstractViewSettings*/ {
|
||||||
coreWarning: false,
|
coreWarning: false,
|
||||||
coreVersion: '',
|
coreVersion: '',
|
||||||
coreVersionCompare: -2,
|
coreVersionCompare: -2,
|
||||||
|
load1: 0,
|
||||||
|
load5: 0,
|
||||||
|
load15: 0,
|
||||||
errorDesc: ''
|
errorDesc: ''
|
||||||
});
|
});
|
||||||
this.coreChecking = ko.observable(false).extend({ throttle: 100 });
|
this.coreChecking = ko.observable(false).extend({ throttle: 100 });
|
||||||
|
|
@ -51,18 +54,21 @@ export class AdminSettingsAbout /*extends AbstractViewSettings*/ {
|
||||||
}
|
}
|
||||||
|
|
||||||
onBuild() {
|
onBuild() {
|
||||||
Remote.request('AdminPHPExtensions', (iError, data) => iError || this.phpextensions(data.Result));
|
|
||||||
|
|
||||||
// beforeShow() {
|
// beforeShow() {
|
||||||
this.coreChecking(true);
|
this.coreChecking(true);
|
||||||
Remote.request('AdminUpdateInfo', (iError, data) => {
|
Remote.request('AdminInfo', (iError, data) => {
|
||||||
this.coreChecking(false);
|
this.coreChecking(false);
|
||||||
if (!iError && data?.Result) {
|
data = data?.Result;
|
||||||
|
if (!iError && data) {
|
||||||
|
this.load1(data.system.load?.[0]);
|
||||||
|
this.load5(data.system.load?.[1]);
|
||||||
|
this.load15(data.system.load?.[2]);
|
||||||
|
this.phpextensions(data.php);
|
||||||
this.coreReal(true);
|
this.coreReal(true);
|
||||||
this.coreUpdatable(!!data.Result.updatable);
|
this.coreUpdatable(!!data.core.updatable);
|
||||||
this.coreWarning(!!data.Result.warning);
|
this.coreWarning(!!data.core.warning);
|
||||||
this.coreVersion(data.Result.version || '');
|
this.coreVersion(data.core.version || '');
|
||||||
this.coreVersionCompare(data.Result.versionCompare);
|
this.coreVersionCompare(data.core.versionCompare);
|
||||||
} else {
|
} else {
|
||||||
this.coreReal(false);
|
this.coreReal(false);
|
||||||
this.coreWarning(false);
|
this.coreWarning(false);
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { AbstractViewSettings } from 'Knoin/AbstractViews';
|
||||||
export class AdminSettingsBranding extends AbstractViewSettings {
|
export class AdminSettingsBranding extends AbstractViewSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.addSetting('Title');
|
this.addSetting('title');
|
||||||
this.addSetting('LoadingDescription');
|
this.addSetting('loadingDescription');
|
||||||
this.addSetting('FaviconUrl');
|
this.addSetting('faviconUrl');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,16 @@ export class AdminSettingsContacts extends AbstractViewSettings {
|
||||||
super();
|
super();
|
||||||
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
|
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
|
||||||
|
|
||||||
this.addSetting('ContactsPdoDsn');
|
this.addSetting('contactsPdoDsn');
|
||||||
this.addSetting('ContactsPdoUser');
|
this.addSetting('contactsPdoUser');
|
||||||
this.addSetting('ContactsPdoPassword');
|
this.addSetting('contactsPdoPassword');
|
||||||
this.addSetting('ContactsPdoType', () => {
|
this.addSetting('contactsPdoType', () => {
|
||||||
this.testContactsSuccess(false);
|
this.testContactsSuccess(false);
|
||||||
this.testContactsError(false);
|
this.testContactsError(false);
|
||||||
this.testContactsErrorMessage('');
|
this.testContactsErrorMessage('');
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addSettings(['ContactsEnable','ContactsSync']);
|
this.addSettings(['contactsEnable','contactsSync']);
|
||||||
|
|
||||||
addObservablesTo(this, {
|
addObservablesTo(this, {
|
||||||
testing: false,
|
testing: false,
|
||||||
|
|
@ -31,7 +31,7 @@ export class AdminSettingsContacts extends AbstractViewSettings {
|
||||||
testContactsErrorMessage: ''
|
testContactsErrorMessage: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
this.addSetting('ContactsSuggestionsLimit');
|
this.addSetting('contactsSuggestionsLimit');
|
||||||
|
|
||||||
const supportedTypes = SettingsGet('supportedPdoDrivers') || [],
|
const supportedTypes = SettingsGet('supportedPdoDrivers') || [],
|
||||||
types = [{
|
types = [{
|
||||||
|
|
@ -92,10 +92,10 @@ export class AdminSettingsContacts extends AbstractViewSettings {
|
||||||
|
|
||||||
this.testing(false);
|
this.testing(false);
|
||||||
}, {
|
}, {
|
||||||
ContactsPdoType: this.contactsPdoType(),
|
contactsPdoType: this.contactsPdoType(),
|
||||||
ContactsPdoDsn: this.contactsPdoDsn(),
|
contactsPdoDsn: this.contactsPdoDsn(),
|
||||||
ContactsPdoUser: this.contactsPdoUser(),
|
contactsPdoUser: this.contactsPdoUser(),
|
||||||
ContactsPdoPassword: this.contactsPdoPassword()
|
contactsPdoPassword: this.contactsPdoPassword()
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,12 +28,12 @@ export class AdminSettingsGeneral extends AbstractViewSettings {
|
||||||
|
|
||||||
const aLanguagesAdmin = Settings.app('languagesAdmin');
|
const aLanguagesAdmin = Settings.app('languagesAdmin');
|
||||||
this.languagesAdmin = ko.observableArray(isArray(aLanguagesAdmin) ? aLanguagesAdmin : []);
|
this.languagesAdmin = ko.observableArray(isArray(aLanguagesAdmin) ? aLanguagesAdmin : []);
|
||||||
this.languageAdmin = ko.observable(SettingsGet('LanguageAdmin'));
|
this.languageAdmin = ko.observable(SettingsGet('languageAdmin'));
|
||||||
|
|
||||||
this.theme = ThemeStore.theme;
|
this.theme = ThemeStore.theme;
|
||||||
this.themes = ThemeStore.themes;
|
this.themes = ThemeStore.themes;
|
||||||
|
|
||||||
this.addSettings(['AllowLanguagesOnSettings']);
|
this.addSettings(['allowLanguagesOnSettings']);
|
||||||
|
|
||||||
addObservablesTo(this, {
|
addObservablesTo(this, {
|
||||||
capaThemes: SettingsCapa('Themes'),
|
capaThemes: SettingsCapa('Themes'),
|
||||||
|
|
@ -53,14 +53,14 @@ export class AdminSettingsGeneral extends AbstractViewSettings {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
this.attachmentLimit = ko
|
this.attachmentLimit = ko
|
||||||
.observable(SettingsGet('AttachmentLimit') / (1024 * 1024))
|
.observable(SettingsGet('attachmentLimit') / (1024 * 1024))
|
||||||
.extend({ debounce: 500 });
|
.extend({ debounce: 500 });
|
||||||
|
|
||||||
this.addSetting('Language');
|
this.addSetting('language');
|
||||||
this.addSetting('AttachmentLimit');
|
this.addSetting('attachmentLimit');
|
||||||
this.addSetting('Theme', value => changeTheme(value, this.themeTrigger));
|
this.addSetting('Theme', value => changeTheme(value, this.themeTrigger));
|
||||||
|
|
||||||
this.uploadData = SettingsGet('PhpUploadSizes');
|
this.uploadData = SettingsGet('phpUploadSizes');
|
||||||
this.uploadDataDesc =
|
this.uploadDataDesc =
|
||||||
(this.uploadData?.upload_max_filesize || this.uploadData?.post_max_size)
|
(this.uploadData?.upload_max_filesize || this.uploadData?.post_max_size)
|
||||||
? [
|
? [
|
||||||
|
|
@ -91,7 +91,7 @@ export class AdminSettingsGeneral extends AbstractViewSettings {
|
||||||
this.languageAdminTrigger(SaveSettingStatus.Saving);
|
this.languageAdminTrigger(SaveSettingStatus.Saving);
|
||||||
translatorReload(value, 1)
|
translatorReload(value, 1)
|
||||||
.then(fReloadLanguageHelper(SaveSettingStatus.Success), fReloadLanguageHelper(SaveSettingStatus.Failed))
|
.then(fReloadLanguageHelper(SaveSettingStatus.Success), fReloadLanguageHelper(SaveSettingStatus.Failed))
|
||||||
.then(() => Remote.saveSetting('LanguageAdmin', value));
|
.then(() => Remote.saveSetting('languageAdmin', value));
|
||||||
},
|
},
|
||||||
|
|
||||||
capaAdditionalAccounts: fSaveHelper('CapaAdditionalAccounts'),
|
capaAdditionalAccounts: fSaveHelper('CapaAdditionalAccounts'),
|
||||||
|
|
@ -114,7 +114,7 @@ export class AdminSettingsGeneral extends AbstractViewSettings {
|
||||||
showScreenPopup(LanguagesPopupView, [
|
showScreenPopup(LanguagesPopupView, [
|
||||||
this.languageAdmin,
|
this.languageAdmin,
|
||||||
this.languagesAdmin(),
|
this.languagesAdmin(),
|
||||||
SettingsGet('UserLanguageAdmin')
|
SettingsGet('languageUsers')
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { AbstractViewSettings } from 'Knoin/AbstractViews';
|
||||||
export class AdminSettingsLogin extends AbstractViewSettings {
|
export class AdminSettingsLogin extends AbstractViewSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.addSetting('LoginDefaultDomain');
|
this.addSetting('loginDefaultDomain');
|
||||||
this.addSettings(['DetermineUserLanguage','DetermineUserDomain','AllowLanguagesOnLogin']);
|
this.addSettings(['determineUserLanguage','determineUserDomain','allowLanguagesOnLogin']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import { Notification } from 'Common/Enums';
|
import { Notifications } from 'Common/Enums';
|
||||||
import { getNotification } from 'Common/Translator';
|
import { getNotification } from 'Common/Translator';
|
||||||
|
|
||||||
import { PackageAdminStore } from 'Stores/Admin/Package';
|
import { PackageAdminStore } from 'Stores/Admin/Package';
|
||||||
|
|
@ -15,7 +15,7 @@ export class AdminSettingsPackages extends AbstractViewSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.addSettings(['EnabledPlugins']);
|
this.addSettings(['pluginsEnable']);
|
||||||
|
|
||||||
addObservablesTo(this, {
|
addObservablesTo(this, {
|
||||||
packagesError: ''
|
packagesError: ''
|
||||||
|
|
@ -67,7 +67,7 @@ export class AdminSettingsPackages extends AbstractViewSettings {
|
||||||
|
|
||||||
if (iError) {
|
if (iError) {
|
||||||
this.packagesError(
|
this.packagesError(
|
||||||
getNotification(install ? Notification.CantInstallPackage : Notification.CantDeletePackage)
|
getNotification(install ? Notifications.CantInstallPackage : Notifications.CantDeletePackage)
|
||||||
+ (data.ErrorMessage ? ':\n' + data.ErrorMessage : '')
|
+ (data.ErrorMessage ? ':\n' + data.ErrorMessage : '')
|
||||||
);
|
);
|
||||||
} else if (data.Result.Reload) {
|
} else if (data.Result.Reload) {
|
||||||
|
|
@ -113,7 +113,7 @@ export class AdminSettingsPackages extends AbstractViewSettings {
|
||||||
if (iError) {
|
if (iError) {
|
||||||
plugin.enabled(disable);
|
plugin.enabled(disable);
|
||||||
this.packagesError(
|
this.packagesError(
|
||||||
(Notification.UnsupportedPluginPackage === iError && data?.ErrorMessage)
|
(Notifications.UnsupportedPluginPackage === iError && data?.ErrorMessage)
|
||||||
? data.ErrorMessage
|
? data.ErrorMessage
|
||||||
: getNotification(iError)
|
: getNotification(iError)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@ export class AdminSettingsSecurity extends AbstractViewSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.addSettings(['UseLocalProxyForExternalImages']);
|
this.addSettings(['useLocalProxyForExternalImages']);
|
||||||
|
|
||||||
this.weakPassword = rl.app.weakPassword;
|
this.weakPassword = rl.app.weakPassword;
|
||||||
|
|
||||||
addObservablesTo(this, {
|
addObservablesTo(this, {
|
||||||
adminLogin: SettingsGet('AdminLogin'),
|
adminLogin: SettingsGet('adminLogin'),
|
||||||
adminLoginError: false,
|
adminLoginError: false,
|
||||||
adminPassword: '',
|
adminPassword: '',
|
||||||
adminPasswordNew: '',
|
adminPasswordNew: '',
|
||||||
|
|
@ -68,7 +68,7 @@ export class AdminSettingsSecurity extends AbstractViewSettings {
|
||||||
capaOpenPGP: value => Remote.saveSetting('CapaOpenPGP', value)
|
capaOpenPGP: value => Remote.saveSetting('CapaOpenPGP', value)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.adminTOTP(SettingsGet('AdminTOTP'));
|
this.adminTOTP(SettingsGet('adminTOTP'));
|
||||||
|
|
||||||
decorateKoCommands(this, {
|
decorateKoCommands(this, {
|
||||||
saveAdminUserCommand: self => self.adminLogin().trim() && self.adminPassword()
|
saveAdminUserCommand: self => self.adminLogin().trim() && self.adminPassword()
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export class UserSettingsAccounts /*extends AbstractViewSettings*/ {
|
||||||
this.accounts = AccountUserStore;
|
this.accounts = AccountUserStore;
|
||||||
this.loading = AccountUserStore.loading;
|
this.loading = AccountUserStore.loading;
|
||||||
this.identities = IdentityUserStore;
|
this.identities = IdentityUserStore;
|
||||||
this.mainEmail = SettingsGet('MainEmail');
|
this.mainEmail = SettingsGet('mainEmail');
|
||||||
|
|
||||||
this.accountForDeletion = ko.observable(null).askDeleteHelper();
|
this.accountForDeletion = ko.observable(null).askDeleteHelper();
|
||||||
this.identityForDeletion = ko.observable(null).askDeleteHelper();
|
this.identityForDeletion = ko.observable(null).askDeleteHelper();
|
||||||
|
|
@ -88,7 +88,7 @@ export class UserSettingsAccounts /*extends AbstractViewSettings*/ {
|
||||||
|
|
||||||
accountsAndIdentitiesAfterMove() {
|
accountsAndIdentitiesAfterMove() {
|
||||||
Remote.request('AccountsAndIdentitiesSortOrder', null, {
|
Remote.request('AccountsAndIdentitiesSortOrder', null, {
|
||||||
Accounts: AccountUserStore.getEmailAddresses().filter(v => v != SettingsGet('MainEmail')),
|
Accounts: AccountUserStore.getEmailAddresses().filter(v => v != SettingsGet('mainEmail')),
|
||||||
Identities: IdentityUserStore.map(item => (item ? item.id() : ""))
|
Identities: IdentityUserStore.map(item => (item ? item.id() : ""))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import { Notification } from 'Common/Enums';
|
import { Notifications } from 'Common/Enums';
|
||||||
import { FolderMetadataKeys } from 'Common/EnumsUser';
|
import { FolderMetadataKeys } from 'Common/EnumsUser';
|
||||||
import { getNotification } from 'Common/Translator';
|
import { getNotification } from 'Common/Translator';
|
||||||
|
|
||||||
import { setFolder, getFolderFromCacheList, removeFolderFromCacheList } from 'Common/Cache';
|
import { getFolderFromCacheList, removeFolderFromCacheList } from 'Common/Cache';
|
||||||
import { defaultOptionsAfterRender } from 'Common/Utils';
|
import { defaultOptionsAfterRender } from 'Common/Utils';
|
||||||
import { sortFolders } from 'Common/Folders';
|
|
||||||
import { initOnStartOrLangChange, i18n } from 'Common/Translator';
|
import { initOnStartOrLangChange, i18n } from 'Common/Translator';
|
||||||
|
|
||||||
import { FolderUserStore } from 'Stores/User/Folder';
|
import { FolderUserStore } from 'Stores/User/Folder';
|
||||||
|
|
@ -18,7 +17,6 @@ import { showScreenPopup } from 'Knoin/Knoin';
|
||||||
|
|
||||||
import { FolderCreatePopupView } from 'View/Popup/FolderCreate';
|
import { FolderCreatePopupView } from 'View/Popup/FolderCreate';
|
||||||
import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
|
import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
|
||||||
import { loadFolders } from 'Model/FolderCollection';
|
|
||||||
|
|
||||||
const folderForDeletion = ko.observable(null).askDeleteHelper();
|
const folderForDeletion = ko.observable(null).askDeleteHelper();
|
||||||
|
|
||||||
|
|
@ -52,52 +50,10 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
|
||||||
|
|
||||||
this.folderForDeletion = folderForDeletion;
|
this.folderForDeletion = folderForDeletion;
|
||||||
|
|
||||||
this.folderForEdit = ko.observable(null).extend({ toggleSubscribeProperty: [this, 'editing'] });
|
|
||||||
|
|
||||||
SettingsUserStore.hideUnsubscribed.subscribe(value => Remote.saveSetting('HideUnsubscribed', value));
|
SettingsUserStore.hideUnsubscribed.subscribe(value => Remote.saveSetting('HideUnsubscribed', value));
|
||||||
SettingsUserStore.unhideKolabFolders.subscribe(value => Remote.saveSetting('UnhideKolabFolders', value));
|
SettingsUserStore.unhideKolabFolders.subscribe(value => Remote.saveSetting('UnhideKolabFolders', value));
|
||||||
}
|
}
|
||||||
|
|
||||||
folderEditOnEnter(folder) {
|
|
||||||
const nameToEdit = folder?.nameForEdit().trim();
|
|
||||||
if (nameToEdit && folder.name() !== nameToEdit) {
|
|
||||||
Remote.abort('Folders').post('FolderRename', FolderUserStore.foldersRenaming, {
|
|
||||||
folder: folder.fullName,
|
|
||||||
newFolderName: nameToEdit,
|
|
||||||
subscribe: folder.isSubscribed() ? 1 : 0
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
folder.name(nameToEdit/*data.name*/);
|
|
||||||
if (folder.subFolders.length) {
|
|
||||||
Remote.setTrigger(FolderUserStore.foldersLoading, true);
|
|
||||||
// clearTimeout(Remote.foldersTimeout);
|
|
||||||
// Remote.foldersTimeout = setTimeout(loadFolders, 500);
|
|
||||||
setTimeout(loadFolders, 500);
|
|
||||||
// TODO: rename all subfolders with folder.delimiter to prevent reload?
|
|
||||||
} else {
|
|
||||||
removeFolderFromCacheList(folder.fullName);
|
|
||||||
folder.fullName = data.Result.fullName;
|
|
||||||
setFolder(folder);
|
|
||||||
const parent = getFolderFromCacheList(folder.parentName);
|
|
||||||
sortFolders(parent ? parent.subFolders : FolderUserStore.folderList);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
FolderUserStore.folderListError(
|
|
||||||
getNotification(error.code, '', Notification.CantRenameFolder)
|
|
||||||
+ '.\n' + error.message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// this.folderForEdit(null);
|
|
||||||
folder.editing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
folderEditOnEsc(folder) {
|
|
||||||
// this.folderForEdit(null);
|
|
||||||
folder?.editing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
onShow() {
|
onShow() {
|
||||||
FolderUserStore.folderListError('');
|
FolderUserStore.folderListError('');
|
||||||
}
|
}
|
||||||
|
|
@ -119,8 +75,8 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
|
||||||
&& folderToRemove.askDelete()
|
&& folderToRemove.askDelete()
|
||||||
) {
|
) {
|
||||||
if (0 < folderToRemove.totalEmails()) {
|
if (0 < folderToRemove.totalEmails()) {
|
||||||
// FolderUserStore.folderListError(getNotification(Notification.CantDeleteNonEmptyFolder));
|
// FolderUserStore.folderListError(getNotification(Notifications.CantDeleteNonEmptyFolder));
|
||||||
folderToRemove.errorMsg(getNotification(Notification.CantDeleteNonEmptyFolder));
|
folderToRemove.errorMsg(getNotification(Notifications.CantDeleteNonEmptyFolder));
|
||||||
} else {
|
} else {
|
||||||
folderForDeletion(null);
|
folderForDeletion(null);
|
||||||
|
|
||||||
|
|
@ -129,7 +85,7 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
|
||||||
folder: folderToRemove.fullName
|
folder: folderToRemove.fullName
|
||||||
}).then(
|
}).then(
|
||||||
() => {
|
() => {
|
||||||
// folderToRemove.flags.push('\\nonexistent');
|
// folderToRemove.attributes.push('\\nonexistent');
|
||||||
folderToRemove.selectable(false);
|
folderToRemove.selectable(false);
|
||||||
// folderToRemove.isSubscribed(false);
|
// folderToRemove.isSubscribed(false);
|
||||||
// folderToRemove.checkable(false);
|
// folderToRemove.checkable(false);
|
||||||
|
|
@ -141,7 +97,7 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
FolderUserStore.folderListError(
|
FolderUserStore.folderListError(
|
||||||
getNotification(error.code, '', Notification.CantDeleteFolder)
|
getNotification(error.code, '', Notifications.CantDeleteFolder)
|
||||||
+ '.\n' + error.message
|
+ '.\n' + error.message
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import { LanguageStore } from 'Stores/Language';
|
||||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
import { IdentityUserStore } from 'Stores/User/Identity';
|
import { IdentityUserStore } from 'Stores/User/Identity';
|
||||||
import { NotificationUserStore } from 'Stores/User/Notification';
|
import { NotificationUserStore } from 'Stores/User/Notification';
|
||||||
import { MessageUserStore } from 'Stores/User/Message';
|
|
||||||
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||||
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
@ -35,7 +34,7 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
||||||
|
|
||||||
this.soundNotification = SMAudio.notifications;
|
this.soundNotification = SMAudio.notifications;
|
||||||
this.notificationSound = ko.observable(SettingsGet('NotificationSound'));
|
this.notificationSound = ko.observable(SettingsGet('NotificationSound'));
|
||||||
this.notificationSounds = ko.observableArray(SettingsGet('NewMailSounds'));
|
this.notificationSounds = ko.observableArray(SettingsGet('newMailSounds'));
|
||||||
|
|
||||||
this.desktopNotification = NotificationUserStore.enabled;
|
this.desktopNotification = NotificationUserStore.enabled;
|
||||||
this.isDesktopNotificationAllowed = NotificationUserStore.allowed;
|
this.isDesktopNotificationAllowed = NotificationUserStore.allowed;
|
||||||
|
|
@ -44,12 +43,13 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
||||||
|
|
||||||
['layout', 'messageReadDelay', 'messagesPerPage', 'checkMailInterval',
|
['layout', 'messageReadDelay', 'messagesPerPage', 'checkMailInterval',
|
||||||
'editorDefaultType', 'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt',
|
'editorDefaultType', 'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt',
|
||||||
'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors', 'allowStyles',
|
'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors', 'allowStyles', 'allowDraftAutosave',
|
||||||
'hideDeleted', 'listInlineAttachments', 'simpleAttachmentsList', 'collapseBlockquotes', 'maxBlockquotesLevel',
|
'hideDeleted', 'listInlineAttachments', 'simpleAttachmentsList', 'collapseBlockquotes', 'maxBlockquotesLevel',
|
||||||
'useCheckboxesInList', 'listGrouped', 'useThreads', 'replySameFolder', 'msgDefaultAction', 'allowSpellcheck'
|
'useCheckboxesInList', 'listGrouped', 'useThreads', 'replySameFolder', 'msgDefaultAction', 'allowSpellcheck',
|
||||||
|
'showNextMessage'
|
||||||
].forEach(name => this[name] = SettingsUserStore[name]);
|
].forEach(name => this[name] = SettingsUserStore[name]);
|
||||||
|
|
||||||
this.allowLanguagesOnSettings = !!SettingsGet('AllowLanguagesOnSettings');
|
this.allowLanguagesOnSettings = !!SettingsGet('allowLanguagesOnSettings');
|
||||||
|
|
||||||
this.languageTrigger = ko.observable(SaveSettingStatus.Idle);
|
this.languageTrigger = ko.observable(SaveSettingStatus.Idle);
|
||||||
|
|
||||||
|
|
@ -87,7 +87,7 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
||||||
layoutTypes: () => {
|
layoutTypes: () => {
|
||||||
translateTrigger();
|
translateTrigger();
|
||||||
return [
|
return [
|
||||||
{ id: Layout.NoPreview, name: i18n('SETTINGS_GENERAL/LAYOUT_NO_SPLIT') },
|
{ id: 0, name: i18n('SETTINGS_GENERAL/LAYOUT_NO_SPLIT') },
|
||||||
{ id: Layout.SidePreview, name: i18n('SETTINGS_GENERAL/LAYOUT_VERTICAL_SPLIT') },
|
{ id: Layout.SidePreview, name: i18n('SETTINGS_GENERAL/LAYOUT_VERTICAL_SPLIT') },
|
||||||
{ id: Layout.BottomPreview, name: i18n('SETTINGS_GENERAL/LAYOUT_HORIZONTAL_SPLIT') }
|
{ id: Layout.BottomPreview, name: i18n('SETTINGS_GENERAL/LAYOUT_HORIZONTAL_SPLIT') }
|
||||||
];
|
];
|
||||||
|
|
@ -102,10 +102,10 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
||||||
this.addSetting('Layout');
|
this.addSetting('Layout');
|
||||||
this.addSetting('MaxBlockquotesLevel');
|
this.addSetting('MaxBlockquotesLevel');
|
||||||
|
|
||||||
this.addSettings(['ViewHTML', 'ViewImages', 'ViewImagesWhitelist', 'HideDeleted', 'AllowStyles',
|
this.addSettings(['ViewHTML', 'ViewImages', 'ViewImagesWhitelist', 'HideDeleted', 'RemoveColors', 'AllowStyles',
|
||||||
'ListInlineAttachments', 'simpleAttachmentsList', 'UseCheckboxesInList', 'listGrouped', 'ReplySameFolder',
|
'ListInlineAttachments', 'simpleAttachmentsList', 'UseCheckboxesInList', 'listGrouped', 'ReplySameFolder',
|
||||||
'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt', 'allowSpellcheck',
|
'requestReadReceipt', 'requestDsn', 'requireTLS', 'pgpSign', 'pgpEncrypt', 'allowSpellcheck',
|
||||||
'DesktopNotifications', 'SoundNotification', 'CollapseBlockquotes']);
|
'DesktopNotifications', 'SoundNotification', 'CollapseBlockquotes', 'AllowDraftAutosave', 'showNextMessage']);
|
||||||
|
|
||||||
const fReloadLanguageHelper = (saveSettingsStep) => () => {
|
const fReloadLanguageHelper = (saveSettingsStep) => () => {
|
||||||
this.languageTrigger(saveSettingsStep);
|
this.languageTrigger(saveSettingsStep);
|
||||||
|
|
@ -117,20 +117,12 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
||||||
this.languageTrigger(SaveSettingStatus.Saving);
|
this.languageTrigger(SaveSettingStatus.Saving);
|
||||||
translatorReload(value)
|
translatorReload(value)
|
||||||
.then(fReloadLanguageHelper(SaveSettingStatus.Success), fReloadLanguageHelper(SaveSettingStatus.Failed))
|
.then(fReloadLanguageHelper(SaveSettingStatus.Success), fReloadLanguageHelper(SaveSettingStatus.Failed))
|
||||||
.then(() => Remote.saveSetting('Language', value));
|
.then(() => Remote.saveSetting('language', value));
|
||||||
},
|
},
|
||||||
|
|
||||||
hourCycle: value =>
|
hourCycle: value =>
|
||||||
Remote.saveSetting('hourCycle', value),
|
Remote.saveSetting('hourCycle', value),
|
||||||
|
|
||||||
removeColors: value => {
|
|
||||||
let dom = MessageUserStore.bodiesDom();
|
|
||||||
if (dom) {
|
|
||||||
dom.innerHTML = '';
|
|
||||||
}
|
|
||||||
Remote.saveSetting('RemoveColors', value);
|
|
||||||
},
|
|
||||||
|
|
||||||
notificationSound: value => {
|
notificationSound: value => {
|
||||||
Remote.saveSetting('NotificationSound', value);
|
Remote.saveSetting('NotificationSound', value);
|
||||||
Settings.set('NotificationSound', value);
|
Settings.set('NotificationSound', value);
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@ import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
import { GnuPGUserStore } from 'Stores/User/GnuPG';
|
import { GnuPGUserStore } from 'Stores/User/GnuPG';
|
||||||
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
||||||
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
|
||||||
|
|
||||||
import { showScreenPopup } from 'Knoin/Knoin';
|
import { showScreenPopup } from 'Knoin/Knoin';
|
||||||
|
|
||||||
import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport';
|
import { OpenPgpImportPopupView } from 'View/Popup/OpenPgpImport';
|
||||||
import { OpenPgpGeneratePopupView } from 'View/Popup/OpenPgpGenerate';
|
import { OpenPgpGeneratePopupView } from 'View/Popup/OpenPgpGenerate';
|
||||||
|
|
||||||
|
//import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
export class UserSettingsSecurity extends AbstractViewSettings {
|
export class UserSettingsSecurity extends AbstractViewSettings {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -46,10 +46,6 @@ export class UserSettingsSecurity extends AbstractViewSettings {
|
||||||
this.canOpenPGP = SettingsCapa('OpenPGP');
|
this.canOpenPGP = SettingsCapa('OpenPGP');
|
||||||
this.canGnuPG = GnuPGUserStore.isSupported();
|
this.canGnuPG = GnuPGUserStore.isSupported();
|
||||||
this.canMailvelope = !!window.mailvelope;
|
this.canMailvelope = !!window.mailvelope;
|
||||||
|
|
||||||
this.allowDraftAutosave = SettingsUserStore.allowDraftAutosave;
|
|
||||||
|
|
||||||
this.allowDraftAutosave.subscribe(value => Remote.saveSetting('AllowDraftAutosave', value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addOpenPgpKey() {
|
addOpenPgpKey() {
|
||||||
|
|
@ -66,5 +62,11 @@ export class UserSettingsSecurity extends AbstractViewSettings {
|
||||||
* The iframe will be injected into the container identified by selector.
|
* The iframe will be injected into the container identified by selector.
|
||||||
*/
|
*/
|
||||||
window.mailvelope && mailvelope.createSettingsContainer('#mailvelope-settings'/*[, keyring], options*/);
|
window.mailvelope && mailvelope.createSettingsContainer('#mailvelope-settings'/*[, keyring], options*/);
|
||||||
|
/**
|
||||||
|
* https://github.com/the-djmaze/snappymail/issues/973
|
||||||
|
Remote.request('GetStoredPGPKeys', (iError, data) => {
|
||||||
|
console.dir([iError, data]);
|
||||||
|
});
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { i18n } from 'Common/Translator';
|
||||||
import { SettingsCapa } from 'Common/Globals';
|
import { SettingsCapa } from 'Common/Globals';
|
||||||
|
|
||||||
import { ThemeStore, convertThemeName, changeTheme } from 'Stores/Theme';
|
import { ThemeStore, convertThemeName, changeTheme } from 'Stores/Theme';
|
||||||
|
import { addSubscribablesTo } from 'External/ko';
|
||||||
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
|
|
@ -24,20 +25,22 @@ export class UserSettingsThemes /*extends AbstractViewSettings*/ {
|
||||||
this.fontSansSerif = ThemeStore.fontSansSerif;
|
this.fontSansSerif = ThemeStore.fontSansSerif;
|
||||||
this.fontSerif = ThemeStore.fontSerif;
|
this.fontSerif = ThemeStore.fontSerif;
|
||||||
this.fontMono = ThemeStore.fontMono;
|
this.fontMono = ThemeStore.fontMono;
|
||||||
ThemeStore.fontSansSerif.subscribe(value => {
|
addSubscribablesTo(ThemeStore, {
|
||||||
|
fontSansSerif: value => {
|
||||||
Remote.saveSettings(null, {
|
Remote.saveSettings(null, {
|
||||||
fontSansSerif: value
|
fontSansSerif: value
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
ThemeStore.fontSerif.subscribe(value => {
|
fontSerif: value => {
|
||||||
Remote.saveSettings(null, {
|
Remote.saveSettings(null, {
|
||||||
fontSerif: value
|
fontSerif: value
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
ThemeStore.fontMono.subscribe(value => {
|
fontMono: value => {
|
||||||
Remote.saveSettings(null, {
|
Remote.saveSettings(null, {
|
||||||
fontMono: value
|
fontMono: value
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.theme = ThemeStore.theme;
|
this.theme = ThemeStore.theme;
|
||||||
|
|
|
||||||
|
|
@ -25,13 +25,13 @@ export const
|
||||||
arr.map(item => item.toString?.() || item).join(separator),
|
arr.map(item => item.toString?.() || item).join(separator),
|
||||||
/*
|
/*
|
||||||
getNotificationMessage = code => {
|
getNotificationMessage = code => {
|
||||||
let key = getKeyByValue(Notification, code);
|
let key = getKeyByValue(Notifications, code);
|
||||||
return key ? I18N_DATA.NOTIFICATIONS[i18nKey(key).replace('_NOTIFICATION', '_ERROR')] : '';
|
return key ? I18N_DATA.NOTIFICATIONS[i18nKey(key).replace('_NOTIFICATION', '_ERROR')] : '';
|
||||||
rl.i18n('NOTIFICATIONS/')
|
rl.i18n('NOTIFICATIONS/')
|
||||||
},
|
},
|
||||||
getNotification = (code, message = '', defCode = 0) => {
|
getNotification = (code, message = '', defCode = 0) => {
|
||||||
code = parseInt(code, 10) || 0;
|
code = parseInt(code, 10) || 0;
|
||||||
if (Notification.ClientViewError === code && message) {
|
if (Notifications.ClientViewError === code && message) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ export class FilterPopupView extends rl.pluginPopupView {
|
||||||
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
|
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
|
||||||
this.folderSelectList = koComputable(() =>
|
this.folderSelectList = koComputable(() =>
|
||||||
folderListOptionsBuilder(
|
folderListOptionsBuilder(
|
||||||
[rl.settings.get('SieveAllowFileintoInbox') ? '' : 'INBOX'],
|
[rl.settings.get('sieveAllowFileintoInbox') ? '' : 'INBOX'],
|
||||||
[['', '']],
|
[['', '']],
|
||||||
item => item?.localName() || ''
|
item => item?.localName() || ''
|
||||||
)
|
)
|
||||||
|
|
|
||||||
1
dev/Storage/Passphrases.js
Normal file
1
dev/Storage/Passphrases.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const Passphrases = new Map();
|
||||||
|
|
@ -11,8 +11,8 @@ export const LanguageStore = {
|
||||||
populate: function() {
|
populate: function() {
|
||||||
const aLanguages = Settings.app('languages');
|
const aLanguages = Settings.app('languages');
|
||||||
this.languages(isArray(aLanguages) ? aLanguages : []);
|
this.languages(isArray(aLanguages) ? aLanguages : []);
|
||||||
this.language(SettingsGet('Language'));
|
this.language(SettingsGet('language'));
|
||||||
this.userLanguage(SettingsGet('UserLanguage'));
|
this.userLanguage(SettingsGet('userLanguage'));
|
||||||
this.hourCycle(SettingsGet('hourCycle'));
|
this.hourCycle(SettingsGet('hourCycle'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
import { $htmlCL, elementById, leftPanelDisabled, Settings, SettingsGet } from 'Common/Globals';
|
import { $htmlCL, appEl, elementById, leftPanelDisabled, Settings, SettingsGet } from 'Common/Globals';
|
||||||
import { isArray, arrayLength } from 'Common/Utils';
|
import { isArray, arrayLength } from 'Common/Utils';
|
||||||
import { cssLink, serverRequestRaw } from 'Common/Links';
|
import { cssLink, serverRequestRaw } from 'Common/Links';
|
||||||
import { SaveSettingStatus } from 'Common/Enums';
|
import { SaveSettingStatus } from 'Common/Enums';
|
||||||
|
import { addSubscribablesTo } from 'External/ko';
|
||||||
|
|
||||||
let __themeTimer = 0;
|
let __themeTimer = 0;
|
||||||
|
|
||||||
export const
|
export const
|
||||||
appEl = () => elementById('rl-app'),
|
// Also see Styles/_Values.less @maxMobileWidth
|
||||||
|
isMobile = matchMedia('(max-width: 799px)'),
|
||||||
|
|
||||||
ThemeStore = {
|
ThemeStore = {
|
||||||
theme: ko.observable(''),
|
theme: ko.observable(''),
|
||||||
|
|
@ -17,7 +19,7 @@ export const
|
||||||
fontSansSerif: ko.observable(''),
|
fontSansSerif: ko.observable(''),
|
||||||
fontSerif: ko.observable(''),
|
fontSerif: ko.observable(''),
|
||||||
fontMono: ko.observable(''),
|
fontMono: ko.observable(''),
|
||||||
isMobile: ko.observable($htmlCL.contains('rl-mobile')),
|
isMobile: ko.observable(false),
|
||||||
|
|
||||||
populate: () => {
|
populate: () => {
|
||||||
const themes = Settings.app('themes');
|
const themes = Settings.app('themes');
|
||||||
|
|
@ -25,8 +27,8 @@ export const
|
||||||
ThemeStore.themes(isArray(themes) ? themes : []);
|
ThemeStore.themes(isArray(themes) ? themes : []);
|
||||||
ThemeStore.theme(SettingsGet('Theme'));
|
ThemeStore.theme(SettingsGet('Theme'));
|
||||||
if (!ThemeStore.isMobile()) {
|
if (!ThemeStore.isMobile()) {
|
||||||
ThemeStore.userBackgroundName(SettingsGet('UserBackgroundName'));
|
ThemeStore.userBackgroundName(SettingsGet('userBackgroundName'));
|
||||||
ThemeStore.userBackgroundHash(SettingsGet('UserBackgroundHash'));
|
ThemeStore.userBackgroundHash(SettingsGet('userBackgroundHash'));
|
||||||
}
|
}
|
||||||
ThemeStore.fontSansSerif(SettingsGet('fontSansSerif'));
|
ThemeStore.fontSansSerif(SettingsGet('fontSansSerif'));
|
||||||
ThemeStore.fontSerif(SettingsGet('fontSerif'));
|
ThemeStore.fontSerif(SettingsGet('fontSerif'));
|
||||||
|
|
@ -62,11 +64,10 @@ export const
|
||||||
|
|
||||||
convertThemeName = theme => theme.replace(/@[a-z]+$/, '').replace(/([A-Z])/g, ' $1').trim();
|
convertThemeName = theme => theme.replace(/@[a-z]+$/, '').replace(/([A-Z])/g, ' $1').trim();
|
||||||
|
|
||||||
ThemeStore.isMobile.subscribe(value => $htmlCL.toggle('rl-mobile', value));
|
addSubscribablesTo(ThemeStore, {
|
||||||
|
fontSansSerif: value => {
|
||||||
ThemeStore.fontSansSerif.subscribe(value => {
|
|
||||||
if (null != value) {
|
if (null != value) {
|
||||||
let cl = appEl().classList;
|
let cl = appEl.classList;
|
||||||
cl.forEach(name => {
|
cl.forEach(name => {
|
||||||
if (name.startsWith('font') && !/font(Serif|Mono)/.test(name)) {
|
if (name.startsWith('font') && !/font(Serif|Mono)/.test(name)) {
|
||||||
cl.remove(name);
|
cl.remove(name);
|
||||||
|
|
@ -74,23 +75,33 @@ ThemeStore.fontSansSerif.subscribe(value => {
|
||||||
});
|
});
|
||||||
value && cl.add('font'+value);
|
value && cl.add('font'+value);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
ThemeStore.fontSerif.subscribe(value => {
|
|
||||||
|
fontSerif: value => {
|
||||||
if (null != value) {
|
if (null != value) {
|
||||||
let cl = appEl().classList;
|
let cl = appEl.classList;
|
||||||
cl.forEach(name => name.startsWith('fontSerif') && cl.remove(name));
|
cl.forEach(name => name.startsWith('fontSerif') && cl.remove(name));
|
||||||
value && cl.add('fontSerif'+value);
|
value && cl.add('fontSerif'+value);
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
ThemeStore.fontMono.subscribe(value => {
|
|
||||||
|
fontMono: value => {
|
||||||
if (null != value) {
|
if (null != value) {
|
||||||
let cl = appEl().classList;
|
let cl = appEl.classList;
|
||||||
cl.forEach(name => name.startsWith('fontMono') && cl.remove(name));
|
cl.forEach(name => name.startsWith('fontMono') && cl.remove(name));
|
||||||
value && cl.add('fontMono'+value);
|
value && cl.add('fontMono'+value);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
userBackgroundHash: value => {
|
||||||
|
appEl.classList.toggle('UserBackground', !!value);
|
||||||
|
appEl.style.backgroundImage = value ? "url("+serverRequestRaw('UserBackground', value)+")" : null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ThemeStore.userBackgroundHash.subscribe(value => {
|
isMobile.onchange = e => {
|
||||||
appEl().classList.toggle('UserBackground', !!value);
|
ThemeStore.isMobile(e.matches);
|
||||||
appEl().style.backgroundImage = value ? "url("+serverRequestRaw('UserBackground', value)+")" : null;
|
$htmlCL.toggle('rl-mobile', e.matches);
|
||||||
});
|
leftPanelDisabled(e.matches);
|
||||||
|
};
|
||||||
|
isMobile.onchange(isMobile);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { keyScope, leftPanelDisabled, SettingsGet, elementById } from 'Common/Globals';
|
import { keyScope, leftPanelDisabled, SettingsGet, elementById } from 'Common/Globals';
|
||||||
import { addObservablesTo } from 'External/ko';
|
import { addObservablesTo } from 'External/ko';
|
||||||
import { ThemeStore } from 'Stores/Theme';
|
import { ThemeStore } from 'Stores/Theme';
|
||||||
|
import { arePopupsVisible } from 'Knoin/Knoin';
|
||||||
|
|
||||||
export const AppUserStore = {
|
export const AppUserStore = {
|
||||||
allowContacts: () => !!SettingsGet('ContactsIsAllowed')
|
allowContacts: () => !!SettingsGet('contactsAllowed')
|
||||||
};
|
};
|
||||||
|
|
||||||
addObservablesTo(AppUserStore, {
|
addObservablesTo(AppUserStore, {
|
||||||
|
|
@ -15,10 +16,9 @@ addObservablesTo(AppUserStore, {
|
||||||
AppUserStore.focusedState.subscribe(value => {
|
AppUserStore.focusedState.subscribe(value => {
|
||||||
['FolderList','MessageList','MessageView'].forEach(name => {
|
['FolderList','MessageList','MessageView'].forEach(name => {
|
||||||
if (name === value) {
|
if (name === value) {
|
||||||
keyScope(value);
|
arePopupsVisible() || keyScope(value);
|
||||||
ThemeStore.isMobile() && leftPanelDisabled('FolderList' !== value);
|
ThemeStore.isMobile() && leftPanelDisabled('FolderList' !== value);
|
||||||
}
|
}
|
||||||
let dom = elementById('V-Mail'+name);
|
elementById('V-Mail'+name).classList.toggle('focused', name === value);
|
||||||
dom?.classList.toggle('focused', name === value);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import ko from 'ko';
|
||||||
import { SettingsGet } from 'Common/Globals';
|
import { SettingsGet } from 'Common/Globals';
|
||||||
import { koComputable, addObservablesTo, koArrayWithDestroy } from 'External/ko';
|
import { koComputable, addObservablesTo, koArrayWithDestroy } from 'External/ko';
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
import { Notifications } from 'Common/Enums';
|
||||||
|
|
||||||
export const ContactUserStore = koArrayWithDestroy();
|
export const ContactUserStore = koArrayWithDestroy();
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ ContactUserStore.sync = fResultFunc => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ContactUserStore.syncing(false);
|
ContactUserStore.syncing(false);
|
||||||
console.error(e);
|
console.error(e);
|
||||||
fResultFunc?.(Notification.UnknownError);
|
fResultFunc?.(Notifications.UnknownError);
|
||||||
}
|
}
|
||||||
}, 'ContactsSync');
|
}, 'ContactsSync');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,11 +199,11 @@ FolderUserStore = new class {
|
||||||
|
|
||||||
saveSystemFolders(folders) {
|
saveSystemFolders(folders) {
|
||||||
folders = folders || {
|
folders = folders || {
|
||||||
Sent: FolderUserStore.sentFolder(),
|
sent: FolderUserStore.sentFolder(),
|
||||||
Drafts: FolderUserStore.draftsFolder(),
|
drafts: FolderUserStore.draftsFolder(),
|
||||||
Spam: FolderUserStore.spamFolder(),
|
junk: FolderUserStore.spamFolder(),
|
||||||
Trash: FolderUserStore.trashFolder(),
|
trash: FolderUserStore.trashFolder(),
|
||||||
Archive: FolderUserStore.archiveFolder()
|
archive: FolderUserStore.archiveFolder()
|
||||||
};
|
};
|
||||||
forEachObjectEntry(folders, (k,v)=>Settings.set(k+'Folder',v));
|
forEachObjectEntry(folders, (k,v)=>Settings.set(k+'Folder',v));
|
||||||
rl.app.Remote.request('SystemFoldersUpdate', null, folders);
|
rl.app.Remote.request('SystemFoldersUpdate', null, folders);
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,15 @@ import { showScreenPopup } from 'Knoin/Knoin';
|
||||||
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
|
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
|
||||||
import { AskPopupView } from 'View/Popup/Ask';
|
import { AskPopupView } from 'View/Popup/Ask';
|
||||||
|
|
||||||
|
import { Passphrases } from 'Storage/Passphrases';
|
||||||
|
|
||||||
const
|
const
|
||||||
passphrases = new Map(),
|
|
||||||
askPassphrase = async (privateKey, btnTxt = 'LABEL_SIGN') => {
|
askPassphrase = async (privateKey, btnTxt = 'LABEL_SIGN') => {
|
||||||
const key = privateKey.id,
|
const key = privateKey.id,
|
||||||
pass = passphrases.has(key)
|
pass = Passphrases.has(key)
|
||||||
? {password:passphrases.get(key), remember:false}
|
? {password:Passphrases.get(key), remember:false}
|
||||||
: await AskPopupView.password('GnuPG key<br>' + key + ' ' + privateKey.emails[0], 'OPENPGP/'+btnTxt);
|
: await AskPopupView.password('GnuPG key<br>' + key + ' ' + privateKey.emails[0], 'OPENPGP/'+btnTxt);
|
||||||
pass && pass.remember && passphrases.set(key, pass.password);
|
pass && pass.remember && Passphrases.set(key, pass.password);
|
||||||
return pass.password;
|
return pass.password;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -83,7 +84,7 @@ export const GnuPGUserStore = new class {
|
||||||
key.armor = oData.Result;
|
key.armor = oData.Result;
|
||||||
showScreenPopup(OpenPgpKeyPopupView, [key]);
|
showScreenPopup(OpenPgpKeyPopupView, [key]);
|
||||||
} else {
|
} else {
|
||||||
passphrases.delete(key.id);
|
Passphrases.delete(key.id);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
keyId: key.id,
|
keyId: key.id,
|
||||||
|
|
@ -198,7 +199,7 @@ export const GnuPGUserStore = new class {
|
||||||
if (result?.Result && false !== result.Result.data) {
|
if (result?.Result && false !== result.Result.data) {
|
||||||
return result.Result;
|
return result.Result;
|
||||||
}
|
}
|
||||||
passphrases.delete(key.id);
|
Passphrases.delete(key.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,25 +30,21 @@ export const MessageUserStore = new class {
|
||||||
AppUserStore.focusedState(Scope.MessageList);
|
AppUserStore.focusedState(Scope.MessageList);
|
||||||
exitFullscreen();
|
exitFullscreen();
|
||||||
}
|
}
|
||||||
this.hideMessageBodies();
|
[...(this.bodiesDom()?.children || [])].forEach(el => el.hidden = true);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.purgeMessageBodyCache = this.purgeMessageBodyCache.throttle(30000);
|
this.purgeCache = this.purgeCache.throttle(30000);
|
||||||
}
|
}
|
||||||
|
|
||||||
purgeMessageBodyCache() {
|
purgeCache(all) {
|
||||||
const messagesDom = this.bodiesDom(),
|
const children = this.bodiesDom()?.children || [];
|
||||||
children = messagesDom?.children;
|
let i = Math.max(0, children.length - (all ? 0 : 15));
|
||||||
if (children) {
|
while (i--) {
|
||||||
while (15 < children.length) {
|
children[i].remove();
|
||||||
children[0].remove();
|
if (children[i].message) {
|
||||||
|
children[i].message.body = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hideMessageBodies() {
|
|
||||||
const messagesDom = this.bodiesDom();
|
|
||||||
messagesDom && Array.from(messagesDom.children).forEach(el => el.hidden = true);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,16 @@
|
||||||
import { koComputable, addObservablesTo, addComputablesTo } from 'External/ko';
|
import { koComputable, addObservablesTo, addComputablesTo } from 'External/ko';
|
||||||
|
|
||||||
import { SMAudio } from 'Common/Audio';
|
import { SMAudio } from 'Common/Audio';
|
||||||
import { Notification } from 'Common/Enums';
|
import { Notifications } from 'Common/Enums';
|
||||||
import { MessageSetAction } from 'Common/EnumsUser';
|
import { MessageSetAction } from 'Common/EnumsUser';
|
||||||
import { $htmlCL } from 'Common/Globals';
|
import { $htmlCL, fireEvent } from 'Common/Globals';
|
||||||
import { arrayLength, pInt, pString } from 'Common/Utils';
|
import { arrayLength, pString } from 'Common/Utils';
|
||||||
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getFolderInboxName,
|
getFolderInboxName,
|
||||||
getFolderFromCacheList,
|
getFolderFromCacheList,
|
||||||
setFolderHash,
|
setFolderETag
|
||||||
MessageFlagsCache
|
|
||||||
} from 'Common/Cache';
|
} from 'Common/Cache';
|
||||||
|
|
||||||
import { mailBox } from 'Common/Links';
|
import { mailBox } from 'Common/Links';
|
||||||
|
|
@ -174,7 +173,7 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrentFolderCach
|
||||||
let iOffset = (MessagelistUserStore.page() - 1) * SettingsUserStore.messagesPerPage();
|
let iOffset = (MessagelistUserStore.page() - 1) * SettingsUserStore.messagesPerPage();
|
||||||
|
|
||||||
if (bDropCurrentFolderCache) {
|
if (bDropCurrentFolderCache) {
|
||||||
setFolderHash(FolderUserStore.currentFolderFullName(), '');
|
setFolderETag(FolderUserStore.currentFolderFullName(), '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bDropPagePosition) {
|
if (bDropPagePosition) {
|
||||||
|
|
@ -198,7 +197,7 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrentFolderCach
|
||||||
let error = '';
|
let error = '';
|
||||||
if (iError) {
|
if (iError) {
|
||||||
error = getNotification(iError);
|
error = getNotification(iError);
|
||||||
if (Notification.RequestAborted !== iError) {
|
if (Notifications.RequestAborted !== iError) {
|
||||||
MessagelistUserStore([]);
|
MessagelistUserStore([]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -207,27 +206,24 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrentFolderCach
|
||||||
error = '';
|
error = '';
|
||||||
|
|
||||||
const
|
const
|
||||||
folder = getFolderFromCacheList(collection.folder),
|
folderInfo = collection.folder,
|
||||||
folderInfo = collection.folderInfo;
|
folder = getFolderFromCacheList(folderInfo.name);
|
||||||
|
collection.folder = folderInfo.name;
|
||||||
if (folder && !bCached) {
|
if (folder && !bCached) {
|
||||||
// folder.revivePropertiesFromJson(result);
|
// folder.revivePropertiesFromJson(result);
|
||||||
folder.expires = Date.now();
|
folder.expires = Date.now();
|
||||||
folder.uidNext = folderInfo.uidNext;
|
folder.uidNext = folderInfo.uidNext;
|
||||||
folder.hash = collection.folderHash;
|
folder.etag = folderInfo.etag;
|
||||||
|
|
||||||
if (null != folderInfo.totalEmails) {
|
if (null != folderInfo.totalEmails) {
|
||||||
folder.totalEmails(folderInfo.totalEmails);
|
folder.totalEmails(folderInfo.totalEmails);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null != folderInfo.unreadEmails) {
|
if (null != folderInfo.unreadEmails) {
|
||||||
if (pInt(folder.unreadEmails()) !== pInt(folderInfo.unreadEmails)) {
|
|
||||||
MessageFlagsCache.clearFolder(folder.fullName);
|
|
||||||
}
|
|
||||||
folder.unreadEmails(folderInfo.unreadEmails);
|
folder.unreadEmails(folderInfo.unreadEmails);
|
||||||
}
|
}
|
||||||
|
|
||||||
folder.flags(folderInfo.flags);
|
let flags = folderInfo.permanentFlags || [];
|
||||||
let flags = folderInfo.permanentFlags;
|
|
||||||
if (flags.includes('\\*')) {
|
if (flags.includes('\\*')) {
|
||||||
let i = 6;
|
let i = 6;
|
||||||
while (--i) {
|
while (--i) {
|
||||||
|
|
@ -251,14 +247,14 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrentFolderCach
|
||||||
MessagelistUserStore.threadUid(collection.threadUid);
|
MessagelistUserStore.threadUid(collection.threadUid);
|
||||||
|
|
||||||
MessagelistUserStore.endHash(
|
MessagelistUserStore.endHash(
|
||||||
collection.folder +
|
folderInfo.name +
|
||||||
'|' + collection.search +
|
'|' + collection.search +
|
||||||
'|' + MessagelistUserStore.threadUid() +
|
'|' + MessagelistUserStore.threadUid() +
|
||||||
'|' + MessagelistUserStore.page()
|
'|' + MessagelistUserStore.page()
|
||||||
);
|
);
|
||||||
MessagelistUserStore.endThreadUid(collection.threadUid);
|
MessagelistUserStore.endThreadUid(collection.threadUid);
|
||||||
const message = MessageUserStore.message();
|
const message = MessageUserStore.message();
|
||||||
if (message && collection.folder !== message.folder) {
|
if (message && folderInfo.name !== message.folder) {
|
||||||
MessageUserStore.message(null);
|
MessageUserStore.message(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,7 +276,7 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrentFolderCach
|
||||||
} else {
|
} else {
|
||||||
MessagelistUserStore.count(0);
|
MessagelistUserStore.count(0);
|
||||||
MessagelistUserStore([]);
|
MessagelistUserStore([]);
|
||||||
error = getNotification(Notification.CantGetMessageList);
|
error = getNotification(Notifications.CantGetMessageList);
|
||||||
}
|
}
|
||||||
MessagelistUserStore.error(error);
|
MessagelistUserStore.error(error);
|
||||||
}
|
}
|
||||||
|
|
@ -306,22 +302,38 @@ MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => {
|
||||||
messages = messages || MessagelistUserStore.listChecked();
|
messages = messages || MessagelistUserStore.listChecked();
|
||||||
|
|
||||||
let folder,
|
let folder,
|
||||||
alreadyUnread = 0,
|
rootUids = [],
|
||||||
rootUids = messages.map(oMessage => oMessage?.uid).validUnique(),
|
length;
|
||||||
|
|
||||||
|
if (iSetAction == MessageSetAction.SetSeen) {
|
||||||
|
messages.forEach(oMessage =>
|
||||||
|
oMessage.isUnseen() && rootUids.push(oMessage.uid) && oMessage.flags.push('\\seen')
|
||||||
|
);
|
||||||
|
} else if (iSetAction == MessageSetAction.UnsetSeen) {
|
||||||
|
messages.forEach(oMessage =>
|
||||||
|
!oMessage.isUnseen() && rootUids.push(oMessage.uid) && oMessage.flags.remove('\\seen')
|
||||||
|
);
|
||||||
|
} else if (iSetAction == MessageSetAction.SetFlag) {
|
||||||
|
messages.forEach(oMessage =>
|
||||||
|
!oMessage.isFlagged() && rootUids.push(oMessage.uid) && oMessage.flags.push('\\flagged')
|
||||||
|
);
|
||||||
|
} else if (iSetAction == MessageSetAction.UnsetFlag) {
|
||||||
|
messages.forEach(oMessage =>
|
||||||
|
oMessage.isFlagged() && rootUids.push(oMessage.uid) && oMessage.flags.remove('\\flagged')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
rootUids = rootUids.validUnique();
|
||||||
length = rootUids.length;
|
length = rootUids.length;
|
||||||
|
|
||||||
if (sFolderFullName && length) {
|
if (sFolderFullName && length) {
|
||||||
rootUids.forEach(sSubUid =>
|
|
||||||
alreadyUnread += MessageFlagsCache.storeBySetAction(sFolderFullName, sSubUid, iSetAction)
|
|
||||||
);
|
|
||||||
switch (iSetAction) {
|
switch (iSetAction) {
|
||||||
case MessageSetAction.SetSeen:
|
case MessageSetAction.SetSeen:
|
||||||
length = 0;
|
length = -length;
|
||||||
// fallthrough is intentionally
|
// fallthrough is intentionally
|
||||||
case MessageSetAction.UnsetSeen:
|
case MessageSetAction.UnsetSeen:
|
||||||
folder = getFolderFromCacheList(sFolderFullName);
|
folder = getFolderFromCacheList(sFolderFullName);
|
||||||
if (folder) {
|
if (folder) {
|
||||||
folder.unreadEmails(folder.unreadEmails() - alreadyUnread + length);
|
folder.unreadEmails(Math.max(0, folder.unreadEmails() + length));
|
||||||
}
|
}
|
||||||
Remote.request('MessageSetSeen', null, {
|
Remote.request('MessageSetSeen', null, {
|
||||||
folder: sFolderFullName,
|
folder: sFolderFullName,
|
||||||
|
|
@ -340,8 +352,6 @@ MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => {
|
||||||
break;
|
break;
|
||||||
// no default
|
// no default
|
||||||
}
|
}
|
||||||
|
|
||||||
MessagelistUserStore.reloadFlagsAndCachedMessage();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -370,22 +380,20 @@ MessagelistUserStore.removeMessagesFromList = (
|
||||||
messages.forEach(item => item?.isUnseen() && ++unseenCount);
|
messages.forEach(item => item?.isUnseen() && ++unseenCount);
|
||||||
|
|
||||||
if (fromFolder) {
|
if (fromFolder) {
|
||||||
fromFolder.hash = '';
|
fromFolder.etag = '';
|
||||||
if (!copy) {
|
if (!copy) {
|
||||||
fromFolder.totalEmails(
|
fromFolder.totalEmails(
|
||||||
0 <= fromFolder.totalEmails() - oUids.size ? fromFolder.totalEmails() - oUids.size : 0
|
0 <= fromFolder.totalEmails() - oUids.size ? fromFolder.totalEmails() - oUids.size : 0
|
||||||
);
|
);
|
||||||
|
|
||||||
if (0 < unseenCount) {
|
if (0 < unseenCount) {
|
||||||
fromFolder.unreadEmails(
|
fromFolder.unreadEmails(Math.max(0, fromFolder.unreadEmails() - unseenCount));
|
||||||
0 <= fromFolder.unreadEmails() - unseenCount ? fromFolder.unreadEmails() - unseenCount : 0
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toFolder) {
|
if (toFolder) {
|
||||||
toFolder.hash = '';
|
toFolder.etag = '';
|
||||||
|
|
||||||
if (trashFolder === toFolder.fullName || spamFolder === toFolder.fullName) {
|
if (trashFolder === toFolder.fullName || spamFolder === toFolder.fullName) {
|
||||||
unseenCount = 0;
|
unseenCount = 0;
|
||||||
|
|
@ -405,6 +413,18 @@ MessagelistUserStore.removeMessagesFromList = (
|
||||||
} else {
|
} else {
|
||||||
MessagelistUserStore.isIncomplete(true);
|
MessagelistUserStore.isIncomplete(true);
|
||||||
|
|
||||||
|
// Select next email https://github.com/the-djmaze/snappymail/issues/968
|
||||||
|
if (currentMessage && 1 == messages.length && SettingsUserStore.showNextMessage()) {
|
||||||
|
let next = MessagelistUserStore.indexOf(currentMessage) + 1;
|
||||||
|
if (0 < next && (next = MessagelistUserStore()[next])) {
|
||||||
|
currentMessage = null;
|
||||||
|
fireEvent('mailbox.message.show', {
|
||||||
|
folder: next.folder,
|
||||||
|
uid: next.uid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
messages.forEach(item => {
|
messages.forEach(item => {
|
||||||
if (currentMessage && currentMessage.hash === item.hash) {
|
if (currentMessage && currentMessage.hash === item.hash) {
|
||||||
currentMessage = null;
|
currentMessage = null;
|
||||||
|
|
@ -455,9 +475,4 @@ MessagelistUserStore.removeMessagesFromList = (
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
MessagelistUserStore.reloadFlagsAndCachedMessage = () => {
|
|
||||||
MessagelistUserStore.forEach(message => MessageFlagsCache.initMessage(message));
|
|
||||||
MessageFlagsCache.initMessage(MessageUserStore.message());
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import * as Links from 'Common/Links';
|
import { staticLink } from 'Common/Links';
|
||||||
import { addObservablesTo } from 'External/ko';
|
import { addObservablesTo } from 'External/ko';
|
||||||
import { fireEvent } from 'Common/Globals';
|
import { fireEvent } from 'Common/Globals';
|
||||||
|
|
||||||
|
|
@ -22,15 +22,19 @@ let DesktopNotifications = false,
|
||||||
WorkerNotifications = navigator.serviceWorker;
|
WorkerNotifications = navigator.serviceWorker;
|
||||||
|
|
||||||
// Are Notifications supported in the service worker?
|
// Are Notifications supported in the service worker?
|
||||||
if (WorkerNotifications && ServiceWorkerRegistration && ServiceWorkerRegistration.prototype.showNotification) {
|
if (WorkerNotifications) {
|
||||||
|
if (ServiceWorkerRegistration && ServiceWorkerRegistration.prototype.showNotification) {
|
||||||
/* Listen for close requests from the ServiceWorker */
|
/* Listen for close requests from the ServiceWorker */
|
||||||
WorkerNotifications.addEventListener('message', event => {
|
WorkerNotifications.addEventListener('message', event => {
|
||||||
const obj = JSON.parse(event.data);
|
const obj = JSON.parse(event.data);
|
||||||
'notificationclick' === obj?.action && dispatchMessage(obj.data);
|
'notificationclick' === obj?.action && dispatchMessage(obj.data);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
console.log('ServiceWorkerRegistration.showNotification undefined');
|
||||||
WorkerNotifications = null;
|
WorkerNotifications = null;
|
||||||
console.log('ServiceWorker Notifications not supported');
|
}
|
||||||
|
} else {
|
||||||
|
console.log('ServiceWorker undefined');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NotificationUserStore = new class {
|
export const NotificationUserStore = new class {
|
||||||
|
|
@ -57,7 +61,7 @@ export const NotificationUserStore = new class {
|
||||||
if (DesktopNotifications && NotificationsGranted()) {
|
if (DesktopNotifications && NotificationsGranted()) {
|
||||||
const options = {
|
const options = {
|
||||||
body: text,
|
body: text,
|
||||||
icon: imageSrc || Links.staticLink('images/icon-message-notification.png'),
|
icon: imageSrc || staticLink('images/icon-message-notification.png'),
|
||||||
data: messageData
|
data: messageData
|
||||||
};
|
};
|
||||||
if (messageData?.uid) {
|
if (messageData?.uid) {
|
||||||
|
|
@ -65,8 +69,7 @@ export const NotificationUserStore = new class {
|
||||||
}
|
}
|
||||||
if (WorkerNotifications) {
|
if (WorkerNotifications) {
|
||||||
// Service-Worker-Allowed HTTP header to allow the scope.
|
// Service-Worker-Allowed HTTP header to allow the scope.
|
||||||
WorkerNotifications.register('/serviceworker.js')
|
WorkerNotifications.register(staticLink('js/serviceworker.js'), {scope:'/'})
|
||||||
// WorkerNotifications.register(Links.staticLink('js/serviceworker.js'), {scope:'/'})
|
|
||||||
.then(() =>
|
.then(() =>
|
||||||
WorkerNotifications.ready.then(registration =>
|
WorkerNotifications.ready.then(registration =>
|
||||||
/* Show the notification */
|
/* Show the notification */
|
||||||
|
|
@ -80,7 +83,10 @@ export const NotificationUserStore = new class {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.catch(e => console.error(e));
|
.catch(e => {
|
||||||
|
console.error(e);
|
||||||
|
WorkerNotifications = null;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const notification = new HTML5Notification(title, options);
|
const notification = new HTML5Notification(title, options);
|
||||||
notification.show?.();
|
notification.show?.();
|
||||||
|
|
|
||||||
|
|
@ -12,22 +12,23 @@ import { showScreenPopup } from 'Knoin/Knoin';
|
||||||
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
|
import { OpenPgpKeyPopupView } from 'View/Popup/OpenPgpKey';
|
||||||
import { AskPopupView } from 'View/Popup/Ask';
|
import { AskPopupView } from 'View/Popup/Ask';
|
||||||
|
|
||||||
|
import { Passphrases } from 'Storage/Passphrases';
|
||||||
|
|
||||||
const
|
const
|
||||||
findOpenPGPKey = (keys, query/*, sign*/) =>
|
findOpenPGPKey = (keys, query/*, sign*/) =>
|
||||||
keys.find(key =>
|
keys.find(key =>
|
||||||
key.emails.includes(query) || query == key.id || query == key.fingerprint
|
key.emails.includes(query) || query == key.id || query == key.fingerprint
|
||||||
),
|
),
|
||||||
|
|
||||||
passphrases = new Map(),
|
|
||||||
decryptKey = async (privateKey, btnTxt = 'LABEL_SIGN') => {
|
decryptKey = async (privateKey, btnTxt = 'LABEL_SIGN') => {
|
||||||
if (privateKey.key.isDecrypted()) {
|
if (privateKey.key.isDecrypted()) {
|
||||||
return privateKey.key;
|
return privateKey.key;
|
||||||
}
|
}
|
||||||
const key = privateKey.id,
|
const key = privateKey.id,
|
||||||
pass = passphrases.has(key)
|
pass = Passphrases.has(key)
|
||||||
? {password:passphrases.get(key), remember:false}
|
? {password:Passphrases.get(key), remember:false}
|
||||||
: await AskPopupView.password(
|
: await AskPopupView.password(
|
||||||
'OpenPGP.js key<br>' + privateKey.id + ' ' + privateKey.emails[0],
|
'OpenPGP.js key<br>' + key + ' ' + privateKey.emails[0],
|
||||||
'OPENPGP/'+btnTxt
|
'OPENPGP/'+btnTxt
|
||||||
);
|
);
|
||||||
if (pass) {
|
if (pass) {
|
||||||
|
|
@ -36,7 +37,7 @@ const
|
||||||
privateKey: privateKey.key,
|
privateKey: privateKey.key,
|
||||||
passphrase
|
passphrase
|
||||||
});
|
});
|
||||||
result && pass.remember && passphrases.set(key, passphrase);
|
result && pass.remember && Passphrases.set(key, passphrase);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,15 @@ import { SettingsCapa, SettingsGet } from 'Common/Globals';
|
||||||
import { GnuPGUserStore } from 'Stores/User/GnuPG';
|
import { GnuPGUserStore } from 'Stores/User/GnuPG';
|
||||||
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
|
||||||
|
|
||||||
|
// https://mailvelope.github.io/mailvelope/Keyring.html
|
||||||
|
let mailvelopeKeyring = null;
|
||||||
|
|
||||||
export const
|
export const
|
||||||
BEGIN_PGP_MESSAGE = '-----BEGIN PGP MESSAGE-----',
|
BEGIN_PGP_MESSAGE = '-----BEGIN PGP MESSAGE-----',
|
||||||
// BEGIN_PGP_SIGNATURE = '-----BEGIN PGP SIGNATURE-----',
|
// BEGIN_PGP_SIGNATURE = '-----BEGIN PGP SIGNATURE-----',
|
||||||
// BEGIN_PGP_SIGNED = '-----BEGIN PGP SIGNED MESSAGE-----',
|
// BEGIN_PGP_SIGNED = '-----BEGIN PGP SIGNED MESSAGE-----',
|
||||||
|
|
||||||
PgpUserStore = new class {
|
PgpUserStore = new class {
|
||||||
constructor() {
|
|
||||||
// https://mailvelope.github.io/mailvelope/Keyring.html
|
|
||||||
this.mailvelopeKeyring = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
if (SettingsCapa('OpenPGP') && window.crypto && crypto.getRandomValues) {
|
if (SettingsCapa('OpenPGP') && window.crypto && crypto.getRandomValues) {
|
||||||
rl.loadScript(SettingsGet('StaticLibsJs').replace('/libs.', '/openpgp.'))
|
rl.loadScript(SettingsGet('StaticLibsJs').replace('/libs.', '/openpgp.'))
|
||||||
|
|
@ -38,7 +36,7 @@ export const
|
||||||
identifier = identifier || SettingsGet('Email');
|
identifier = identifier || SettingsGet('Email');
|
||||||
if (window.mailvelope) {
|
if (window.mailvelope) {
|
||||||
const fn = keyring => {
|
const fn = keyring => {
|
||||||
this.mailvelopeKeyring = keyring;
|
mailvelopeKeyring = keyring;
|
||||||
console.log('mailvelope ready');
|
console.log('mailvelope ready');
|
||||||
};
|
};
|
||||||
mailvelope.getKeyring().then(fn, err => {
|
mailvelope.getKeyring().then(fn, err => {
|
||||||
|
|
@ -79,8 +77,7 @@ export const
|
||||||
|
|
||||||
async mailvelopeHasPublicKeyForEmails(recipients) {
|
async mailvelopeHasPublicKeyForEmails(recipients) {
|
||||||
const
|
const
|
||||||
keyring = this.mailvelopeKeyring,
|
mailvelope = mailvelopeKeyring && await mailvelopeKeyring.validKeyForAddress(recipients)
|
||||||
mailvelope = keyring && await keyring.validKeyForAddress(recipients)
|
|
||||||
/*.then(LookupResult => Object.entries(LookupResult))*/,
|
/*.then(LookupResult => Object.entries(LookupResult))*/,
|
||||||
entries = mailvelope && Object.entries(mailvelope);
|
entries = mailvelope && Object.entries(mailvelope);
|
||||||
return entries && entries.filter(value => value[1]).length === recipients.length;
|
return entries && entries.filter(value => value[1]).length === recipients.length;
|
||||||
|
|
@ -104,8 +101,7 @@ export const
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMailvelopePrivateKeyFor(email/*, sign*/) {
|
async getMailvelopePrivateKeyFor(email/*, sign*/) {
|
||||||
let keyring = this.mailvelopeKeyring;
|
if (mailvelopeKeyring && await mailvelopeKeyring.hasPrivateKey({email:email})) {
|
||||||
if (keyring && await keyring.hasPrivateKey({email:email})) {
|
|
||||||
return ['mailvelope', email];
|
return ['mailvelope', email];
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -145,6 +141,7 @@ export const
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try Mailvelope (does not support inline images)
|
// Try Mailvelope (does not support inline images)
|
||||||
|
if (mailvelopeKeyring) {
|
||||||
try {
|
try {
|
||||||
let emails = [...message.from,...message.to,...message.cc].validUnique(),
|
let emails = [...message.from,...message.to,...message.cc].validUnique(),
|
||||||
i = emails.length;
|
i = emails.length;
|
||||||
|
|
@ -164,7 +161,7 @@ export const
|
||||||
let result = await mailvelope.createDisplayContainer(
|
let result = await mailvelope.createDisplayContainer(
|
||||||
'#'+body.id,
|
'#'+body.id,
|
||||||
armoredText,
|
armoredText,
|
||||||
this.mailvelopeKeyring,
|
mailvelopeKeyring,
|
||||||
{
|
{
|
||||||
senderAddress: sender
|
senderAddress: sender
|
||||||
// emails[i].email
|
// emails[i].email
|
||||||
|
|
@ -186,6 +183,7 @@ export const
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now try GnuPG
|
// Now try GnuPG
|
||||||
return GnuPGUserStore.decrypt(message);
|
return GnuPGUserStore.decrypt(message);
|
||||||
|
|
@ -217,19 +215,19 @@ export const
|
||||||
* So far this is only the autocrypt header.
|
* So far this is only the autocrypt header.
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
this.mailvelopeKeyring.additionalHeadersForOutgoingEmail(headers)
|
mailvelopeKeyring.additionalHeadersForOutgoingEmail(headers)
|
||||||
this.mailvelopeKeyring.addSyncHandler(syncHandlerObj)
|
mailvelopeKeyring.addSyncHandler(syncHandlerObj)
|
||||||
this.mailvelopeKeyring.createKeyBackupContainer(selector, options)
|
mailvelopeKeyring.createKeyBackupContainer(selector, options)
|
||||||
this.mailvelopeKeyring.createKeyGenContainer(selector, {
|
mailvelopeKeyring.createKeyGenContainer(selector, {
|
||||||
// userIds: [],
|
// userIds: [],
|
||||||
keySize: 4096
|
keySize: 4096
|
||||||
})
|
})
|
||||||
|
|
||||||
this.mailvelopeKeyring.exportOwnPublicKey(emailAddr).then(<AsciiArmored, Error>)
|
mailvelopeKeyring.exportOwnPublicKey(emailAddr).then(<AsciiArmored, Error>)
|
||||||
this.mailvelopeKeyring.importPublicKey(armored)
|
mailvelopeKeyring.importPublicKey(armored)
|
||||||
|
|
||||||
// https://mailvelope.github.io/mailvelope/global.html#SyncHandlerObject
|
// https://mailvelope.github.io/mailvelope/global.html#SyncHandlerObject
|
||||||
this.mailvelopeKeyring.addSyncHandler({
|
mailvelopeKeyring.addSyncHandler({
|
||||||
uploadSync
|
uploadSync
|
||||||
downloadSync
|
downloadSync
|
||||||
backup
|
backup
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export const SettingsUserStore = new class {
|
||||||
simpleAttachmentsList: 0,
|
simpleAttachmentsList: 0,
|
||||||
useCheckboxesInList: 1,
|
useCheckboxesInList: 1,
|
||||||
listGrouped: 0,
|
listGrouped: 0,
|
||||||
|
showNextMessage: 0,
|
||||||
allowDraftAutosave: 1,
|
allowDraftAutosave: 1,
|
||||||
useThreads: 0,
|
useThreads: 0,
|
||||||
replySameFolder: 0,
|
replySameFolder: 0,
|
||||||
|
|
@ -49,13 +50,12 @@ export const SettingsUserStore = new class {
|
||||||
|
|
||||||
self.init();
|
self.init();
|
||||||
|
|
||||||
self.usePreviewPane = koComputable(() => Layout.NoPreview !== self.layout() && !ThemeStore.isMobile());
|
self.usePreviewPane = koComputable(() => self.layout() && !ThemeStore.isMobile());
|
||||||
|
|
||||||
const toggleLayout = () => {
|
const toggleLayout = () => {
|
||||||
const value = ThemeStore.isMobile() ? Layout.NoPreview : self.layout();
|
const value = ThemeStore.isMobile() ? 0 : self.layout();
|
||||||
$htmlCL.toggle('rl-no-preview-pane', Layout.NoPreview === value);
|
$htmlCL.toggle('sm-msgView-side', Layout.SidePreview === value);
|
||||||
$htmlCL.toggle('rl-side-preview-pane', Layout.SidePreview === value);
|
$htmlCL.toggle('sm-msgView-bottom', Layout.BottomPreview === value);
|
||||||
$htmlCL.toggle('rl-bottom-preview-pane', Layout.BottomPreview === value);
|
|
||||||
fireEvent('rl-layout', value);
|
fireEvent('rl-layout', value);
|
||||||
};
|
};
|
||||||
self.layout.subscribe(toggleLayout);
|
self.layout.subscribe(toggleLayout);
|
||||||
|
|
@ -65,7 +65,7 @@ export const SettingsUserStore = new class {
|
||||||
let iAutoLogoutTimer;
|
let iAutoLogoutTimer;
|
||||||
self.delayLogout = (() => {
|
self.delayLogout = (() => {
|
||||||
clearTimeout(iAutoLogoutTimer);
|
clearTimeout(iAutoLogoutTimer);
|
||||||
if (0 < self.autoLogout() && !SettingsGet('AccountSignMe')) {
|
if (0 < self.autoLogout() && !SettingsGet('accountSignMe')) {
|
||||||
iAutoLogoutTimer = setTimeout(
|
iAutoLogoutTimer = setTimeout(
|
||||||
rl.app.logout,
|
rl.app.logout,
|
||||||
self.autoLogout() * 60000
|
self.autoLogout() * 60000
|
||||||
|
|
@ -96,6 +96,7 @@ export const SettingsUserStore = new class {
|
||||||
self.simpleAttachmentsList(SettingsGet('simpleAttachmentsList'));
|
self.simpleAttachmentsList(SettingsGet('simpleAttachmentsList'));
|
||||||
self.useCheckboxesInList(SettingsGet('UseCheckboxesInList'));
|
self.useCheckboxesInList(SettingsGet('UseCheckboxesInList'));
|
||||||
self.listGrouped(SettingsGet('listGrouped'));
|
self.listGrouped(SettingsGet('listGrouped'));
|
||||||
|
self.showNextMessage(SettingsGet('showNextMessage'));
|
||||||
self.allowDraftAutosave(SettingsGet('AllowDraftAutosave'));
|
self.allowDraftAutosave(SettingsGet('AllowDraftAutosave'));
|
||||||
self.useThreads(SettingsGet('UseThreads'));
|
self.useThreads(SettingsGet('UseThreads'));
|
||||||
self.replySameFolder(SettingsGet('ReplySameFolder'));
|
self.replySameFolder(SettingsGet('ReplySameFolder'));
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
:root {
|
:root {
|
||||||
--fontSans: Verdana, Geneva, "Bitstream Vera Sans", "DejaVu LGC Sans", Arial, sans-serif;
|
--fontSans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
--fontSerif: "Nimbus Roman No9 L", "Times New Roman", Times, FreeSerif, serif;
|
--fontSerif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||||
--fontMono: "Liberation Mono", Monaco, Menlo, Consolas, "Courier New", FreeMono, Courier, monospace;
|
--fontMono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rl-app.fontArial {
|
#rl-app.fontArial {
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,14 @@
|
||||||
height: calc(100% - 50px - @rlLowMargin);
|
height: calc(100% - 50px - @rlLowMargin);
|
||||||
margin: 50px @rlLowMargin 0 0;
|
margin: 50px @rlLowMargin 0 0;
|
||||||
|
|
||||||
|
background-color: var(--panel-bg-clr, #fff);
|
||||||
|
border: 1px solid var(--border-color, #aaa);
|
||||||
|
border-radius: var(--border-radius, 5px);
|
||||||
|
box-shadow: var(--smMainShadow);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 20px;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
|
||||||
> .b-toolbar {
|
> .b-toolbar {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -56,28 +64,13 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rl-mobile #V-AdminPane {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn.btn-logout {
|
.btn.btn-logout {
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
padding-right: 12px;
|
padding-right: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rl-settings-subscreen {
|
|
||||||
background-color: var(--settings-bg-clr, #fff);
|
|
||||||
border: 1px solid var(--border-color, #aaa);
|
|
||||||
border-radius: var(--border-radius, 5px);
|
|
||||||
box-shadow: var(--smMainShadow);
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 20px;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* desktop */
|
/* desktop */
|
||||||
@media screen and (min-width: 1000px) {
|
@media screen and (min-width: @maxMobileWidth + 1px) {
|
||||||
.toggleLeft {
|
.toggleLeft {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
@ -90,16 +83,16 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* mobile and tablet */
|
@media screen and (max-width: @maxMobileWidth) {
|
||||||
@media screen and (max-width: 999px) {
|
|
||||||
#rl-settings-subscreen {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rl-right {
|
#rl-right {
|
||||||
min-width: calc(100% - @rlLowMargin);
|
min-width: calc(100% - @rlLowMargin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#V-AdminPane {
|
||||||
|
margin-right: 0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
html.rl-left-panel-disabled {
|
html.rl-left-panel-disabled {
|
||||||
#rl-left {
|
#rl-left {
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 733px) {
|
@media screen and (max-width: 480px) {
|
||||||
#V-PopupsLanguages label {
|
#V-PopupsLanguages label {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@glass-color: #fff;
|
|
||||||
@glass-error-color: #f76260;
|
|
||||||
@glass-m-color: rgba(255, 255, 255, .8);
|
|
||||||
|
|
||||||
.LoginView {
|
.LoginView {
|
||||||
|
|
||||||
.descWrapper {
|
.descWrapper {
|
||||||
|
|
@ -29,18 +25,18 @@
|
||||||
border: var(--login-border, 1px solid rgba(255, 255, 255, .2));
|
border: var(--login-border, 1px solid rgba(255, 255, 255, .2));
|
||||||
border-radius: var(--login-border-radius, 7px);
|
border-radius: var(--login-border-radius, 7px);
|
||||||
box-shadow: var(--login-box-shadow);
|
box-shadow: var(--login-box-shadow);
|
||||||
color: var(--login-color, @glass-m-color);
|
color: var(--login-color, rgba(255, 255, 255, .8));
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 40px 40px 20px 40px;
|
padding: 40px 40px 20px 40px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
* {
|
* {
|
||||||
color: var(--login-color, @glass-m-color);
|
color: var(--login-color, rgba(255, 255, 255, .8));
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls.error * {
|
.controls.error * {
|
||||||
color: @glass-error-color;
|
color: #f76260;
|
||||||
border-color: @glass-error-color;
|
border-color: #f76260;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.submitting > * {
|
&.submitting > * {
|
||||||
|
|
@ -77,17 +73,17 @@
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
|
|
||||||
&:placeholder {
|
&:placeholder {
|
||||||
color: @glass-color;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus, &:hover {
|
&:focus, &:hover {
|
||||||
border-color: @glass-color;
|
border-color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input, .btn {
|
input, .btn {
|
||||||
background: none;
|
background: none;
|
||||||
border: 1px solid @glass-m-color;
|
border: 1px solid rgba(255, 255, 255, .8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.fontastic + input {
|
.fontastic + input {
|
||||||
|
|
@ -107,7 +103,7 @@
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|
||||||
&:hover, &:active {
|
&:hover, &:active {
|
||||||
border-color: @glass-color;
|
border-color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ html.list-loading body {
|
||||||
cursor: progress;
|
cursor: progress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1000px) {
|
@media screen and (min-width: @maxMobileWidth + 1px) {
|
||||||
#rl-app {
|
#rl-app {
|
||||||
background-image: var(--main-bg-image);
|
background-image: var(--main-bg-image);
|
||||||
background-size: var(--main-bg-size);
|
background-size: var(--main-bg-size);
|
||||||
|
|
@ -73,14 +73,15 @@ dialog:not(.animate) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://github.com/the-djmaze/snappymail/issues/686
|
* https://github.com/the-djmaze/snappymail/issues/686
|
||||||
@media screen and (max-width: 999px) {
|
|
||||||
*/
|
*/
|
||||||
.rl-mobile dialog {
|
@media screen and (max-width: @maxMobileWidth) {
|
||||||
|
dialog {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
/* max-height: calc(100vh - 86px);*/
|
/* max-height: calc(100vh - 86px);*/
|
||||||
/* max-height: 100dvh;*/
|
/* max-height: 100dvh;*/
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dialog > header {
|
dialog > header {
|
||||||
padding: 9px 15px;
|
padding: 9px 15px;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
#rl-settings-subscreen,
|
|
||||||
.g-ui-user-select-none {
|
.g-ui-user-select-none {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-touch-callout: none;
|
-webkit-touch-callout: none;
|
||||||
|
|
@ -159,20 +158,7 @@ summary.legend {
|
||||||
padding: 0 9px;
|
padding: 0 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert a:not(.close) {
|
@media screen and (max-width: @maxMobileWidth) {
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert.alert-info a:not(.close) {
|
|
||||||
color: var(--info-clr, #3a87ad);
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert.alert-error a:not(.close) {
|
|
||||||
color: var(--error-clr, #b94a48);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* mobile and tablet */
|
|
||||||
@media screen and (max-width: 999px) {
|
|
||||||
.form-horizontal {
|
.form-horizontal {
|
||||||
.control-group {
|
.control-group {
|
||||||
> label {
|
> label {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
margin-right:1em;
|
margin-right:1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 410px) {
|
@media screen and (max-width: 480px) {
|
||||||
#advancedsearchform label {
|
#advancedsearchform label {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
100% {background-position: 60px 0;}
|
100% {background-position: 60px 0;}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1000px) {
|
@media screen and (min-width: @maxMobileWidth + 1px) {
|
||||||
|
|
||||||
.b-folders li .anim-action-class {
|
.b-folders li .anim-action-class {
|
||||||
animation: highlight-folder-row 0.5s linear;
|
animation: highlight-folder-row 0.5s linear;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
color: #666;
|
opacity: 0.5;
|
||||||
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,18 +131,20 @@
|
||||||
top: 5px;
|
top: 5px;
|
||||||
bottom: 5px;
|
bottom: 5px;
|
||||||
|
|
||||||
border: 2px #777 dashed;
|
border: 2px var(--border-color, #ddd) dashed;
|
||||||
z-index: 300;
|
z-index: 300;
|
||||||
|
|
||||||
line-height: 119px;
|
line-height: 119px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
background-color: #efefef;
|
background-color: var(--dialog-bg-clr, #fff);
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|
||||||
|
opacity: 0.9;
|
||||||
&.dragAndDropOver {
|
&.dragAndDropOver {
|
||||||
background-color: #fff;
|
border-color: var(--main-color, #fff);
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html:not(rl-mobile) {
|
@media screen and (min-width: @maxMobileWidth + 1px) {
|
||||||
.hideContactListCheckbox {
|
.hideContactListCheckbox {
|
||||||
.checkboxItem {
|
.checkboxItem {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
@ -203,7 +203,7 @@ html:not(rl-mobile) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 900px) {
|
@media screen and (max-width: @maxMobileWidth) {
|
||||||
#V-PopupsContacts {
|
#V-PopupsContacts {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.focused .b-folders a.focused {
|
||||||
|
background-color: var(--folders-focused-bg-color, #333);
|
||||||
|
border-left-color: #fff;
|
||||||
|
color: var(--folders-focused-color, #eee);
|
||||||
|
}
|
||||||
|
|
||||||
.b-folders {
|
.b-folders {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -38,7 +44,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.b-toolbar {
|
.b-toolbar {
|
||||||
padding: 10px 0 10px @rlLowMargin;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +61,6 @@
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
border-color: #999;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
|
|
@ -101,22 +105,12 @@
|
||||||
background-color: var(--folders-selected-bg-color, #333);
|
background-color: var(--folders-selected-bg-color, #333);
|
||||||
color: var(--folders-selected-color, #eee);
|
color: var(--folders-selected-color, #eee);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.focused {
|
|
||||||
background-color: var(--folders-focused-bg-color, #333);
|
|
||||||
color: var(--folders-focused-color, #eee);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&:not(.selectable) {
|
&:not(.selectable) {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.focused {
|
|
||||||
background-color: #888;
|
|
||||||
border-left-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.unread-sub {
|
&.unread-sub {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
@ -213,24 +207,19 @@
|
||||||
/**/
|
/**/
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
#rl-left .buttonCompose {
|
||||||
&.buttonCompose {
|
|
||||||
max-height: 2em;
|
max-height: 2em;
|
||||||
max-width: fit-content;
|
max-width: fit-content;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: calc(100% - 48px);
|
width: calc(100% - 48px);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
html.rl-left-panel-disabled {
|
html.rl-left-panel-disabled {
|
||||||
.buttonComposeText{
|
.buttonComposeText,
|
||||||
|
.buttonContacts {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.btn.buttonCompose {
|
#rl-left .buttonCompose {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
.btn.buttonContacts {
|
|
||||||
margin-top: 10px !important;
|
|
||||||
margin-left: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,10 @@
|
||||||
/* transition: width 0.3s ease-out;*/
|
/* transition: width 0.3s ease-out;*/
|
||||||
width: @rlLeftWidth;
|
width: @rlLeftWidth;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
|
||||||
|
.b-toolbar {
|
||||||
|
padding: 10px 0 10px @rlLowMargin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#rl-right:not([hidden]) {
|
#rl-right:not([hidden]) {
|
||||||
|
|
@ -19,9 +23,6 @@
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
width: 20%;
|
width: 20%;
|
||||||
}
|
}
|
||||||
.rl-bottom-preview-pane #rl-right {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
.resizable::after {
|
.resizable::after {
|
||||||
|
|
@ -41,47 +42,12 @@
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.resizer {
|
|
||||||
background: #aaa;
|
|
||||||
background: rgba(255,255,255,0.5);
|
|
||||||
display: none;
|
|
||||||
opacity: 0;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 102;
|
|
||||||
}
|
|
||||||
.resizer:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
html:not(.rl-left-panel-disabled) #rl-left {
|
html:not(.rl-left-panel-disabled) #rl-left {
|
||||||
resize: horizontal; overflow: auto;
|
resize: horizontal; overflow: auto;
|
||||||
min-width: 155px;
|
min-width: 155px;
|
||||||
max-width: 350px;
|
max-width: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rl-left > .resizer,
|
|
||||||
.rl-side-preview-pane #V-MailMessageList .resizer {
|
|
||||||
cursor: ew-resize;
|
|
||||||
cursor: col-resize;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 5px;
|
|
||||||
}
|
|
||||||
.rl-bottom-preview-pane #V-MailMessageList .resizer {
|
|
||||||
cursor: ns-resize;
|
|
||||||
cursor: row-resize;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html:not(.rl-left-panel-disabled) #rl-left > .resizer,
|
|
||||||
#V-MailMessageList .resizer {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
#top-system-dropdown-id::after,
|
#top-system-dropdown-id::after,
|
||||||
#button-add-prop-dropdown-id::after {
|
#button-add-prop-dropdown-id::after {
|
||||||
content: '▼';
|
content: '▼';
|
||||||
|
|
@ -140,32 +106,6 @@ html:not(.rl-left-panel-disabled) #rl-left > .resizer,
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* desktop */
|
|
||||||
@media screen and (min-width: 1000px) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/* desktop-large */
|
|
||||||
@media screen and (min-width: 1401px) {
|
|
||||||
#rl-left {
|
|
||||||
width: @rlLeftWidth + 20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* mobile and tablet */
|
|
||||||
@media screen and (max-width: 999px) {
|
|
||||||
#rl-left {
|
|
||||||
width: 155px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rl-settings-subscreen {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown-menu a {
|
|
||||||
line-height: 2.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html:not(.rl-ctrl-key-pressed) .visible-on-ctrl-btn,
|
html:not(.rl-ctrl-key-pressed) .visible-on-ctrl-btn,
|
||||||
html.rl-ctrl-key-pressed .hidden-on-ctrl-btn,
|
html.rl-ctrl-key-pressed .hidden-on-ctrl-btn,
|
||||||
.show-on-panel-disabled {
|
.show-on-panel-disabled {
|
||||||
|
|
@ -191,32 +131,6 @@ html.rl-left-panel-disabled {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html.rl-mobile #rl-left > .resizer,
|
|
||||||
html.rl-no-preview-pane #rl-right .resizer {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.rl-mobile {
|
|
||||||
#rl-left {
|
|
||||||
background: var(--main-bg-color, #aaa);
|
|
||||||
border-right: 1px solid var(--border-color, #ddd);
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 50vw;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
&.rl-left-panel-disabled #rl-left {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html.rl-mobile .hide-mobile,
|
|
||||||
html:not(.rl-mobile) .show-mobile {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.e-paginator {
|
.e-paginator {
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
@ -237,3 +151,85 @@ html:not(.rl-mobile) .show-mobile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resizer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @maxMobileWidth) {
|
||||||
|
#rl-app {
|
||||||
|
#V-SettingsPane {
|
||||||
|
margin-right: 0;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#rl-left {
|
||||||
|
background: var(--main-bg-color, #aaa);
|
||||||
|
border-right: 1px solid var(--border-color, #ddd);
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 50vw;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.rl-left-panel-disabled #rl-left {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu a {
|
||||||
|
line-height: 2.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide-mobile {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* desktop */
|
||||||
|
@media screen and (min-width: @maxMobileWidth + 1px) {
|
||||||
|
.toggleLeft,
|
||||||
|
#V-MailMessageList .buttonCompose {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm-msgView-bottom #rl-right {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizer {
|
||||||
|
background: #aaa;
|
||||||
|
background: rgba(255,255,255,0.5);
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 102;
|
||||||
|
}
|
||||||
|
.resizer:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
html:not(.rl-left-panel-disabled) #rl-left > .resizer,
|
||||||
|
.sm-msgView-side #V-MailMessageList .resizer {
|
||||||
|
cursor: ew-resize;
|
||||||
|
cursor: col-resize;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
width: 5px;
|
||||||
|
}
|
||||||
|
.sm-msgView-bottom #V-MailMessageList .resizer {
|
||||||
|
cursor: ns-resize;
|
||||||
|
cursor: row-resize;
|
||||||
|
display: block;
|
||||||
|
left: 0;
|
||||||
|
height: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* desktop-large */
|
||||||
|
@media screen and (min-width: 1401px) {
|
||||||
|
#rl-left {
|
||||||
|
width: @rlLeftWidth + 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,9 @@
|
||||||
|
|
||||||
html.rl-mobile, html.rl-no-preview-pane {
|
|
||||||
.message-selected #V-MailMessageList {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#V-MailMessageList {
|
#V-MailMessageList {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
|
||||||
.rl-side-preview-pane #V-MailMessageList {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
.rl-no-preview-pane #V-MailMessageList {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.rl-side-preview-pane #V-MailMessageList .messageList {
|
|
||||||
max-width: 50vw;
|
|
||||||
min-width: 320px;
|
|
||||||
overflow: auto;
|
|
||||||
resize: horizontal;
|
|
||||||
width: 35vw;
|
|
||||||
}
|
|
||||||
.rl-bottom-preview-pane #V-MailMessageList .messageList {
|
|
||||||
height: 35vh;
|
|
||||||
max-height: 50vh;
|
|
||||||
min-height: 200px;
|
|
||||||
overflow: auto;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
#V-MailMessageList.focused .messageList {
|
#V-MailMessageList.focused .messageList {
|
||||||
border-color: #9d9d9d;
|
border-color: #9d9d9d;
|
||||||
|
|
@ -47,7 +22,7 @@ html.rl-mobile, html.rl-no-preview-pane {
|
||||||
|
|
||||||
.messageList {
|
.messageList {
|
||||||
height: calc(100% - 60px);
|
height: calc(100% - 60px);
|
||||||
background-color: #fff;
|
background-color: var(--panel-bg-clr, #fff);
|
||||||
border: 1px solid var(--border-color, #aaa);
|
border: 1px solid var(--border-color, #aaa);
|
||||||
border-radius: var(--border-radius, 5px);
|
border-radius: var(--border-radius, 5px);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -196,18 +171,6 @@ html.rl-mobile, html.rl-no-preview-pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html:not(rl-mobile) {
|
|
||||||
.hideMessageListCheckbox {
|
|
||||||
.checkboxCheckAll {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageCheckbox {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageListItem > div + div {
|
.messageListItem > div + div {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -396,20 +359,55 @@ html:not(rl-mobile) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1000px) {
|
html:not(.sm-msgView-bottom):not(.sm-msgView-side) .message-selected #V-MailMessageList {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: @maxMobileWidth) {
|
||||||
|
#V-MailMessageList .btn-toolbar:not(.hasChecked) .onCheckedShow,
|
||||||
|
#V-MailMessageList .hasChecked .onCheckedHide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: @maxMobileWidth + 1px) {
|
||||||
.messageList {
|
.messageList {
|
||||||
.listDragOver {
|
.listDragOver {
|
||||||
transition: all 400ms ease;
|
transition: all 400ms ease;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hideMessageListCheckbox {
|
||||||
|
.checkboxCheckAll {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.checkboxMessage {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* desktop-large */
|
.sm-msgView-side #V-MailMessageList {
|
||||||
@media screen and (min-width: 1401px) {
|
width: unset;
|
||||||
.messageListItem {
|
|
||||||
font-size: 13px;
|
.messageList {
|
||||||
time {
|
max-width: 50vw;
|
||||||
font-size: 13px;
|
min-width: 320px;
|
||||||
|
overflow: auto;
|
||||||
|
resize: horizontal;
|
||||||
|
width: 35vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sm-msgView-bottom #V-MailMessageList {
|
||||||
|
height: unset;
|
||||||
|
width: unset;
|
||||||
|
|
||||||
|
.messageList {
|
||||||
|
height: 35vh;
|
||||||
|
max-height: 50vh;
|
||||||
|
min-height: 200px;
|
||||||
|
overflow: auto;
|
||||||
|
resize: vertical;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -425,7 +423,7 @@ html:not(rl-mobile) {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rl-side-preview-pane, .rl-mobile {
|
.sm-msgView-side, .rl-mobile {
|
||||||
|
|
||||||
.messageListItem > div + div {
|
.messageListItem > div + div {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
|
||||||
|
|
@ -30,28 +30,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html.rl-no-preview-pane {
|
|
||||||
#rl-right:not(.message-selected) #V-MailMessageView {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#V-MailMessageView .top-toolbar {
|
#V-MailMessageView .top-toolbar {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
}
|
visibility: visible;
|
||||||
html.rl-no-preview-pane #V-MailMessageView .top-toolbar {
|
|
||||||
padding-left: 1px;
|
|
||||||
}
|
|
||||||
html:not(.rl-mobile):not(.rl-no-preview-pane) #V-MailMessageView .top-toolbar {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
|
||||||
display: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.messageView {
|
.messageView {
|
||||||
background-color: #fff;
|
background-color: var(--panel-bg-clr, #fff);
|
||||||
border: 1px solid var(--border-color, #aaa);
|
border: 1px solid var(--border-color, #aaa);
|
||||||
border-radius: var(--border-radius, 5px);
|
border-radius: var(--border-radius, 5px);
|
||||||
height: calc(100% - 50px);
|
height: calc(100% - 50px);
|
||||||
|
|
@ -140,7 +126,7 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
|
|
||||||
.messageItemHeader {
|
.messageItemHeader {
|
||||||
|
|
||||||
background-color: #f8f8f8;
|
background-color: var(--message-header-bg-clr, #eee);
|
||||||
border-bottom: 1px solid var(--border-color, #bbb);
|
border-bottom: 1px solid var(--border-color, #bbb);
|
||||||
border-radius: var(--border-radius, 5px) var(--border-radius, 5px) 0 0;
|
border-radius: var(--border-radius, 5px) var(--border-radius, 5px) 0 0;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
@ -215,7 +201,6 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
}
|
}
|
||||||
|
|
||||||
.openpgp-control {
|
.openpgp-control {
|
||||||
margin: 0.5em;
|
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
|
|
@ -248,9 +233,6 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.b-text-part > iframe {
|
|
||||||
min-height: 50vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thread-controls {
|
.thread-controls {
|
||||||
.dropdown-toggle {
|
.dropdown-toggle {
|
||||||
|
|
@ -271,7 +253,6 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
|
|
||||||
#messageItem {
|
#messageItem {
|
||||||
|
|
||||||
color: #000;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
|
@ -283,11 +264,13 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
padding: 50px 0;
|
padding: 50px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bodySubHeader {
|
||||||
|
background-color: var(--message-header-bg-clr, #eee);
|
||||||
|
}
|
||||||
.bodySubHeader > * {
|
.bodySubHeader > * {
|
||||||
background-color: #eee;
|
|
||||||
border-bottom: 1px solid var(--border-color, #bbb);
|
border-bottom: 1px solid var(--border-color, #bbb);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 10px 15px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pgpInfo {
|
.pgpInfo {
|
||||||
|
|
@ -300,14 +283,8 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.readReceipt {
|
|
||||||
background-color: #ffffd9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachmentsPlace {
|
.attachmentsPlace {
|
||||||
|
|
||||||
padding: 10px 10px 6px 10px;
|
|
||||||
background: #eee;
|
|
||||||
border-bottom: 1px solid var(--border-color, #bbb);
|
border-bottom: 1px solid var(--border-color, #bbb);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|
@ -336,9 +313,8 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
}
|
}
|
||||||
|
|
||||||
.attachmentsControls {
|
.attachmentsControls {
|
||||||
padding: 7px 5px 7px 14px;
|
padding-top: 7px;
|
||||||
background: #e8e8e8;
|
border-top: 1px solid var(--border-color, #bbb);
|
||||||
border-bottom: 1px solid var(--border-color, #bbb);
|
|
||||||
}
|
}
|
||||||
.attachmentsControls > * {
|
.attachmentsControls > * {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
@ -365,8 +341,12 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: unset;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: blue;
|
color: var(--link-color, blue);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|
||||||
&:visited {
|
&:visited {
|
||||||
|
|
@ -382,6 +362,10 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.html {
|
&.html {
|
||||||
|
|
@ -471,15 +455,34 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html.rl-no-preview-pane .messageView {
|
.mailvelope iframe {
|
||||||
border: 1px solid var(--border-color, #aaa);
|
min-height: 400px;
|
||||||
box-shadow: var(--smMainShadow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
html.rl-bottom-preview-pane .messageView {
|
html:not(.sm-msgView-bottom):not(.sm-msgView-side) {
|
||||||
|
#rl-right:not(.message-selected) #V-MailMessageView {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1400px) {
|
||||||
|
html.sm-msgView-side #V-MailMessageView .top-toolbar {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html.sm-msgView-bottom {
|
||||||
|
#V-MailMessageView .top-toolbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.messageView {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@media all and (display-mode: fullscreen) {
|
||||||
|
*/
|
||||||
html.rl-fullscreen {
|
html.rl-fullscreen {
|
||||||
|
|
||||||
#rl-left,
|
#rl-left,
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#V-PopupsOpenPgpKey {
|
#V-PopupsOpenPgpKey {
|
||||||
.key-viewer {
|
pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
|
padding: 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,9 +14,8 @@
|
||||||
#V-PopupsOpenPgpImport {
|
#V-PopupsOpenPgpImport {
|
||||||
max-width: 645px;
|
max-width: 645px;
|
||||||
|
|
||||||
.inputKey {
|
textarea {
|
||||||
font-family: var(--fontMono);
|
font-family: var(--fontMono);
|
||||||
width: 600px;
|
width: 600px;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,5 @@
|
||||||
|
|
||||||
#V-SettingsMenu {
|
#V-SettingsMenu {
|
||||||
|
|
||||||
.b-footer {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 10px;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
padding: 0 10px 0 5px;
|
|
||||||
z-index: 101;
|
|
||||||
}
|
|
||||||
|
|
||||||
.b-content {
|
|
||||||
position: absolute;
|
|
||||||
top: 50px + @rlLowMargin + 10px;
|
|
||||||
bottom: @rlLowMargin;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav {
|
nav {
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
@ -51,15 +32,20 @@
|
||||||
#V-SettingsPane {
|
#V-SettingsPane {
|
||||||
height: calc(100% - 50px - @rlLowMargin);
|
height: calc(100% - 50px - @rlLowMargin);
|
||||||
margin: 50px @rlLowMargin 0 0;
|
margin: 50px @rlLowMargin 0 0;
|
||||||
|
padding:20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
background-color: var(--panel-bg-clr, #fff);
|
||||||
|
border: 1px solid var(--border-color, #aaa);
|
||||||
|
border-radius: var(--border-radius, 5px);
|
||||||
|
box-shadow: var(--smMainShadow);
|
||||||
|
overflow-y: auto;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
|
||||||
.btn-toolbar {
|
.btn-toolbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 8px;
|
||||||
right: 0;
|
left: @rlLowMargin;
|
||||||
left: 0;
|
|
||||||
padding: 8px 0;
|
|
||||||
color: #fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
|
|
@ -68,22 +54,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rl-mobile #V-SettingsPane {
|
.firefox-drag #V-SettingsPane,
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#rl-settings-subscreen {
|
|
||||||
background-color: var(--settings-bg-clr, #fff);
|
|
||||||
border: 1px solid var(--border-color, #aaa);
|
|
||||||
border-radius: var(--border-radius, 5px);
|
|
||||||
box-shadow: var(--smMainShadow);
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding:20px;
|
|
||||||
scroll-behavior: smooth;
|
|
||||||
}
|
|
||||||
|
|
||||||
.firefox-drag #rl-settings-subscreen,
|
|
||||||
.firefox-drag .modal-body {
|
.firefox-drag .modal-body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
|
|
||||||
#top-system-dropdown-id {
|
#top-system-dropdown-id {
|
||||||
overflow: hidden;
|
padding: 0 6px;
|
||||||
padding-left: 8px;
|
|
||||||
padding-right: 6px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rl-left-panel-disabled #more-list-dropdown-id + .dropdown-menu {
|
.rl-left-panel-disabled #more-list-dropdown-id + .dropdown-menu {
|
||||||
|
|
@ -50,16 +48,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.accountPlace {
|
.accountPlace {
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
border-right: 1px solid var(--btn-border-clr, rgba(0, 0, 0, 0.2));
|
||||||
border-radius: 4px;
|
|
||||||
color: #fff;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
line-height: 29px;
|
line-height: 22px;
|
||||||
|
margin: 0 6px -3px 0;
|
||||||
max-width: 25vw;
|
max-width: 25vw;
|
||||||
overflow: hidden;
|
overflow-x: hidden;
|
||||||
padding: 1px 8px;
|
padding-right: 6px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
text-shadow: 0 1px 0 #000;
|
text-shadow: 0 1px 0 #000;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
z-index: 2001;
|
z-index: 2001;
|
||||||
}
|
}
|
||||||
@media only screen and (max-width: 600px) {
|
@media screen and (max-width: @maxMobileWidth) {
|
||||||
[data-rainloopErrorTip]::before {
|
[data-rainloopErrorTip]::before {
|
||||||
min-width: 60vw;
|
min-width: 60vw;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,16 @@
|
||||||
@rlLeftWidth: 200px;
|
@rlLeftWidth: 200px;
|
||||||
@rlLowMargin: 8px;
|
@rlLowMargin: 8px;
|
||||||
|
|
||||||
|
/* mobile and tablet */
|
||||||
|
@maxMobileWidth: 799px;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--smDialogShrink: 20px;
|
--smDialogShrink: 20px;
|
||||||
--smMainShadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
--smMainShadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
.rl-mobile {
|
|
||||||
/* For Firefox */
|
/* For Firefox */
|
||||||
|
@media screen and (max-width: @maxMobileWidth) {
|
||||||
|
:root {
|
||||||
--smDialogShrink: 25px;
|
--smDialogShrink: 25px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Notification,
|
Notifications,
|
||||||
UploadErrorCode
|
UploadErrorCode
|
||||||
} from 'Common/Enums';
|
} from 'Common/Enums';
|
||||||
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
FolderType
|
FolderType
|
||||||
} from 'Common/EnumsUser';
|
} from 'Common/EnumsUser';
|
||||||
|
|
||||||
import { pInt, isArray, arrayLength } from 'Common/Utils';
|
import { pInt, isArray, arrayLength, b64Encode } from 'Common/Utils';
|
||||||
import { encodeHtml, HtmlEditor, htmlToPlain } from 'Common/Html';
|
import { encodeHtml, HtmlEditor, htmlToPlain } from 'Common/Html';
|
||||||
import { koArrayWithDestroy, addObservablesTo, addComputablesTo, addSubscribablesTo } from 'External/ko';
|
import { koArrayWithDestroy, addObservablesTo, addComputablesTo, addSubscribablesTo } from 'External/ko';
|
||||||
|
|
||||||
|
|
@ -19,8 +19,8 @@ import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
||||||
import { folderInformation, messagesDeleteHelper } from 'Common/Folders';
|
import { folderInformation, messagesDeleteHelper } from 'Common/Folders';
|
||||||
import { serverRequest } from 'Common/Links';
|
import { serverRequest } from 'Common/Links';
|
||||||
import { i18n, getNotification, getUploadErrorDescByCode, timestampToString } from 'Common/Translator';
|
import { i18n, getNotification, getUploadErrorDescByCode, timestampToString } from 'Common/Translator';
|
||||||
import { MessageFlagsCache, setFolderHash } from 'Common/Cache';
|
import { setFolderETag } from 'Common/Cache';
|
||||||
import { Settings, SettingsCapa, SettingsGet, elementById, addShortcut, createElement } from 'Common/Globals';
|
import { SettingsCapa, SettingsGet, elementById, addShortcut, createElement } from 'Common/Globals';
|
||||||
//import { exitFullscreen, isFullscreen, toggleFullscreen } from 'Common/Fullscreen';
|
//import { exitFullscreen, isFullscreen, toggleFullscreen } from 'Common/Fullscreen';
|
||||||
|
|
||||||
import { AppUserStore } from 'Stores/User/App';
|
import { AppUserStore } from 'Stores/User/App';
|
||||||
|
|
@ -57,7 +57,7 @@ const
|
||||||
|
|
||||||
tpl = createElement('template'),
|
tpl = createElement('template'),
|
||||||
|
|
||||||
base64_encode = text => btoa(unescape(encodeURIComponent(text))).match(/.{1,76}/g).join('\r\n'),
|
base64_encode = text => b64Encode(text).match(/.{1,76}/g).join('\r\n'),
|
||||||
|
|
||||||
getEmail = value => addressparser(value)[0]?.email || false,
|
getEmail = value => addressparser(value)[0]?.email || false,
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ const
|
||||||
reloadDraftFolder = () => {
|
reloadDraftFolder = () => {
|
||||||
const draftsFolder = FolderUserStore.draftsFolder();
|
const draftsFolder = FolderUserStore.draftsFolder();
|
||||||
if (draftsFolder && UNUSED_OPTION_VALUE !== draftsFolder) {
|
if (draftsFolder && UNUSED_OPTION_VALUE !== draftsFolder) {
|
||||||
setFolderHash(draftsFolder, '');
|
setFolderETag(draftsFolder, '');
|
||||||
if (FolderUserStore.currentFolderFullName() === draftsFolder) {
|
if (FolderUserStore.currentFolderFullName() === draftsFolder) {
|
||||||
MessagelistUserStore.reload(true);
|
MessagelistUserStore.reload(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -441,16 +441,6 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
this.sendError(false);
|
this.sendError(false);
|
||||||
this.sending(true);
|
this.sending(true);
|
||||||
|
|
||||||
if (3 === arrayLength(this.aDraftInfo)) {
|
|
||||||
const flagsCache = MessageFlagsCache.getFor(this.aDraftInfo[2], this.aDraftInfo[1]);
|
|
||||||
if (isArray(flagsCache)) {
|
|
||||||
flagsCache.push(('forward' === this.aDraftInfo[0]) ? '$forwarded' : '\\answered');
|
|
||||||
MessageFlagsCache.setFor(this.aDraftInfo[2], this.aDraftInfo[1], flagsCache);
|
|
||||||
MessagelistUserStore.reloadFlagsAndCachedMessage();
|
|
||||||
setFolderHash(this.aDraftInfo[2], '');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sSentFolder = UNUSED_OPTION_VALUE === sSentFolder ? '' : sSentFolder;
|
sSentFolder = UNUSED_OPTION_VALUE === sSentFolder ? '' : sSentFolder;
|
||||||
|
|
||||||
this.getMessageRequestParams(sSentFolder).then(params => {
|
this.getMessageRequestParams(sSentFolder).then(params => {
|
||||||
|
|
@ -458,19 +448,23 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
(iError, data) => {
|
(iError, data) => {
|
||||||
this.sending(false);
|
this.sending(false);
|
||||||
if (iError) {
|
if (iError) {
|
||||||
if (Notification.CantSaveMessage === iError) {
|
if (Notifications.CantSaveMessage === iError) {
|
||||||
this.sendSuccessButSaveError(true);
|
this.sendSuccessButSaveError(true);
|
||||||
this.savedErrorDesc(i18n('COMPOSE/SAVED_ERROR_ON_SEND').trim());
|
this.savedErrorDesc(i18n('COMPOSE/SAVED_ERROR_ON_SEND').trim());
|
||||||
} else {
|
} else {
|
||||||
this.sendError(true);
|
this.sendError(true);
|
||||||
this.sendErrorDesc(getNotification(iError, data?.ErrorMessage)
|
this.sendErrorDesc(getNotification(iError, data?.ErrorMessage)
|
||||||
|| getNotification(Notification.CantSendMessage));
|
|| getNotification(Notifications.CantSendMessage));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.close();
|
this.close();
|
||||||
}
|
}
|
||||||
setFolderHash(this.draftsFolder(), '');
|
setFolderETag(this.draftsFolder(), '');
|
||||||
setFolderHash(sSentFolder, '');
|
setFolderETag(sSentFolder, '');
|
||||||
|
if (3 === arrayLength(this.aDraftInfo)) {
|
||||||
|
const folder = this.aDraftInfo[2];
|
||||||
|
setFolderETag(folder, '');
|
||||||
|
}
|
||||||
reloadDraftFolder();
|
reloadDraftFolder();
|
||||||
},
|
},
|
||||||
params,
|
params,
|
||||||
|
|
@ -492,6 +486,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
saveCommand() {
|
saveCommand() {
|
||||||
|
if (!this.saving() && !this.sending()) {
|
||||||
if (FolderUserStore.draftsFolderNotEnabled()) {
|
if (FolderUserStore.draftsFolderNotEnabled()) {
|
||||||
showScreenPopup(FolderSystemPopupView, [FolderType.Drafts]);
|
showScreenPopup(FolderSystemPopupView, [FolderType.Drafts]);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -522,15 +517,15 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
this.savedTime(new Date);
|
this.savedTime(new Date);
|
||||||
|
|
||||||
if (this.bFromDraft) {
|
if (this.bFromDraft) {
|
||||||
setFolderHash(this.draftsFolder(), '');
|
setFolderETag(this.draftsFolder(), '');
|
||||||
}
|
}
|
||||||
setFolderHash(FolderUserStore.draftsFolder(), '');
|
setFolderETag(FolderUserStore.draftsFolder(), '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
this.savedError(true);
|
this.savedError(true);
|
||||||
this.savedErrorDesc(getNotification(Notification.CantSaveMessage));
|
this.savedErrorDesc(getNotification(Notifications.CantSaveMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
reloadDraftFolder();
|
reloadDraftFolder();
|
||||||
|
|
@ -541,10 +536,11 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.saving(false);
|
this.saving(false);
|
||||||
this.savedError(true);
|
this.savedError(true);
|
||||||
this.savedErrorDesc(getNotification(Notification.CantSaveMessage) + ': ' + e);
|
this.savedErrorDesc(getNotification(Notifications.CantSaveMessage) + ': ' + e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
deleteCommand() {
|
deleteCommand() {
|
||||||
AskPopupView.hidden()
|
AskPopupView.hidden()
|
||||||
|
|
@ -569,12 +565,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
skipCommand() {
|
skipCommand() {
|
||||||
ComposePopupView.inEdit(true);
|
ComposePopupView.inEdit(true);
|
||||||
|
|
||||||
if (
|
if (!FolderUserStore.draftsFolderNotEnabled() && SettingsUserStore.allowDraftAutosave()) {
|
||||||
!this.saving() &&
|
|
||||||
!this.sending() &&
|
|
||||||
!FolderUserStore.draftsFolderNotEnabled() &&
|
|
||||||
SettingsUserStore.allowDraftAutosave()
|
|
||||||
) {
|
|
||||||
this.saveCommand();
|
this.saveCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -597,8 +588,6 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
&& !FolderUserStore.draftsFolderNotEnabled()
|
&& !FolderUserStore.draftsFolderNotEnabled()
|
||||||
&& SettingsUserStore.allowDraftAutosave()
|
&& SettingsUserStore.allowDraftAutosave()
|
||||||
&& !this.isEmptyForm(false)
|
&& !this.isEmptyForm(false)
|
||||||
&& !this.saving()
|
|
||||||
&& !this.sending()
|
|
||||||
&& !this.savedError()
|
&& !this.savedError()
|
||||||
) {
|
) {
|
||||||
this.saveCommand();
|
this.saveCommand();
|
||||||
|
|
@ -617,7 +606,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
data.Result.map(item => (item?.[0] ? (new EmailModel(item[0], item[1])).toLine() : null))
|
data.Result.map(item => (item?.[0] ? (new EmailModel(item[0], item[1])).toLine() : null))
|
||||||
.filter(v => v)
|
.filter(v => v)
|
||||||
);
|
);
|
||||||
} else if (Notification.RequestAborted !== iError) {
|
} else if (Notifications.RequestAborted !== iError) {
|
||||||
fResponse([]);
|
fResponse([]);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -870,9 +859,9 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
|
|
||||||
// https://github.com/the-djmaze/snappymail/issues/491
|
// https://github.com/the-djmaze/snappymail/issues/491
|
||||||
tpl.innerHTML = message.bodyAsHTML();
|
tpl.innerHTML = message.bodyAsHTML();
|
||||||
tpl.content.querySelectorAll('img').forEach(img =>
|
tpl.content.querySelectorAll('img').forEach(img => {
|
||||||
img.dataset.xSrcCid || img.dataset.xSrc || img.replaceWith(img.alt || img.title)
|
img.src || img.dataset.xSrcCid || img.dataset.xSrc || img.replaceWith(img.alt || img.title)
|
||||||
);
|
});
|
||||||
sText = tpl.innerHTML.trim();
|
sText = tpl.innerHTML.trim();
|
||||||
|
|
||||||
switch (msgComposeType) {
|
switch (msgComposeType) {
|
||||||
|
|
@ -1010,7 +999,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
clickElement: dom.querySelector('#composeUploadButton'),
|
clickElement: dom.querySelector('#composeUploadButton'),
|
||||||
dragAndDropElement: dom.querySelector('.b-attachment-place')
|
dragAndDropElement: dom.querySelector('.b-attachment-place')
|
||||||
}),
|
}),
|
||||||
attachmentSizeLimit = pInt(SettingsGet('AttachmentLimit'));
|
attachmentSizeLimit = pInt(SettingsGet('attachmentLimit'));
|
||||||
|
|
||||||
oJua
|
oJua
|
||||||
.on('onDragEnter', () => {
|
.on('onDragEnter', () => {
|
||||||
|
|
@ -1124,12 +1113,12 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Settings.app('allowCtrlEnterOnCompose')) {
|
|
||||||
addShortcut('enter', 'meta', ScopeCompose, () => {
|
addShortcut('enter', 'meta', ScopeCompose, () => {
|
||||||
|
// if (SettingsUserStore.allowCtrlEnterOnCompose()) {
|
||||||
this.sendCommand();
|
this.sendCommand();
|
||||||
return false;
|
return false;
|
||||||
|
// }
|
||||||
});
|
});
|
||||||
}
|
|
||||||
addShortcut('mailsend', '', ScopeCompose, () => {
|
addShortcut('mailsend', '', ScopeCompose, () => {
|
||||||
this.sendCommand();
|
this.sendCommand();
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1297,7 +1286,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
*/
|
*/
|
||||||
let text = this.oEditor.getData(),
|
let text = this.oEditor.getData(),
|
||||||
encrypted = PgpUserStore.isEncrypted(text),
|
encrypted = PgpUserStore.isEncrypted(text),
|
||||||
size = SettingsGet('PhpUploadSizes')['post_max_size'],
|
size = SettingsGet('phpUploadSizes')['post_max_size'],
|
||||||
quota = pInt(size);
|
quota = pInt(size);
|
||||||
switch (size.slice(-1)) {
|
switch (size.slice(-1)) {
|
||||||
case 'G': quota *= 1024; // fallthrough
|
case 'G': quota *= 1024; // fallthrough
|
||||||
|
|
@ -1409,7 +1398,10 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
encrypt = this.pgpEncrypt() && this.canPgpEncrypt(),
|
encrypt = this.pgpEncrypt() && this.canPgpEncrypt(),
|
||||||
isHtml = this.oEditor.isHtml();
|
isHtml = this.oEditor.isHtml();
|
||||||
|
|
||||||
let Text = this.oEditor.getData();
|
let Text = this.oEditor.getData().trim();
|
||||||
|
if (!Text.length) {
|
||||||
|
throw 'Message body is empty';
|
||||||
|
}
|
||||||
if (isHtml) {
|
if (isHtml) {
|
||||||
let l;
|
let l;
|
||||||
do {
|
do {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { koComputable, addObservablesTo } from 'External/ko';
|
import { koComputable, addObservablesTo } from 'External/ko';
|
||||||
|
|
||||||
import { Notification } from 'Common/Enums';
|
import { Notifications } from 'Common/Enums';
|
||||||
import { defaultOptionsAfterRender } from 'Common/Utils';
|
import { defaultOptionsAfterRender } from 'Common/Utils';
|
||||||
import { folderListOptionsBuilder, sortFolders } from 'Common/Folders';
|
import { folderListOptionsBuilder, sortFolders } from 'Common/Folders';
|
||||||
import { getNotification } from 'Common/Translator';
|
import { getNotification } from 'Common/Translator';
|
||||||
|
|
@ -65,7 +65,7 @@ export class FolderCreatePopupView extends AbstractViewPopup {
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
FolderUserStore.folderListError(
|
FolderUserStore.folderListError(
|
||||||
getNotification(error.code, '', Notification.CantCreateFolder)
|
getNotification(error.code, '', Notifications.CantCreateFolder)
|
||||||
+ '.\n' + error.message);
|
+ '.\n' + error.message);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { addObservablesTo, addSubscribablesTo } from 'External/ko';
|
|
||||||
|
import { addObservablesTo } from 'External/ko';
|
||||||
|
|
||||||
import { getNotification } from 'Common/Translator';
|
import { getNotification } from 'Common/Translator';
|
||||||
|
|
||||||
|
|
@ -6,49 +7,34 @@ import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||||
|
|
||||||
|
import { IdentityModel } from 'Model/Identity';
|
||||||
|
|
||||||
export class IdentityPopupView extends AbstractViewPopup {
|
export class IdentityPopupView extends AbstractViewPopup {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('Identity');
|
super('Identity');
|
||||||
|
|
||||||
this.id = '';
|
|
||||||
addObservablesTo(this, {
|
addObservablesTo(this, {
|
||||||
|
id: '',
|
||||||
edit: false,
|
edit: false,
|
||||||
owner: false,
|
|
||||||
|
|
||||||
email: '',
|
email: '',
|
||||||
emailFocused: false,
|
emailFocused: false,
|
||||||
|
|
||||||
name: '',
|
name: '',
|
||||||
|
nameFocused: false,
|
||||||
|
|
||||||
replyTo: '',
|
replyTo: '',
|
||||||
replyToFocused: false,
|
showReplyTo: false,
|
||||||
|
|
||||||
bcc: '',
|
bcc: '',
|
||||||
bccFocused: false,
|
showBcc: false,
|
||||||
bccHasError: false,
|
|
||||||
|
|
||||||
signature: '',
|
signature: '',
|
||||||
signatureInsertBefore: false,
|
signatureInsertBefore: false,
|
||||||
|
|
||||||
showBcc: false,
|
|
||||||
showReplyTo: false,
|
|
||||||
|
|
||||||
submitRequest: false,
|
submitRequest: false,
|
||||||
submitError: ''
|
submitError: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
addSubscribablesTo(this, {
|
|
||||||
replyTo: value => {
|
|
||||||
if (false === this.showReplyTo() && value.length) {
|
|
||||||
this.showReplyTo(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
bcc: value => {
|
|
||||||
if (false === this.showBcc() && value.length) {
|
|
||||||
this.showBcc(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
/*
|
/*
|
||||||
this.email.valueHasMutated();
|
this.email.valueHasMutated();
|
||||||
this.replyTo.valueHasMutated();
|
this.replyTo.valueHasMutated();
|
||||||
|
|
@ -61,9 +47,8 @@ export class IdentityPopupView extends AbstractViewPopup {
|
||||||
this.signature?.__fetchEditorValue?.();
|
this.signature?.__fetchEditorValue?.();
|
||||||
this.submitRequest(true);
|
this.submitRequest(true);
|
||||||
const data = new FormData(form);
|
const data = new FormData(form);
|
||||||
data.set('Id', this.id);
|
data.set('Id', this.id());
|
||||||
data.set('Signature', this.signature());
|
data.set('Signature', this.signature());
|
||||||
data.set('SignatureInsertBefore', this.signatureInsertBefore() ? 1 : 0);
|
|
||||||
Remote.request('IdentityUpdate', iError => {
|
Remote.request('IdentityUpdate', iError => {
|
||||||
this.submitRequest(false);
|
this.submitRequest(false);
|
||||||
if (iError) {
|
if (iError) {
|
||||||
|
|
@ -89,32 +74,23 @@ export class IdentityPopupView extends AbstractViewPopup {
|
||||||
|
|
||||||
if (identity) {
|
if (identity) {
|
||||||
this.edit(true);
|
this.edit(true);
|
||||||
|
} else {
|
||||||
this.id = identity.id() || '';
|
this.edit(false);
|
||||||
|
identity = new IdentityModel;
|
||||||
|
identity.id(Jua.randomId());
|
||||||
|
}
|
||||||
|
this.id(identity.id() || '');
|
||||||
this.name(identity.name());
|
this.name(identity.name());
|
||||||
this.email(identity.email());
|
this.email(identity.email());
|
||||||
this.replyTo(identity.replyTo());
|
this.replyTo(identity.replyTo());
|
||||||
|
this.showReplyTo(0 < identity.replyTo().length);
|
||||||
this.bcc(identity.bcc());
|
this.bcc(identity.bcc());
|
||||||
|
this.showBcc(0 < identity.bcc().length);
|
||||||
this.signature(identity.signature());
|
this.signature(identity.signature());
|
||||||
this.signatureInsertBefore(identity.signatureInsertBefore());
|
this.signatureInsertBefore(identity.signatureInsertBefore());
|
||||||
|
|
||||||
this.owner(!this.id);
|
|
||||||
} else {
|
|
||||||
this.edit(false);
|
|
||||||
|
|
||||||
this.id = Jua.randomId();
|
|
||||||
this.name('');
|
|
||||||
this.email('');
|
|
||||||
this.replyTo('');
|
|
||||||
this.bcc('');
|
|
||||||
this.signature('');
|
|
||||||
this.signatureInsertBefore(false);
|
|
||||||
|
|
||||||
this.owner(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
afterShow() {
|
afterShow() {
|
||||||
this.owner() || this.emailFocused(true);
|
this.id() ? this.emailFocused(true) : this.nameFocused(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
import { koComputable } from 'External/ko';
|
|
||||||
|
|
||||||
import { convertLangName } from 'Common/Translator';
|
import { convertLangName } from 'Common/Translator';
|
||||||
|
|
||||||
|
|
@ -8,46 +7,19 @@ import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||||
export class LanguagesPopupView extends AbstractViewPopup {
|
export class LanguagesPopupView extends AbstractViewPopup {
|
||||||
constructor() {
|
constructor() {
|
||||||
super('Languages');
|
super('Languages');
|
||||||
|
|
||||||
this.fLang = null;
|
this.fLang = null;
|
||||||
this.userLanguage = ko.observable('');
|
this.languages = ko.observableArray();
|
||||||
|
|
||||||
this.langs = ko.observableArray();
|
|
||||||
|
|
||||||
this.languages = koComputable(() => {
|
|
||||||
const userLanguage = this.userLanguage();
|
|
||||||
return this.langs.map(language => ({
|
|
||||||
key: language,
|
|
||||||
user: language === userLanguage,
|
|
||||||
selected: ko.observable(false),
|
|
||||||
fullName: convertLangName(language)
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.langs.subscribe(() => this.setLanguageSelection());
|
|
||||||
}
|
|
||||||
|
|
||||||
languageTooltipName(language) {
|
|
||||||
return convertLangName(language, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
setLanguageSelection() {
|
|
||||||
const currentLang = this.fLang ? ko.unwrap(this.fLang) : '';
|
|
||||||
this.languages().forEach(item => item.selected(item.key === currentLang));
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeShow() {
|
|
||||||
this.fLang = null;
|
|
||||||
this.userLanguage('');
|
|
||||||
|
|
||||||
this.langs([]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onShow(fLanguage, langs, userLanguage) {
|
onShow(fLanguage, langs, userLanguage) {
|
||||||
this.fLang = fLanguage;
|
this.fLang = fLanguage;
|
||||||
this.userLanguage(userLanguage || '');
|
this.languages(langs.map(language => ({
|
||||||
|
key: language,
|
||||||
this.langs(langs);
|
user: userLanguage === language,
|
||||||
|
selected: fLanguage?.() === language,
|
||||||
|
fullName: convertLangName(language),
|
||||||
|
title: convertLangName(language, true)
|
||||||
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
changeLanguage(lang) {
|
changeLanguage(lang) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Notification } from 'Common/Enums';
|
import { Notifications } from 'Common/Enums';
|
||||||
import { ClientSideKeyNameLastSignMe } from 'Common/EnumsUser';
|
import { ClientSideKeyNameLastSignMe } from 'Common/EnumsUser';
|
||||||
import { SettingsGet, fireEvent } from 'Common/Globals';
|
import { SettingsGet, fireEvent } from 'Common/Globals';
|
||||||
import { getNotification, translatorReload, convertLangName } from 'Common/Translator';
|
import { getNotification, translatorReload, convertLangName } from 'Common/Translator';
|
||||||
|
|
@ -26,7 +26,7 @@ export class LoginUserView extends AbstractViewLogin {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
addObservablesTo(this, {
|
addObservablesTo(this, {
|
||||||
loadingDesc: SettingsGet('LoadingDescription'),
|
loadingDesc: SettingsGet('loadingDescription'),
|
||||||
|
|
||||||
email: SettingsGet('DevEmail'),
|
email: SettingsGet('DevEmail'),
|
||||||
password: SettingsGet('DevPassword'),
|
password: SettingsGet('DevPassword'),
|
||||||
|
|
@ -44,7 +44,7 @@ export class LoginUserView extends AbstractViewLogin {
|
||||||
signMeType: SignMeUnused
|
signMeType: SignMeUnused
|
||||||
});
|
});
|
||||||
|
|
||||||
this.allowLanguagesOnLogin = !!SettingsGet('AllowLanguagesOnLogin');
|
this.allowLanguagesOnLogin = !!SettingsGet('allowLanguagesOnLogin');
|
||||||
|
|
||||||
this.language = LanguageStore.language;
|
this.language = LanguageStore.language;
|
||||||
this.languages = LanguageStore.languages;
|
this.languages = LanguageStore.languages;
|
||||||
|
|
@ -110,8 +110,8 @@ export class LoginUserView extends AbstractViewLogin {
|
||||||
|
|
||||||
if (valid) {
|
if (valid) {
|
||||||
this.submitRequest(true);
|
this.submitRequest(true);
|
||||||
data.set('Language', this.bSendLanguage ? this.language() : '');
|
data.set('language', this.bSendLanguage ? this.language() : '');
|
||||||
data.set('SignMe', this.signMe() ? 1 : 0);
|
data.set('signMe', this.signMe() ? 1 : 0);
|
||||||
Remote.request('Login',
|
Remote.request('Login',
|
||||||
(iError, oData) => {
|
(iError, oData) => {
|
||||||
fireEvent('sm-user-login-response', {
|
fireEvent('sm-user-login-response', {
|
||||||
|
|
@ -120,11 +120,11 @@ export class LoginUserView extends AbstractViewLogin {
|
||||||
});
|
});
|
||||||
if (iError) {
|
if (iError) {
|
||||||
this.submitRequest(false);
|
this.submitRequest(false);
|
||||||
if (Notification.InvalidInputArgument == iError) {
|
if (Notifications.InvalidInputArgument == iError) {
|
||||||
iError = Notification.AuthError;
|
iError = Notifications.AuthError;
|
||||||
}
|
}
|
||||||
this.submitError(getNotification(iError, oData?.ErrorMessage,
|
this.submitError(getNotification(iError, oData?.ErrorMessage,
|
||||||
Notification.UnknownNotification));
|
Notifications.UnknownNotification));
|
||||||
this.submitErrorAdditional(oData?.ErrorMessageAdditional);
|
this.submitErrorAdditional(oData?.ErrorMessageAdditional);
|
||||||
} else {
|
} else {
|
||||||
rl.setData(oData.Result);
|
rl.setData(oData.Result);
|
||||||
|
|
@ -142,7 +142,7 @@ export class LoginUserView extends AbstractViewLogin {
|
||||||
onBuild(dom) {
|
onBuild(dom) {
|
||||||
super.onBuild(dom);
|
super.onBuild(dom);
|
||||||
|
|
||||||
const signMe = (SettingsGet('SignMe') || '').toLowerCase();
|
const signMe = (SettingsGet('signMe') || '').toLowerCase();
|
||||||
|
|
||||||
switch (signMe) {
|
switch (signMe) {
|
||||||
case 'defaultoff':
|
case 'defaultoff':
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
|
|
||||||
import { Scope } from 'Common/Enums';
|
import { Scope } from 'Common/Enums';
|
||||||
import { addShortcut } from 'Common/Globals';
|
import { addShortcut, stopEvent } from 'Common/Globals';
|
||||||
import { mailBox, settings } from 'Common/Links';
|
import { mailBox, settings } from 'Common/Links';
|
||||||
//import { setFolderHash } from 'Common/Cache';
|
//import { setFolderETag } from 'Common/Cache';
|
||||||
import { addComputablesTo } from 'External/ko';
|
import { addComputablesTo } from 'External/ko';
|
||||||
|
|
||||||
import { AppUserStore } from 'Stores/User/App';
|
import { AppUserStore } from 'Stores/User/App';
|
||||||
|
|
@ -80,8 +80,7 @@ export class MailFolderList extends AbstractViewLeft {
|
||||||
setExpandedFolder(folder.fullName, collapsed);
|
setExpandedFolder(folder.fullName, collapsed);
|
||||||
|
|
||||||
folder.collapsed(!collapsed);
|
folder.collapsed(!collapsed);
|
||||||
event.preventDefault();
|
stopEvent(event);
|
||||||
event.stopPropagation();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +104,7 @@ export class MailFolderList extends AbstractViewLeft {
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
if (folder.fullName === FolderUserStore.currentFolderFullName()) {
|
if (folder.fullName === FolderUserStore.currentFolderFullName()) {
|
||||||
setFolderHash(folder.fullName, '');
|
setFolderETag(folder.fullName, '');
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
let search = '';
|
let search = '';
|
||||||
|
|
@ -174,15 +173,6 @@ export class MailFolderList extends AbstractViewLeft {
|
||||||
moveAction(false);
|
moveAction(false);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
AppUserStore.focusedState.subscribe(value => {
|
|
||||||
let el = qs('li a.focused');
|
|
||||||
el?.classList.remove('focused');
|
|
||||||
if (Scope.FolderList === value) {
|
|
||||||
el = qs('li a.selected');
|
|
||||||
el?.classList.add('focused');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToFocused() {
|
scrollToFocused() {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { ComposeType, FolderType, MessageSetAction } from 'Common/EnumsUser';
|
||||||
import { doc,
|
import { doc,
|
||||||
leftPanelDisabled, toggleLeftPanel,
|
leftPanelDisabled, toggleLeftPanel,
|
||||||
Settings, SettingsCapa,
|
Settings, SettingsCapa,
|
||||||
addEventsListeners,
|
addEventsListeners, stopEvent,
|
||||||
addShortcut, registerShortcut, formFieldFocused
|
addShortcut, registerShortcut, formFieldFocused
|
||||||
} from 'Common/Globals';
|
} from 'Common/Globals';
|
||||||
|
|
||||||
|
|
@ -24,10 +24,7 @@ import { i18n } from 'Common/Translator';
|
||||||
|
|
||||||
import { dropFilesInFolder } from 'Common/Folders';
|
import { dropFilesInFolder } from 'Common/Folders';
|
||||||
|
|
||||||
import {
|
import { getFolderFromCacheList } from 'Common/Cache';
|
||||||
getFolderFromCacheList,
|
|
||||||
MessageFlagsCache
|
|
||||||
} from 'Common/Cache';
|
|
||||||
|
|
||||||
import { AppUserStore } from 'Stores/User/App';
|
import { AppUserStore } from 'Stores/User/App';
|
||||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
|
|
@ -90,7 +87,7 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
|
|
||||||
this.composeInEdit = ComposePopupView.inEdit;
|
this.composeInEdit = ComposePopupView.inEdit;
|
||||||
|
|
||||||
this.isMobile = ThemeStore.isMobile;
|
this.isMobile = ThemeStore.isMobile; // Obsolete
|
||||||
this.leftPanelDisabled = leftPanelDisabled;
|
this.leftPanelDisabled = leftPanelDisabled;
|
||||||
this.toggleLeftPanel = toggleLeftPanel;
|
this.toggleLeftPanel = toggleLeftPanel;
|
||||||
|
|
||||||
|
|
@ -146,10 +143,6 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
return c && MessagelistUserStore().length > c;
|
return c && MessagelistUserStore().length > c;
|
||||||
},
|
},
|
||||||
|
|
||||||
mobileCheckedStateShow: () => ThemeStore.isMobile() ? MessagelistUserStore.hasChecked() : 1,
|
|
||||||
|
|
||||||
mobileCheckedStateHide: () => ThemeStore.isMobile() ? !MessagelistUserStore.hasChecked() : 1,
|
|
||||||
|
|
||||||
listGrouped: () => {
|
listGrouped: () => {
|
||||||
let uid = MessagelistUserStore.threadUid(),
|
let uid = MessagelistUserStore.threadUid(),
|
||||||
sort = FolderUserStore.sortMode() || 'DATE';
|
sort = FolderUserStore.sortMode() || 'DATE';
|
||||||
|
|
@ -179,7 +172,7 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
rtf = Intl.RelativeTimeFormat
|
rtf = Intl.RelativeTimeFormat
|
||||||
? new Intl.RelativeTimeFormat(doc.documentElement.lang, { numeric: "auto" }) : 0;
|
? new Intl.RelativeTimeFormat(doc.documentElement.lang, { numeric: "auto" }) : 0;
|
||||||
MessagelistUserStore.forEach(msg => {
|
MessagelistUserStore.forEach(msg => {
|
||||||
let dt = (new Date(msg.dateTimeStampInUTC() * 1000)),
|
let dt = (new Date(msg.dateTimestamp() * 1000)),
|
||||||
date,
|
date,
|
||||||
ymd = Ymd(dt);
|
ymd = Ymd(dt);
|
||||||
if (!current || ymd != current.id) {
|
if (!current || ymd != current.id) {
|
||||||
|
|
@ -367,13 +360,19 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download selected messages
|
||||||
|
*/
|
||||||
downloadZipCommand() {
|
downloadZipCommand() {
|
||||||
let hashes = []/*, uids = []*/;
|
let hashes = []/*, uids = []*/;
|
||||||
// MessagelistUserStore.forEach(message => message.checked() && uids.push(message.uid));
|
// MessagelistUserStore.forEach(message => message.checked() && uids.push(message.uid));
|
||||||
MessagelistUserStore.forEach(message => message.checked() && hashes.push(message.requestHash));
|
MessagelistUserStore.forEach(message => message.checked() && hashes.push(message.requestHash));
|
||||||
downloadZip(hashes, null, null, MessagelistUserStore().folder);
|
downloadZip(null, hashes, null, null, MessagelistUserStore().folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download attachments of selected messages
|
||||||
|
*/
|
||||||
downloadAttachCommand() {
|
downloadAttachCommand() {
|
||||||
let hashes = [];
|
let hashes = [];
|
||||||
MessagelistUserStore.forEach(message => {
|
MessagelistUserStore.forEach(message => {
|
||||||
|
|
@ -385,7 +384,7 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
downloadZip(hashes);
|
downloadZip(null, hashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteWithoutMoveCommand() {
|
deleteWithoutMoveCommand() {
|
||||||
|
|
@ -410,10 +409,9 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
}
|
}
|
||||||
|
|
||||||
moveCommand(vm, event) {
|
moveCommand(vm, event) {
|
||||||
if (this.mobileCheckedStateShow()) {
|
if (MessagelistUserStore.hasChecked()) {
|
||||||
if (vm && event?.preventDefault) {
|
if (vm && event?.preventDefault) {
|
||||||
event.preventDefault();
|
stopEvent(event);
|
||||||
event.stopPropagation();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let b = moveAction();
|
let b = moveAction();
|
||||||
|
|
@ -475,15 +473,11 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
folder.unreadEmails(0);
|
folder.unreadEmails(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageFlagsCache.clearFolder(sFolderFullName);
|
|
||||||
|
|
||||||
Remote.request('MessageSetSeenToAll', null, {
|
Remote.request('MessageSetSeenToAll', null, {
|
||||||
folder: sFolderFullName,
|
folder: sFolderFullName,
|
||||||
setAction: 1,
|
setAction: 1,
|
||||||
threadUids: uids.join(',')
|
threadUids: uids.join(',')
|
||||||
});
|
});
|
||||||
|
|
||||||
MessagelistUserStore.reloadFlagsAndCachedMessage();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -574,7 +568,7 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
fToggle = () => {
|
fToggle = () => {
|
||||||
let layout = SettingsUserStore.layout();
|
let layout = SettingsUserStore.layout();
|
||||||
setLayoutResizer(top, ClientSideKeyNameMessageListSize,
|
setLayoutResizer(top, ClientSideKeyNameMessageListSize,
|
||||||
(ThemeStore.isMobile() || Layout.NoPreview === layout)
|
(ThemeStore.isMobile() || !layout)
|
||||||
? 0
|
? 0
|
||||||
: (Layout.SidePreview === layout ? 'Width' : 'Height')
|
: (Layout.SidePreview === layout ? 'Width' : 'Height')
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,11 @@ import {
|
||||||
|
|
||||||
import {
|
import {
|
||||||
elementById,
|
elementById,
|
||||||
leftPanelDisabled,
|
|
||||||
keyScopeReal,
|
keyScopeReal,
|
||||||
Settings,
|
Settings,
|
||||||
SettingsCapa,
|
SettingsCapa,
|
||||||
fireEvent,
|
fireEvent,
|
||||||
|
stopEvent,
|
||||||
addShortcut,
|
addShortcut,
|
||||||
registerShortcut
|
registerShortcut
|
||||||
} from 'Common/Globals';
|
} from 'Common/Globals';
|
||||||
|
|
@ -30,15 +30,12 @@ import { SMAudio } from 'Common/Audio';
|
||||||
|
|
||||||
import { i18n } from 'Common/Translator';
|
import { i18n } from 'Common/Translator';
|
||||||
|
|
||||||
import { MessageFlagsCache } from 'Common/Cache';
|
|
||||||
|
|
||||||
import { AppUserStore } from 'Stores/User/App';
|
import { AppUserStore } from 'Stores/User/App';
|
||||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||||
import { AccountUserStore } from 'Stores/User/Account';
|
import { AccountUserStore } from 'Stores/User/Account';
|
||||||
import { FolderUserStore, isAllowedKeyword } from 'Stores/User/Folder';
|
import { FolderUserStore, isAllowedKeyword } from 'Stores/User/Folder';
|
||||||
import { MessageUserStore } from 'Stores/User/Message';
|
import { MessageUserStore } from 'Stores/User/Message';
|
||||||
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||||
import { ThemeStore } from 'Stores/Theme';
|
|
||||||
|
|
||||||
import * as Local from 'Storage/Client';
|
import * as Local from 'Storage/Client';
|
||||||
|
|
||||||
|
|
@ -128,7 +125,6 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
|
|
||||||
this.message = currentMessage;
|
this.message = currentMessage;
|
||||||
this.messageLoadingThrottle = MessageUserStore.loading;
|
this.messageLoadingThrottle = MessageUserStore.loading;
|
||||||
this.messagesBodiesDom = MessageUserStore.bodiesDom;
|
|
||||||
this.messageError = MessageUserStore.error;
|
this.messageError = MessageUserStore.error;
|
||||||
|
|
||||||
this.fullScreenMode = isFullscreen;
|
this.fullScreenMode = isFullscreen;
|
||||||
|
|
@ -166,7 +162,7 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
|
|
||||||
listAttachments: () => currentMessage()?.attachments()
|
listAttachments: () => currentMessage()?.attachments()
|
||||||
.filter(item => SettingsUserStore.listInlineAttachments() || !item.isLinked()),
|
.filter(item => SettingsUserStore.listInlineAttachments() || !item.isLinked()),
|
||||||
hasAttachments: () => this.listAttachments().length,
|
hasAttachments: () => this.listAttachments()?.length,
|
||||||
|
|
||||||
canBeRepliedOrForwarded: () => !MessagelistUserStore.isDraftFolder() && this.messageVisibility(),
|
canBeRepliedOrForwarded: () => !MessagelistUserStore.isDraftFolder() && this.messageVisibility(),
|
||||||
|
|
||||||
|
|
@ -280,23 +276,19 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
onBuild(dom) {
|
onBuild(dom) {
|
||||||
const eqs = (ev, s) => ev.target.closestWithin(s, dom);
|
const eqs = (ev, s) => ev.target.closestWithin(s, dom);
|
||||||
dom.addEventListener('click', event => {
|
dom.addEventListener('click', event => {
|
||||||
ThemeStore.isMobile() && leftPanelDisabled(true);
|
|
||||||
|
|
||||||
let el = eqs(event, 'a');
|
let el = eqs(event, 'a');
|
||||||
if (el && 0 === event.button && mailToHelper(el.href)) {
|
if (el && 0 === event.button && mailToHelper(el.href)) {
|
||||||
event.preventDefault();
|
stopEvent(event);
|
||||||
event.stopPropagation();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (eqs(event, '.attachmentsPlace .showPreview')) {
|
if (eqs(event, '.attachmentsPlace .showPreview')) {
|
||||||
event.stopPropagation();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
el = eqs(event, '.attachmentsPlace .showPreplay');
|
el = eqs(event, '.attachmentsPlace .showPreplay');
|
||||||
if (el) {
|
if (el) {
|
||||||
event.stopPropagation();
|
stopEvent(event);
|
||||||
const attachment = ko.dataFor(el);
|
const attachment = ko.dataFor(el);
|
||||||
if (attachment && SMAudio.supported) {
|
if (attachment && SMAudio.supported) {
|
||||||
switch (true) {
|
switch (true) {
|
||||||
|
|
@ -343,13 +335,6 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AppUserStore.focusedState.subscribe(value => {
|
|
||||||
if (Scope.MessageView !== value) {
|
|
||||||
this.scrollMessageToTop();
|
|
||||||
this.scrollMessageToLeft();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
keyScopeReal.subscribe(value => this.messageDomFocused(Scope.MessageView === value));
|
keyScopeReal.subscribe(value => this.messageDomFocused(Scope.MessageView === value));
|
||||||
|
|
||||||
// initShortcuts
|
// initShortcuts
|
||||||
|
|
@ -459,10 +444,12 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
MessageUserStore.bodiesDom(dom.querySelector('.bodyText'));
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollMessageToTop() {
|
scrollMessageToTop() {
|
||||||
oMessageScrollerDom().scrollTop = (50 < oMessageScrollerDom().scrollTop) ? 50 : 0;
|
oMessageScrollerDom().scrollTop = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollMessageToLeft() {
|
scrollMessageToLeft() {
|
||||||
|
|
@ -479,7 +466,9 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
const hashes = (currentMessage()?.attachments || [])
|
const hashes = (currentMessage()?.attachments || [])
|
||||||
.map(item => item?.checked() /*&& !item?.isLinked()*/ ? item.download : '')
|
.map(item => item?.checked() /*&& !item?.isLinked()*/ ? item.download : '')
|
||||||
.filter(v => v);
|
.filter(v => v);
|
||||||
downloadZip(hashes,
|
downloadZip(
|
||||||
|
currentMessage().subject(),
|
||||||
|
hashes,
|
||||||
() => this.downloadAsZipError(true),
|
() => this.downloadAsZipError(true),
|
||||||
this.downloadAsZipLoading
|
this.downloadAsZipLoading
|
||||||
);
|
);
|
||||||
|
|
@ -524,8 +513,6 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
if (!iError) {
|
if (!iError) {
|
||||||
oMessage.flags.push('$mdnsent');
|
oMessage.flags.push('$mdnsent');
|
||||||
// oMessage.flags.valueHasMutated();
|
// oMessage.flags.valueHasMutated();
|
||||||
MessageFlagsCache.store(oMessage);
|
|
||||||
MessagelistUserStore.reloadFlagsAndCachedMessage();
|
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
messageFolder: oMessage.folder,
|
messageFolder: oMessage.folder,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { settings } from 'Common/Links';
|
import { settings } from 'Common/Links';
|
||||||
|
import { mailbox } from 'Common/Links';
|
||||||
|
import { getFolderInboxName } from 'Common/Cache';
|
||||||
import { AbstractViewLeft } from 'Knoin/AbstractViews';
|
import { AbstractViewLeft } from 'Knoin/AbstractViews';
|
||||||
|
|
||||||
export class SettingsMenuUserView extends AbstractViewLeft {
|
export class SettingsMenuUserView extends AbstractViewLeft {
|
||||||
|
|
@ -15,4 +16,8 @@ export class SettingsMenuUserView extends AbstractViewLeft {
|
||||||
link(route) {
|
link(route) {
|
||||||
return settings(route);
|
return settings(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backToInbox() {
|
||||||
|
hasher.setHash(mailbox(getFolderInboxName()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { mailbox } from 'Common/Links';
|
|
||||||
import { getFolderInboxName } from 'Common/Cache';
|
|
||||||
import { leftPanelDisabled, toggleLeftPanel } from 'Common/Globals';
|
import { leftPanelDisabled, toggleLeftPanel } from 'Common/Globals';
|
||||||
|
|
||||||
import { MessageUserStore } from 'Stores/User/Message';
|
import { MessageUserStore } from 'Stores/User/Message';
|
||||||
|
|
@ -11,7 +9,6 @@ export class SettingsPaneUserView extends AbstractViewRight {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.isMobile = ThemeStore.isMobile;
|
|
||||||
this.leftPanelDisabled = leftPanelDisabled;
|
this.leftPanelDisabled = leftPanelDisabled;
|
||||||
this.toggleLeftPanel = toggleLeftPanel;
|
this.toggleLeftPanel = toggleLeftPanel;
|
||||||
}
|
}
|
||||||
|
|
@ -25,8 +22,4 @@ export class SettingsPaneUserView extends AbstractViewRight {
|
||||||
ThemeStore.isMobile() && !event.target.closestWithin('.toggleLeft', dom) && leftPanelDisabled(true)
|
ThemeStore.isMobile() && !event.target.closestWithin('.toggleLeft', dom) && leftPanelDisabled(true)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
backToInbox() {
|
|
||||||
hasher.setHash(mailbox(getFolderInboxName()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,13 +12,10 @@ import { KeyboardShortcutsHelpPopupView } from 'View/Popup/KeyboardShortcutsHelp
|
||||||
import { AccountPopupView } from 'View/Popup/Account';
|
import { AccountPopupView } from 'View/Popup/Account';
|
||||||
import { ContactsPopupView } from 'View/Popup/Contacts';
|
import { ContactsPopupView } from 'View/Popup/Contacts';
|
||||||
|
|
||||||
import { doc, leftPanelDisabled, fireEvent, SettingsCapa, registerShortcut } from 'Common/Globals';
|
import { fireEvent, stopEvent, SettingsCapa, registerShortcut } from 'Common/Globals';
|
||||||
|
|
||||||
import { ThemeStore } from 'Stores/Theme';
|
|
||||||
|
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
import { getNotification } from 'Common/Translator';
|
import { getNotification } from 'Common/Translator';
|
||||||
//import { clearCache } from 'Common/Cache';
|
|
||||||
//import { koComputable } from 'External/ko';
|
//import { koComputable } from 'External/ko';
|
||||||
import { addObservablesTo } from 'External/ko';
|
import { addObservablesTo } from 'External/ko';
|
||||||
|
|
||||||
|
|
@ -56,8 +53,7 @@ export class SystemDropDownUserView extends AbstractViewRight {
|
||||||
let email = account?.email;
|
let email = account?.email;
|
||||||
if (email && 0 === event.button && AccountUserStore.email() != email) {
|
if (email && 0 === event.button && AccountUserStore.email() != email) {
|
||||||
AccountUserStore.loading(true);
|
AccountUserStore.loading(true);
|
||||||
event.preventDefault();
|
stopEvent(event);
|
||||||
event.stopPropagation();
|
|
||||||
Remote.request('AccountSwitch',
|
Remote.request('AccountSwitch',
|
||||||
(iError/*, oData*/) => {
|
(iError/*, oData*/) => {
|
||||||
if (iError) {
|
if (iError) {
|
||||||
|
|
@ -69,10 +65,8 @@ export class SystemDropDownUserView extends AbstractViewRight {
|
||||||
} else {
|
} else {
|
||||||
/* // Not working yet
|
/* // Not working yet
|
||||||
forEachObjectEntry(oData.Result, (key, value) => rl.settings.set(key, value));
|
forEachObjectEntry(oData.Result, (key, value) => rl.settings.set(key, value));
|
||||||
clearCache();
|
// MessageUserStore.message();
|
||||||
// MessageUserStore.setMessage();
|
// MessageUserStore.purgeCache();
|
||||||
// MessageUserStore.purgeMessageBodyCache();
|
|
||||||
// MessageUserStore.hideMessageBodies();
|
|
||||||
MessagelistUserStore([]);
|
MessagelistUserStore([]);
|
||||||
// FolderUserStore.folderList([]);
|
// FolderUserStore.folderList([]);
|
||||||
loadFolders(value => {
|
loadFolders(value => {
|
||||||
|
|
@ -113,14 +107,6 @@ export class SystemDropDownUserView extends AbstractViewRight {
|
||||||
this.allowContacts && showScreenPopup(ContactsPopupView);
|
this.allowContacts && showScreenPopup(ContactsPopupView);
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleLayout()
|
|
||||||
{
|
|
||||||
const mobile = !ThemeStore.isMobile();
|
|
||||||
doc.cookie = 'rllayout=' + (mobile ? 'mobile' : 'desktop') + '; samesite=strict';
|
|
||||||
ThemeStore.isMobile(mobile);
|
|
||||||
leftPanelDisabled(mobile);
|
|
||||||
}
|
|
||||||
|
|
||||||
logoutClick() {
|
logoutClick() {
|
||||||
rl.app.logout();
|
rl.app.logout();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
134
dev/boot.js
134
dev/boot.js
|
|
@ -3,30 +3,26 @@
|
||||||
const
|
const
|
||||||
qUri = path => doc.location.pathname.replace(/\/+$/,'') + '/?/' + path,
|
qUri = path => doc.location.pathname.replace(/\/+$/,'') + '/?/' + path,
|
||||||
eId = id => doc.getElementById('rl-'+id),
|
eId = id => doc.getElementById('rl-'+id),
|
||||||
app = eId('app'),
|
admin = '1' == eId('app')?.dataset?.admin,
|
||||||
admin = app && '1' == app.dataset.admin,
|
|
||||||
layout = doc.cookie.match(/(^|;) ?rllayout=([^;]+)/) || '',
|
|
||||||
|
|
||||||
showError = msg => {
|
toggle = div => {
|
||||||
let div = eId('loading-error');
|
|
||||||
div.append(msg);
|
|
||||||
eId('loading').hidden = true;
|
eId('loading').hidden = true;
|
||||||
div.hidden = false;
|
div.hidden = false;
|
||||||
},
|
},
|
||||||
|
showError = msg => {
|
||||||
|
let div = eId('loading-error');
|
||||||
|
div.append(msg);
|
||||||
|
toggle(div);
|
||||||
|
},
|
||||||
|
|
||||||
loadScript = src => {
|
loadScript = src => src ? new Promise((resolve, reject) => {
|
||||||
if (!src) {
|
|
||||||
throw new Error('src should not be empty.');
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const script = doc.createElement('script');
|
const script = doc.createElement('script');
|
||||||
script.onload = () => resolve();
|
script.onload = () => resolve();
|
||||||
script.onerror = () => reject('Failed loading ' + src);
|
script.onerror = () => reject('Failed loading ' + src);
|
||||||
script.src = src;
|
script.src = src;
|
||||||
// script.async = true;
|
// script.async = true;
|
||||||
doc.head.append(script);
|
doc.head.append(script);
|
||||||
});
|
}) : Promise.reject('src is empty');
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let smctoken = doc.cookie.match(/(^|;) ?smctoken=([^;]+)/);
|
let smctoken = doc.cookie.match(/(^|;) ?smctoken=([^;]+)/);
|
||||||
|
|
@ -37,8 +33,6 @@ try {
|
||||||
smctoken = encodeURIComponent(btoa(String.fromCharCode(...data)));
|
smctoken = encodeURIComponent(btoa(String.fromCharCode(...data)));
|
||||||
}
|
}
|
||||||
localStorage.setItem('smctoken', smctoken);
|
localStorage.setItem('smctoken', smctoken);
|
||||||
// doc.cookie = `smctoken=${smctoken};path=${doc.location.pathname};samesite=strict;secure";
|
|
||||||
// doc.cookie = 'smctoken='+smctoken+";path=/;samesite=lax";
|
|
||||||
doc.cookie = 'smctoken='+smctoken+";path=/;samesite=strict";
|
doc.cookie = 'smctoken='+smctoken+";path=/;samesite=strict";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
@ -46,8 +40,6 @@ try {
|
||||||
|
|
||||||
let RL_APP_DATA = {};
|
let RL_APP_DATA = {};
|
||||||
|
|
||||||
doc.documentElement.classList.toggle('rl-mobile', 'mobile' === layout[2] || (!layout && 1000 > innerWidth));
|
|
||||||
|
|
||||||
window.rl = {
|
window.rl = {
|
||||||
adminArea: () => admin,
|
adminArea: () => admin,
|
||||||
|
|
||||||
|
|
@ -58,9 +50,94 @@ window.rl = {
|
||||||
},
|
},
|
||||||
|
|
||||||
setTitle: title =>
|
setTitle: title =>
|
||||||
doc.title = (title || '') + (RL_APP_DATA.Title ? (title ? ' - ' : '') + RL_APP_DATA.Title : ''),
|
doc.title = (title || '') + (RL_APP_DATA.title ? (title ? ' - ' : '') + RL_APP_DATA.title : ''),
|
||||||
|
|
||||||
initData: appData => {
|
setData: appData => {
|
||||||
|
RL_APP_DATA = appData;
|
||||||
|
rl.app.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
loadScript: loadScript,
|
||||||
|
|
||||||
|
fetch: (resource, init, postData) => {
|
||||||
|
init = Object.assign({
|
||||||
|
mode: 'same-origin',
|
||||||
|
cache: 'no-cache',
|
||||||
|
redirect: 'error',
|
||||||
|
referrerPolicy: 'no-referrer',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {}
|
||||||
|
}, init);
|
||||||
|
let asJSON = 1,
|
||||||
|
XToken = RL_APP_DATA.System?.token,
|
||||||
|
object = {};
|
||||||
|
if (postData) {
|
||||||
|
init.method = 'POST';
|
||||||
|
if (postData instanceof FormData) {
|
||||||
|
postData.forEach((value, key) => {
|
||||||
|
if (value instanceof File) {
|
||||||
|
asJSON = 0;
|
||||||
|
} else if (!Reflect.has(object, key)) {
|
||||||
|
object[key] = value;
|
||||||
|
} else {
|
||||||
|
Array.isArray(object[key]) || (object[key] = [object[key]]);
|
||||||
|
object[key].push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (asJSON) {
|
||||||
|
postData = object;
|
||||||
|
// postData.XToken = XToken;
|
||||||
|
} else {
|
||||||
|
XToken && postData.set('XToken', XToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (asJSON) {
|
||||||
|
init.headers['Content-Type'] = 'application/json';
|
||||||
|
postData = JSON.stringify(postData);
|
||||||
|
}
|
||||||
|
init.body = postData;
|
||||||
|
}
|
||||||
|
XToken && (init.headers['X-SM-Token'] = XToken);
|
||||||
|
// init.headers = new Headers(init.headers);
|
||||||
|
return fetch(resource, init);
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchJSON: (resource, init, postData) => {
|
||||||
|
init = Object.assign({ headers: {} }, init);
|
||||||
|
init.headers.Accept = 'application/json';
|
||||||
|
return rl.fetch(resource, init, postData).then(response => {
|
||||||
|
if (response.ok) {
|
||||||
|
/* TODO: use this for non-developers?
|
||||||
|
response.clone()
|
||||||
|
let data = response.text();
|
||||||
|
try {
|
||||||
|
return JSON.parse(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
// console.log(data);
|
||||||
|
return Promise.reject(Notifications.JsonParse);
|
||||||
|
return {
|
||||||
|
Result: false,
|
||||||
|
ErrorCode: 952, // Notifications.JsonParse
|
||||||
|
ErrorMessage: e.message,
|
||||||
|
ErrorMessageAdditional: data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
return Promise.reject('Network response error: ' + response.status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!navigator.cookieEnabled) {
|
||||||
|
toggle(eId('NoCookie'));
|
||||||
|
} else if (![].flat) {
|
||||||
|
toggle(eId('BadBrowser'));
|
||||||
|
} else {
|
||||||
|
rl.fetchJSON(qUri(`${admin ? 'Admin' : ''}AppData/0/${Math.random().toString().slice(2)}/`))
|
||||||
|
.then(appData => {
|
||||||
RL_APP_DATA = appData;
|
RL_APP_DATA = appData;
|
||||||
const url = appData.StaticLibsJs,
|
const url = appData.StaticLibsJs,
|
||||||
cb = () => rl.app.bootstart();
|
cb = () => rl.app.bootstart();
|
||||||
|
|
@ -75,24 +152,7 @@ window.rl = {
|
||||||
showError(e);
|
showError(e);
|
||||||
throw e;
|
throw e;
|
||||||
});
|
});
|
||||||
},
|
})
|
||||||
|
|
||||||
setData: appData => {
|
|
||||||
RL_APP_DATA = appData;
|
|
||||||
rl.app.refresh();
|
|
||||||
},
|
|
||||||
|
|
||||||
loadScript: loadScript
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!navigator.cookieEnabled) {
|
|
||||||
eId('loading').hidden = true;
|
|
||||||
eId('NoCookie').hidden = false;
|
|
||||||
} else if (![].flat) {
|
|
||||||
eId('loading').hidden = true;
|
|
||||||
eId('BadBrowser').hidden = false;
|
|
||||||
} else {
|
|
||||||
loadScript(qUri(`${admin ? 'Admin' : ''}AppData/0/${Math.random().toString().slice(2)}/`))
|
|
||||||
.catch(e => showError(e));
|
.catch(e => showError(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
64
dev/bootstrap.js
vendored
64
dev/bootstrap.js
vendored
|
|
@ -1,10 +1,6 @@
|
||||||
import { Settings } from 'Common/Globals';
|
|
||||||
import { i18n } from 'Common/Translator';
|
import { i18n } from 'Common/Translator';
|
||||||
|
|
||||||
import { root } from 'Common/Links';
|
import { root } from 'Common/Links';
|
||||||
|
|
||||||
import { isArray } from 'Common/Utils';
|
|
||||||
|
|
||||||
export default App => {
|
export default App => {
|
||||||
|
|
||||||
rl.app = App;
|
rl.app = App;
|
||||||
|
|
@ -33,64 +29,4 @@ export default App => {
|
||||||
on: () => hasher.active = true
|
on: () => hasher.active = true
|
||||||
};
|
};
|
||||||
|
|
||||||
rl.fetch = (resource, init, postData) => {
|
|
||||||
init = Object.assign({
|
|
||||||
mode: 'same-origin',
|
|
||||||
cache: 'no-cache',
|
|
||||||
redirect: 'error',
|
|
||||||
referrerPolicy: 'no-referrer',
|
|
||||||
credentials: 'same-origin',
|
|
||||||
headers: {}
|
|
||||||
}, init);
|
|
||||||
if (postData) {
|
|
||||||
init.method = 'POST';
|
|
||||||
init.headers['Content-Type'] = 'application/json';
|
|
||||||
if (postData instanceof FormData) {
|
|
||||||
const object = {};
|
|
||||||
postData.forEach((value, key) => {
|
|
||||||
if (!Reflect.has(object, key)){
|
|
||||||
object[key] = value;
|
|
||||||
} else {
|
|
||||||
isArray(object[key]) || (object[key] = [object[key]]);
|
|
||||||
object[key].push(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
postData = object;
|
|
||||||
}
|
|
||||||
postData.XToken = Settings.app('token');
|
|
||||||
init.body = JSON.stringify(postData);
|
|
||||||
}
|
|
||||||
init.headers['X-SM-Token'] = Settings.app('token');
|
|
||||||
// init.headers = new Headers(init.headers);
|
|
||||||
return fetch(resource, init);
|
|
||||||
};
|
|
||||||
|
|
||||||
rl.fetchJSON = (resource, init, postData) => {
|
|
||||||
init = Object.assign({ headers: {} }, init);
|
|
||||||
init.headers.Accept = 'application/json';
|
|
||||||
return rl.fetch(resource, init, postData).then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
return Promise.reject('Network response error: ' + response.status);
|
|
||||||
}
|
|
||||||
/* TODO: use this for non-developers?
|
|
||||||
response.clone()
|
|
||||||
let data = response.text();
|
|
||||||
try {
|
|
||||||
return JSON.parse(data);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
// console.log(data);
|
|
||||||
return Promise.reject(Notification.JsonParse);
|
|
||||||
return {
|
|
||||||
Result: false,
|
|
||||||
ErrorCode: 952, // Notification.JsonParse
|
|
||||||
ErrorMessage: e.message,
|
|
||||||
ErrorMessageAdditional: data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return response.json();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
8
dev/prototype.js
vendored
8
dev/prototype.js
vendored
|
|
@ -53,7 +53,7 @@
|
||||||
return function(...args) {
|
return function(...args) {
|
||||||
timer && clearTimeout(timer);
|
timer && clearTimeout(timer);
|
||||||
timer = setTimeout(()=>{
|
timer = setTimeout(()=>{
|
||||||
func.apply(this, args)
|
func.apply(this, args);
|
||||||
timer = 0;
|
timer = 0;
|
||||||
}, ms);
|
}, ms);
|
||||||
};
|
};
|
||||||
|
|
@ -68,12 +68,10 @@
|
||||||
Function.prototype.throttle = function(ms) {
|
Function.prototype.throttle = function(ms) {
|
||||||
let func = this, timer;
|
let func = this, timer;
|
||||||
return function(...args) {
|
return function(...args) {
|
||||||
if (!timer) {
|
timer = timer || setTimeout(()=>{
|
||||||
timer = setTimeout(()=>{
|
func.apply(this, args);
|
||||||
func.apply(this, args)
|
|
||||||
timer = 0;
|
timer = 0;
|
||||||
}, ms);
|
}, ms);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
This app packages SnappyMail <upstream>2.26.1</upstream>.
|
This app packages SnappyMail <upstream>2.26.4</upstream>.
|
||||||
|
|
||||||
SnappyMail is a simple, modern, lightweight & fast web-based email client.
|
SnappyMail is a simple, modern, lightweight & fast web-based email client.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ RUN mkdir -p /app/code
|
||||||
WORKDIR /app/code
|
WORKDIR /app/code
|
||||||
|
|
||||||
# If you change the extraction below, be sure to test on scaleway
|
# If you change the extraction below, be sure to test on scaleway
|
||||||
VERSION=2.26.1
|
VERSION=2.26.4
|
||||||
RUN wget https://github.com/the-djmaze/snappymail/releases/download/v${VERSION}/snappymail-${VERSION}.zip -O /tmp/snappymail.zip && \
|
RUN wget https://github.com/the-djmaze/snappymail/releases/download/v${VERSION}/snappymail-${VERSION}.zip -O /tmp/snappymail.zip && \
|
||||||
unzip /tmp/snappymail.zip -d /app/code && \
|
unzip /tmp/snappymail.zip -d /app/code && \
|
||||||
rm /tmp/snappymail.zip && \
|
rm /tmp/snappymail.zip && \
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<id>snappymail</id>
|
<id>snappymail</id>
|
||||||
<name>SnappyMail</name>
|
<name>SnappyMail</name>
|
||||||
<summary>SnappyMail Webmail</summary>
|
<summary>SnappyMail Webmail</summary>
|
||||||
<version>2.26.1</version>
|
<version>2.26.4</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
<author>SnappyMail, RainLoop Team, Nextgen-Networks, Tab Fitts, Nathan Kinkade, Pierre-Alain Bandinelli</author>
|
<author>SnappyMail, RainLoop Team, Nextgen-Networks, Tab Fitts, Nathan Kinkade, Pierre-Alain Bandinelli</author>
|
||||||
<description><![CDATA[**Simple, modern, lightweight & fast web-based email client.**
|
<description><![CDATA[**Simple, modern, lightweight & fast web-based email client.**
|
||||||
|
|
@ -39,7 +39,7 @@ There, click on the link to go to the SnappyMail admin panel.
|
||||||
<repository>https://github.com/the-djmaze/snappymail/tree/master/integrations/nextcloud</repository>
|
<repository>https://github.com/the-djmaze/snappymail/tree/master/integrations/nextcloud</repository>
|
||||||
<bugs>https://github.com/the-djmaze/snappymail/issues</bugs>
|
<bugs>https://github.com/the-djmaze/snappymail/issues</bugs>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<php min-version="7.4" min-int-size="64" />
|
<php min-version="7.4" min-int-size="32" />
|
||||||
<lib>mbstring</lib>
|
<lib>mbstring</lib>
|
||||||
<lib>zlib</lib>
|
<lib>zlib</lib>
|
||||||
<!--
|
<!--
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
|
||||||
function __construct() {
|
function __construct() {
|
||||||
$CSP = \RainLoop\Api::getCSP();
|
$CSP = \RainLoop\Api::getCSP();
|
||||||
|
|
||||||
$this->allowedScriptDomains = \array_unique(\array_merge($this->allowedScriptDomains, $CSP->script));
|
$this->allowedScriptDomains = \array_unique(\array_merge($this->allowedScriptDomains, $CSP->get('script-src')));
|
||||||
$this->allowedScriptDomains = \array_diff($this->allowedScriptDomains, ["'unsafe-inline'", "'unsafe-eval'"]);
|
$this->allowedScriptDomains = \array_diff($this->allowedScriptDomains, ["'unsafe-inline'", "'unsafe-eval'"]);
|
||||||
|
|
||||||
// Nextcloud only sets 'strict-dynamic' when browserSupportsCspV3() ?
|
// Nextcloud only sets 'strict-dynamic' when browserSupportsCspV3() ?
|
||||||
|
|
@ -24,12 +24,12 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy
|
||||||
? $this->useStrictDynamic(true) // NC24+
|
? $this->useStrictDynamic(true) // NC24+
|
||||||
: $this->addAllowedScriptDomain("'strict-dynamic'");
|
: $this->addAllowedScriptDomain("'strict-dynamic'");
|
||||||
|
|
||||||
$this->allowedImageDomains = \array_unique(\array_merge($this->allowedImageDomains, $CSP->img));
|
$this->allowedImageDomains = \array_unique(\array_merge($this->allowedImageDomains, $CSP->get('img-src')));
|
||||||
|
|
||||||
$this->allowedStyleDomains = \array_unique(\array_merge($this->allowedStyleDomains, $CSP->style));
|
$this->allowedStyleDomains = \array_unique(\array_merge($this->allowedStyleDomains, $CSP->get('style-src')));
|
||||||
$this->allowedStyleDomains = \array_diff($this->allowedStyleDomains, ["'unsafe-inline'"]);
|
$this->allowedStyleDomains = \array_diff($this->allowedStyleDomains, ["'unsafe-inline'"]);
|
||||||
|
|
||||||
$this->allowedFrameDomains = \array_unique(\array_merge($this->allowedFrameDomains, $CSP->frame));
|
$this->allowedFrameDomains = \array_unique(\array_merge($this->allowedFrameDomains, $CSP->get('frame-src')));
|
||||||
|
|
||||||
$this->reportTo = \array_unique(\array_merge($this->reportTo, $CSP->report_to));
|
$this->reportTo = \array_unique(\array_merge($this->reportTo, $CSP->report_to));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ class PageController extends Controller
|
||||||
]);
|
]);
|
||||||
$csp = new ContentSecurityPolicy();
|
$csp = new ContentSecurityPolicy();
|
||||||
$csp->addAllowedFrameDomain("'self'");
|
$csp->addAllowedFrameDomain("'self'");
|
||||||
|
// $csp->addAllowedFrameAncestorDomain("'self'");
|
||||||
$response->setContentSecurityPolicy($csp);
|
$response->setContentSecurityPolicy($csp);
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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