mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Merge branch 'the-djmaze:master' into rework-on-mailaddress-handling
This commit is contained in:
commit
aa03e84c79
107 changed files with 972 additions and 1018 deletions
|
|
@ -316,5 +316,5 @@ dev_email = ""
|
||||||
dev_password = ""
|
dev_password = ""
|
||||||
|
|
||||||
[version]
|
[version]
|
||||||
current = "2.25.4"
|
current = "2.26.1"
|
||||||
saved = "Sun, 18 Dec 2022 22:10:48 +0000"
|
saved = "Sun, 18 Dec 2022 22:10:48 +0000"
|
||||||
|
|
|
||||||
98
CHANGELOG.md
98
CHANGELOG.md
|
|
@ -1,3 +1,101 @@
|
||||||
|
## 2.26.1 – 2023-02-14
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Option to allow `<style>` in messages (beta)
|
||||||
|
- Message in new tab/window was missing BCC
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- Don't clone message for viewing, so that there is instant interaction with messagelist item
|
||||||
|
- emailArrayToStringLineHelper() now filters addresses without email address
|
||||||
|
- Cleanup EmailModel and better email address parsing and handling
|
||||||
|
- Workaround "server connection error"
|
||||||
|
[#936](https://github.com/the-djmaze/snappymail/pull/936)
|
||||||
|
- Cleanup AbstractViewPopup handling
|
||||||
|
- Italian translation by @lota
|
||||||
|
[#948](https://github.com/the-djmaze/snappymail/pull/948)
|
||||||
|
- Replace Element.fromHTML() with createElement()
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- New subfolder not visible
|
||||||
|
[#937](https://github.com/the-djmaze/snappymail/pull/937)
|
||||||
|
- OpenPGP decrypted attachments `friendlySize` not a function
|
||||||
|
- Message @media print was broken due to new `display:flex`
|
||||||
|
- addressparser() didn't handle groups properly
|
||||||
|
- middleclick in messages list failed when messagesDom not yet initialized
|
||||||
|
- Cannot open messages that have an email address without an @ in the From header
|
||||||
|
[#950](https://github.com/the-djmaze/snappymail/pull/950)
|
||||||
|
- CSS don't display:flex when `[hidden]`
|
||||||
|
- Default theme handling by @Niveshkrishna
|
||||||
|
|
||||||
|
|
||||||
|
## 2.26.0 – 2023-02-10
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- Whitelist advanced SPF/DKIM/DMARC valid feature
|
||||||
|
[#938](https://github.com/the-djmaze/snappymail/pull/938)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- \RainLoop\Providers\AddressBook\Utils functions param `Contact` changed to `VCard`
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Issue with themes and Chrome cache
|
||||||
|
[#188](https://github.com/the-djmaze/snappymail/pull/188)
|
||||||
|
- Settings panel width due to display:flex
|
||||||
|
[#940](https://github.com/the-djmaze/snappymail/pull/940)
|
||||||
|
- Not respecting default theme setting
|
||||||
|
[#941](https://github.com/the-djmaze/snappymail/pull/941)
|
||||||
|
- Some files had 0755 instead of 0644
|
||||||
|
- Some spacing between message "view images" buttons for
|
||||||
|
[#201](https://github.com/the-djmaze/snappymail/pull/201)
|
||||||
|
- Whitelist failed when empty or when using `:`
|
||||||
|
[#938](https://github.com/the-djmaze/snappymail/pull/938)
|
||||||
|
- Cosmetics of the external images whitelist menu
|
||||||
|
[#939](https://github.com/the-djmaze/snappymail/pull/939)
|
||||||
|
- PdoAddressBook ORDER BY deleted DESC to prevent sync and export issues
|
||||||
|
- Undefined variable $items in upgrade.php
|
||||||
|
- qq.com not supporting literal-string in search
|
||||||
|
[#836](https://github.com/the-djmaze/snappymail/pull/836)
|
||||||
|
|
||||||
|
|
||||||
|
## 2.25.5 – 2023-02-09
|
||||||
|
|
||||||
|
## Added
|
||||||
|
- New dark themes by @TheCuteFoxxy
|
||||||
|
[#925](https://github.com/the-djmaze/snappymail/pull/925)
|
||||||
|
- External images option 'Always when DKIM is valid' for
|
||||||
|
[#201](https://github.com/the-djmaze/snappymail/pull/201)
|
||||||
|
- Image whitelist menu on message for
|
||||||
|
[#201](https://github.com/the-djmaze/snappymail/pull/201)
|
||||||
|
|
||||||
|
## Changed
|
||||||
|
- pt-PT translation by @ner00
|
||||||
|
[#917](https://github.com/the-djmaze/snappymail/pull/917) and
|
||||||
|
[#920](https://github.com/the-djmaze/snappymail/pull/920)
|
||||||
|
- fr-FR translation by @hguilbert
|
||||||
|
[#919](https://github.com/the-djmaze/snappymail/pull/919)
|
||||||
|
- Layout section rl-right now is `display: flex` and solves
|
||||||
|
[#928](https://github.com/the-djmaze/snappymail/pull/928)
|
||||||
|
- Some Themes background to `cover`
|
||||||
|
[#918](https://github.com/the-djmaze/snappymail/pull/918)
|
||||||
|
- Speedup cleanHtml() parser
|
||||||
|
- Reduce memory usage on addressbook import
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
- Prevent loading loop between MessageList and FolderInformation requests
|
||||||
|
- Admin -> Config layout
|
||||||
|
- Inline images sometimes failed
|
||||||
|
- Undefined index: cid
|
||||||
|
[#921](https://github.com/the-djmaze/snappymail/pull/921)
|
||||||
|
- On upgrade prevent Apache access errors for
|
||||||
|
[#358](https://github.com/the-djmaze/snappymail/pull/358)
|
||||||
|
- Import contacts as CSV is broken
|
||||||
|
[#931](https://github.com/the-djmaze/snappymail/pull/931)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- Blockquote height calculator for
|
||||||
|
[#902](https://github.com/the-djmaze/snappymail/pull/902)
|
||||||
|
|
||||||
|
|
||||||
## 2.25.4 – 2023-02-06
|
## 2.25.4 – 2023-02-06
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
|
||||||
24
README.md
24
README.md
|
|
@ -141,26 +141,26 @@ RainLoop 1.17 vs SnappyMail
|
||||||
|
|
||||||
|js/* |RainLoop |Snappy |
|
|js/* |RainLoop |Snappy |
|
||||||
|--------------- |--------: |--------: |
|
|--------------- |--------: |--------: |
|
||||||
|admin.js |2.170.153 | 81.589 |
|
|admin.js |2.170.153 | 81.159 |
|
||||||
|app.js |4.207.787 | 415.987 |
|
|app.js |4.207.787 | 414.949 |
|
||||||
|boot.js | 868.735 | 2.711 |
|
|boot.js | 868.735 | 2.711 |
|
||||||
|libs.js | 658.812 | 192.392 |
|
|libs.js | 658.812 | 192.392 |
|
||||||
|sieve.js | 0 | 85.597 |
|
|sieve.js | 0 | 85.597 |
|
||||||
|polyfills.js | 334.608 | 0 |
|
|polyfills.js | 334.608 | 0 |
|
||||||
|serviceworker.js | 0 | 285 |
|
|serviceworker.js | 0 | 285 |
|
||||||
|TOTAL |8.240.095 | 778.561 |
|
|TOTAL |8.240.095 | 777.093 |
|
||||||
|
|
||||||
|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.319 | 73.606 | 13.343 | 60.877 | 11.970 |
|
|admin.min.js | 256.831 | 40.198 | 73.606 | 13.281 | 60.877 | 11.916 |
|
||||||
|app.min.js | 515.367 | 191.740 |139.456 | 64.135 |110.485 | 54.920 |
|
|app.min.js | 515.367 | 190.317 |139.456 | 63.879 |110.485 | 54.792 |
|
||||||
|boot.min.js | 84.659 | 1.600 | 26.998 | 940 | 23.643 | 767 |
|
|boot.min.js | 84.659 | 1.600 | 26.998 | 940 | 23.643 | 767 |
|
||||||
|libs.min.js | 584.772 | 91.434 |180.901 | 33.893 |155.182 | 30.383 |
|
|libs.min.js | 584.772 | 91.434 |180.901 | 33.893 |155.182 | 30.383 |
|
||||||
|sieve.min.js | 0 | 41.566 | 0 | 10.427 | 0 | 9.413 |
|
|sieve.min.js | 0 | 41.566 | 0 | 10.427 | 0 | 9.413 |
|
||||||
|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 | 284.774 |358.761 | 98.968 |299.485 | 86.070 |
|
|TOTAL user |1.217.635 | 283.351 |358.761 | 98.712 |299.485 | 85.942 |
|
||||||
|TOTAL user+sieve |1.217.635 | 326.340 |358.761 |109.395 |299.485 | 95.483 |
|
|TOTAL user+sieve |1.217.635 | 324.917 |358.761 |109.139 |299.485 | 95.355 |
|
||||||
|TOTAL admin | 959.099 | 133.353 |292.911 | 48.176 |249.877 | 43.120 |
|
|TOTAL admin | 959.099 | 133.232 |292.911 | 48.114 |249.877 | 43.066 |
|
||||||
|
|
||||||
For a user its around 70% smaller and faster than traditional RainLoop.
|
For a user its around 70% smaller and faster than traditional RainLoop.
|
||||||
|
|
||||||
|
|
@ -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.190 | 46.946 | 17.063 | 14.669 |
|
|app.css | 340.331 | 82.134 | 46.946 | 17.067 | 14.688 |
|
||||||
|app.min.css | 274.947 | 66.363 | 39.647 | 15.092 | 13.246 |
|
|app.min.css | 274.947 | 66.337 | 39.647 | 15.095 | 13.285 |
|
||||||
|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.535 | | 6.980 | 6.091 |
|
|admin.css | | 30.554 | | 6.984 | 6.085 |
|
||||||
|admin.min.css | | 24.733 | | 6.336 | 5.587 |
|
|admin.min.css | | 24.752 | | 6.339 | 5.606 |
|
||||||
|
|
||||||
### PGP
|
### PGP
|
||||||
RainLoop uses the old OpenPGP.js v2
|
RainLoop uses the old OpenPGP.js v2
|
||||||
|
|
|
||||||
|
|
@ -251,7 +251,7 @@ export class AppUser extends AbstractApp {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const left = elementById('rl-left'),
|
const left = elementById('rl-left'),
|
||||||
fToggle = () =>
|
fToggle = () =>
|
||||||
setLayoutResizer(left, null, ClientSideKeyNameFolderListSize,
|
setLayoutResizer(left, ClientSideKeyNameFolderListSize,
|
||||||
(ThemeStore.isMobile() || leftPanelDisabled()) ? 0 : 'Width');
|
(ThemeStore.isMobile() || leftPanelDisabled()) ? 0 : 'Width');
|
||||||
if (left) {
|
if (left) {
|
||||||
fToggle();
|
fToggle();
|
||||||
|
|
|
||||||
|
|
@ -151,9 +151,11 @@ folderInformation = (folder, list) => {
|
||||||
if (!oldHash || unreadCountChange || result.hash !== oldHash) {
|
if (!oldHash || unreadCountChange || result.hash !== oldHash) {
|
||||||
if (folderFromCache.fullName === FolderUserStore.currentFolderFullName()) {
|
if (folderFromCache.fullName === FolderUserStore.currentFolderFullName()) {
|
||||||
MessagelistUserStore.reload();
|
MessagelistUserStore.reload();
|
||||||
|
/*
|
||||||
} else if (getFolderInboxName() === folderFromCache.fullName) {
|
} else if (getFolderInboxName() === folderFromCache.fullName) {
|
||||||
// Remote.messageList(null, {folder: getFolderFromCacheList(getFolderInboxName())}, true);
|
// Remote.messageList(null, {folder: getFolderFromCacheList(getFolderInboxName())}, true);
|
||||||
Remote.messageList(null, {folder: getFolderInboxName()}, true);
|
Remote.messageList(null, {folder: getFolderInboxName()}, true);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ const
|
||||||
SettingsUserStore.collapseBlockquotes() &&
|
SettingsUserStore.collapseBlockquotes() &&
|
||||||
// tpl.content.querySelectorAll('blockquote').forEach(node => {
|
// tpl.content.querySelectorAll('blockquote').forEach(node => {
|
||||||
[...tpl.content.querySelectorAll('blockquote')].reverse().forEach(node => {
|
[...tpl.content.querySelectorAll('blockquote')].reverse().forEach(node => {
|
||||||
const el = Element.fromHTML('<details class="sm-bq-switcher"><summary>•••</summary></details>');
|
const el = createElement('details', {class:'sm-bq-switcher'});
|
||||||
|
el.innerHTML = '<summary>•••</summary>';
|
||||||
node.replaceWith(el);
|
node.replaceWith(el);
|
||||||
el.append(node);
|
el.append(node);
|
||||||
});
|
});
|
||||||
|
|
@ -87,6 +88,86 @@ const
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
Parses given css string, and returns css object
|
||||||
|
keys as selectors and values are css rules
|
||||||
|
eliminates all css comments before parsing
|
||||||
|
|
||||||
|
@param source css string to be parsed
|
||||||
|
|
||||||
|
@return object css
|
||||||
|
*/
|
||||||
|
parseCSS = source => {
|
||||||
|
const css = [];
|
||||||
|
css.toString = () => css.reduce(
|
||||||
|
(ret, tmp) =>
|
||||||
|
ret + tmp.selector + ' {\n'
|
||||||
|
+ (tmp.type === 'media' ? tmp.subStyles.toString() : tmp.rules)
|
||||||
|
+ '}\n'
|
||||||
|
,
|
||||||
|
''
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Given css array, parses it and then for every selector,
|
||||||
|
* prepends namespace to prevent css collision issues
|
||||||
|
*/
|
||||||
|
css.applyNamespace = namespace => css.forEach(obj => {
|
||||||
|
if (obj.type === 'media') {
|
||||||
|
obj.subStyles.applyNamespace(namespace);
|
||||||
|
} else {
|
||||||
|
obj.selector = obj.selector.split(',').map(selector =>
|
||||||
|
(namespace + ' .mail-body ' + selector.replace(/\./g, '.msg-'))
|
||||||
|
.replace(/\sbody/gi, '')
|
||||||
|
).join(',');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
source = source
|
||||||
|
// strip comments
|
||||||
|
.replace(/\/\*[\s\S]*?\*\/|<!--|-->/gi, '')
|
||||||
|
// strip import statements
|
||||||
|
.replace(/@import .*?;/gi , '')
|
||||||
|
// strip keyframe statements
|
||||||
|
.replace(/((@.*?keyframes [\s\S]*?){([\s\S]*?}\s*?)})/gi, '');
|
||||||
|
|
||||||
|
// unified regex to match css & media queries together
|
||||||
|
let unified = /((\s*?(?:\/\*[\s\S]*?\*\/)?\s*?@media[\s\S]*?){([\s\S]*?)}\s*?})|(([\s\S]*?){([\s\S]*?)})/gi,
|
||||||
|
arr;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
arr = unified.exec(source);
|
||||||
|
if (arr === null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selector = arr[arr[2] === undefined ? 5 : 2].split('\r\n').join('\n').trim()
|
||||||
|
// Never have more than a single line break in a row
|
||||||
|
.replace(/\n+/, "\n")
|
||||||
|
// Remove :root and html
|
||||||
|
.split(/\s+/g).map(item => item.replace(/^(:root|html)$/, '')).join(' ').trim();
|
||||||
|
|
||||||
|
// determine the type
|
||||||
|
if (selector.includes('@media')) {
|
||||||
|
// we have a media query
|
||||||
|
css.push({
|
||||||
|
selector: selector,
|
||||||
|
type: 'media',
|
||||||
|
subStyles: parseCSS(arr[3] + '\n}') //recursively parse media query inner css
|
||||||
|
});
|
||||||
|
} else if (selector && !selector.includes('@')) {
|
||||||
|
// we have standard css
|
||||||
|
css.push({
|
||||||
|
selector: selector,
|
||||||
|
rules: arr[6]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return css;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const
|
export const
|
||||||
|
|
@ -102,7 +183,7 @@ export const
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
cleanHtml = (html, oAttachments) => {
|
cleanHtml = (html, oAttachments, msgId) => {
|
||||||
let aColor;
|
let aColor;
|
||||||
const
|
const
|
||||||
debug = false, // Config()->Get('debug', 'enable', false);
|
debug = false, // Config()->Get('debug', 'enable', false);
|
||||||
|
|
@ -159,7 +240,7 @@ export const
|
||||||
'colspan', 'rowspan', 'headers'
|
'colspan', 'rowspan', 'headers'
|
||||||
],
|
],
|
||||||
disallowedTags = [
|
disallowedTags = [
|
||||||
'STYLE','SVG','SCRIPT','TITLE','LINK','BASE','META',
|
'SVG','SCRIPT','TITLE','LINK','BASE','META',
|
||||||
'INPUT','OUTPUT','SELECT','BUTTON','TEXTAREA',
|
'INPUT','OUTPUT','SELECT','BUTTON','TEXTAREA',
|
||||||
'BGSOUND','KEYGEN','SOURCE','OBJECT','EMBED','APPLET','IFRAME','FRAME','FRAMESET','VIDEO','AUDIO','AREA','MAP'
|
'BGSOUND','KEYGEN','SOURCE','OBJECT','EMBED','APPLET','IFRAME','FRAME','FRAMESET','VIDEO','AUDIO','AREA','MAP'
|
||||||
// Not supported by <template> element
|
// Not supported by <template> element
|
||||||
|
|
@ -169,7 +250,15 @@ export const
|
||||||
'A','B','EM','I','SPAN','STRONG'
|
'A','B','EM','I','SPAN','STRONG'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (SettingsUserStore.allowStyles()) {
|
||||||
|
allowedAttributes.push('class');
|
||||||
|
} else {
|
||||||
|
msgId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
tpl.innerHTML = html
|
tpl.innerHTML = html
|
||||||
|
// Strip Microsoft comments
|
||||||
|
.replace(/<!--\[if[\s\S]*?endif\]-->/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, '')
|
||||||
|
|
@ -198,6 +287,21 @@ export const
|
||||||
const name = oElement.tagName,
|
const name = oElement.tagName,
|
||||||
oStyle = oElement.style;
|
oStyle = oElement.style;
|
||||||
|
|
||||||
|
if ('STYLE' === name) {
|
||||||
|
let css = msgId ? parseCSS(oElement.textContent) : [];
|
||||||
|
if (css.length) {
|
||||||
|
css.applyNamespace(msgId);
|
||||||
|
css = css.toString();
|
||||||
|
if (SettingsUserStore.removeColors()) {
|
||||||
|
css = css.replace(/(background-)?color:[^};]+;?/g, '');
|
||||||
|
}
|
||||||
|
oElement.textContent = css;
|
||||||
|
} else {
|
||||||
|
oElement.remove();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// \MailSo\Base\HtmlUtils::ClearTags()
|
// \MailSo\Base\HtmlUtils::ClearTags()
|
||||||
if (disallowedTags.includes(name)
|
if (disallowedTags.includes(name)
|
||||||
|| 'none' == oStyle.display
|
|| 'none' == oStyle.display
|
||||||
|
|
@ -210,29 +314,23 @@ export const
|
||||||
oElement.remove();
|
oElement.remove();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
// Idea to allow CSS
|
|
||||||
if ('STYLE' === name) {
|
|
||||||
msgId = '#rl-msg-061eb4d647771be4185943ce91f0039d';
|
|
||||||
oElement.textContent = oElement.textContent
|
|
||||||
.replace(/[^{}]+{/g, m => msgId + ' ' + m.replace(',', ', '+msgId+' '))
|
|
||||||
.replace(/(background-)color:[^};]+/g, '');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
const aAttrsForRemove = [],
|
const aAttrsForRemove = [],
|
||||||
|
className = oElement.className,
|
||||||
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 => oElement.removeAttribute(name);
|
||||||
|
|
||||||
if ('mail-body' === oElement.className) {
|
if ('mail-body' === className) {
|
||||||
forEachObjectEntry(tasks, (name, cb) => {
|
forEachObjectEntry(tasks, (name, cb) => {
|
||||||
if (hasAttribute(name)) {
|
if (hasAttribute(name)) {
|
||||||
cb(getAttribute(name), oElement);
|
cb(getAttribute(name), oElement);
|
||||||
delAttribute(name);
|
delAttribute(name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else if (msgId && className) {
|
||||||
|
oElement.className = className.replace(/(^|\s+)/g, '$1msg-');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oElement.hasAttributes()) {
|
if (oElement.hasAttributes()) {
|
||||||
|
|
@ -258,7 +356,7 @@ export const
|
||||||
delAttribute('width');
|
delAttribute('width');
|
||||||
}
|
}
|
||||||
value = oStyle.removeProperty('width');
|
value = oStyle.removeProperty('width');
|
||||||
if (value && !oStyle.maxWidth) {
|
if (parseInt(value,10) && !oStyle.maxWidth) {
|
||||||
oStyle.maxWidth = value;
|
oStyle.maxWidth = value;
|
||||||
oStyle.width = '100%';
|
oStyle.width = '100%';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ 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 { EmailModel } from 'Model/Email';
|
import { EmailCollectionModel } from 'Model/EmailCollection';
|
||||||
import { MessageModel } from 'Model/Message';
|
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';
|
||||||
|
|
@ -158,27 +158,13 @@ mailToHelper = mailToUrl => {
|
||||||
const
|
const
|
||||||
email = mailToUrl[0],
|
email = mailToUrl[0],
|
||||||
params = new URLSearchParams(mailToUrl[1]),
|
params = new URLSearchParams(mailToUrl[1]),
|
||||||
toEmailModel = value => null != value ? EmailModel.parseEmailLine(value) : null;
|
to = params.get('to'),
|
||||||
|
toEmailModel = value => EmailCollectionModel.fromString(value);
|
||||||
|
|
||||||
showMessageComposer([
|
showMessageComposer([
|
||||||
ComposeType.Empty,
|
ComposeType.Empty,
|
||||||
null,
|
null,
|
||||||
params.get('to')
|
toEmailModel(to ? email + ',' + to : email),
|
||||||
? Object.values(
|
|
||||||
toEmailModel(email + ',' + params.get('to')).reduce((result, value) => {
|
|
||||||
if (value) {
|
|
||||||
if (result[value.email]) {
|
|
||||||
if (!result[value.email].name) {
|
|
||||||
result[value.email] = value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result[value.email] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}, {})
|
|
||||||
)
|
|
||||||
: EmailModel.parseEmailLine(email),
|
|
||||||
toEmailModel(params.get('cc')),
|
toEmailModel(params.get('cc')),
|
||||||
toEmailModel(params.get('bcc')),
|
toEmailModel(params.get('bcc')),
|
||||||
params.get('subject'),
|
params.get('subject'),
|
||||||
|
|
@ -196,37 +182,24 @@ showMessageComposer = (params = []) =>
|
||||||
rl.app.showMessageComposer(params);
|
rl.app.showMessageComposer(params);
|
||||||
},
|
},
|
||||||
|
|
||||||
setLayoutResizer = (source, target, sClientSideKeyName, mode) =>
|
setLayoutResizer = (source, sClientSideKeyName, mode) =>
|
||||||
{
|
{
|
||||||
if (source.layoutResizer && source.layoutResizer.mode != mode) {
|
if (source.layoutResizer && source.layoutResizer.mode != mode) {
|
||||||
target?.removeAttribute('style');
|
|
||||||
source.removeAttribute('style');
|
source.removeAttribute('style');
|
||||||
}
|
}
|
||||||
source.observer?.disconnect();
|
source.observer?.disconnect();
|
||||||
// source.classList.toggle('resizable', mode);
|
// source.classList.toggle('resizable', mode);
|
||||||
if (mode) {
|
if (mode) {
|
||||||
const length = Local.get(sClientSideKeyName + mode) || SettingsGet('Resizer' + sClientSideKeyName + mode),
|
const length = Local.get(sClientSideKeyName + mode) || SettingsGet('Resizer' + sClientSideKeyName + mode);
|
||||||
setTargetPos = mode => {
|
|
||||||
let value;
|
|
||||||
if ('Width' == mode) {
|
|
||||||
value = source.offsetWidth;
|
|
||||||
target && (target.style.left = value + 'px');
|
|
||||||
} else {
|
|
||||||
value = source.offsetHeight;
|
|
||||||
target && (target.style.top = value + 'px');
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
if (length) {
|
if (length) {
|
||||||
source.style[mode.toLowerCase()] = length + 'px';
|
source.style[mode.toLowerCase()] = length + 'px';
|
||||||
setTargetPos(mode);
|
|
||||||
}
|
}
|
||||||
if (!source.layoutResizer) {
|
if (!source.layoutResizer) {
|
||||||
const resizer = createElement('div', {'class':'resizer'}),
|
const resizer = createElement('div', {'class':'resizer'}),
|
||||||
save = (data => Remote.saveSettings(0, data)).debounce(500),
|
save = (data => Remote.saveSettings(0, data)).debounce(500),
|
||||||
size = {},
|
size = {},
|
||||||
store = () => {
|
store = () => {
|
||||||
const value = setTargetPos(resizer.mode),
|
const value = ('Width' == resizer.mode) ? source.offsetWidth : source.offsetHeight,
|
||||||
prop = resizer.key + resizer.mode;
|
prop = resizer.key + resizer.mode;
|
||||||
(value == Local.get(prop)) || Local.set(prop, value);
|
(value == Local.get(prop)) || Local.set(prop, value);
|
||||||
(value == SettingsGet('Resizer' + prop)) || save({['Resizer' + prop]: value});
|
(value == SettingsGet('Resizer' + prop)) || save({['Resizer' + prop]: value});
|
||||||
|
|
@ -285,49 +258,38 @@ populateMessageBody = (oMessage, popup) => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let json = oData?.Result;
|
let json = oData?.Result;
|
||||||
|
if (json
|
||||||
if (
|
&& MessageModel.validJson(json)
|
||||||
json &&
|
&& oMessage.hash === json.hash
|
||||||
MessageModel.validJson(json) &&
|
// && oMessage.folder === json.folder
|
||||||
oMessage.folder === json.folder
|
// && oMessage.uid == json.uid
|
||||||
|
&& oMessage.revivePropertiesFromJson(json)
|
||||||
) {
|
) {
|
||||||
const threads = oMessage.threads(),
|
|
||||||
isNew = !popup && oMessage.uid != json.uid && threads.includes(json.uid),
|
|
||||||
messagesDom = MessageUserStore.bodiesDom();
|
|
||||||
if (isNew) {
|
|
||||||
oMessage = MessageModel.reviveFromJson(json);
|
|
||||||
if (oMessage) {
|
|
||||||
oMessage.threads(threads);
|
|
||||||
MessageFlagsCache.initMessage(oMessage);
|
|
||||||
|
|
||||||
// Set clone
|
|
||||||
oMessage = MessageModel.fromMessageListItem(oMessage);
|
|
||||||
}
|
|
||||||
MessageUserStore.message(oMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oMessage && oMessage.uid == json.uid) {
|
|
||||||
popup || MessageUserStore.error('');
|
|
||||||
/*
|
/*
|
||||||
if (bCached) {
|
if (bCached) {
|
||||||
delete json.flags;
|
delete json.flags;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
isNew || oMessage.revivePropertiesFromJson(json);
|
if (popup) {
|
||||||
|
oMessage.viewPopupMessage();
|
||||||
|
} else {
|
||||||
|
MessageUserStore.error('');
|
||||||
|
const messagesDom = MessageUserStore.bodiesDom();
|
||||||
if (messagesDom) {
|
if (messagesDom) {
|
||||||
let id = 'rl-msg-' + oMessage.hash.replace(/[^a-zA-Z0-9]/g, ''),
|
let id = 'rl-msg-' + oMessage.hash,
|
||||||
body = elementById(id);
|
body = elementById(id);
|
||||||
if (body) {
|
if (body) {
|
||||||
oMessage.body = body;
|
oMessage.body = body;
|
||||||
oMessage.isHtml(body.classList.contains('html'));
|
oMessage.isHtml(body.classList.contains('html'));
|
||||||
oMessage.hasImages(body.rlHasImages);
|
oMessage.hasImages(body.rlHasImages);
|
||||||
} else {
|
} else {
|
||||||
body = Element.fromHTML('<div id="' + id + '" hidden="" class="b-text-part '
|
body = createElement('div',{
|
||||||
|
id:id,
|
||||||
|
hidden:'',
|
||||||
|
class:'b-text-part'
|
||||||
+ (oMessage.pgpSigned() ? ' openpgp-signed' : '')
|
+ (oMessage.pgpSigned() ? ' openpgp-signed' : '')
|
||||||
+ (oMessage.pgpEncrypted() ? ' openpgp-encrypted' : '')
|
+ (oMessage.pgpEncrypted() ? ' openpgp-encrypted' : '')
|
||||||
+ '">'
|
});
|
||||||
+ '</div>');
|
|
||||||
oMessage.body = body;
|
oMessage.body = body;
|
||||||
if (!SettingsUserStore.viewHTML() || !oMessage.viewHtml()) {
|
if (!SettingsUserStore.viewHTML() || !oMessage.viewHtml()) {
|
||||||
oMessage.viewPlain();
|
oMessage.viewPlain();
|
||||||
|
|
@ -338,8 +300,8 @@ populateMessageBody = (oMessage, popup) => {
|
||||||
|
|
||||||
messagesDom.append(body);
|
messagesDom.append(body);
|
||||||
|
|
||||||
popup || (oMessage.body.hidden = false);
|
oMessage.body.hidden = false;
|
||||||
popup && oMessage.viewPopupMessage();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageFlagsCache.initMessage(oMessage);
|
MessageFlagsCache.initMessage(oMessage);
|
||||||
|
|
@ -349,32 +311,6 @@ populateMessageBody = (oMessage, popup) => {
|
||||||
SettingsUserStore.messageReadDelay() * 1000 // seconds
|
SettingsUserStore.messageReadDelay() * 1000 // seconds
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNew) {
|
|
||||||
let selectedMessage = MessagelistUserStore.selectedMessage();
|
|
||||||
if (
|
|
||||||
selectedMessage &&
|
|
||||||
(oMessage.folder !== selectedMessage.folder || oMessage.uid != selectedMessage.uid)
|
|
||||||
) {
|
|
||||||
MessagelistUserStore.selectedMessage(null);
|
|
||||||
if (1 === MessagelistUserStore.length) {
|
|
||||||
MessagelistUserStore.focusedMessage(null);
|
|
||||||
}
|
|
||||||
} else if (!selectedMessage) {
|
|
||||||
selectedMessage = MessagelistUserStore.find(
|
|
||||||
subMessage =>
|
|
||||||
subMessage &&
|
|
||||||
subMessage.folder === oMessage.folder &&
|
|
||||||
subMessage.uid == oMessage.uid
|
|
||||||
);
|
|
||||||
|
|
||||||
if (selectedMessage) {
|
|
||||||
MessagelistUserStore.selectedMessage(selectedMessage);
|
|
||||||
MessagelistUserStore.focusedMessage(selectedMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
popup || MessageUserStore.loading(false);
|
popup || MessageUserStore.loading(false);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,29 @@
|
||||||
import { doc, createElement, addEventsListeners } from 'Common/Globals';
|
import { doc, createElement, addEventsListeners } from 'Common/Globals';
|
||||||
import { EmailModel } from 'Model/Email';
|
import { EmailModel, addressparser } from 'Model/Email';
|
||||||
|
|
||||||
const contentType = 'snappymail/emailaddress',
|
const contentType = 'snappymail/emailaddress',
|
||||||
getAddressKey = li => li?.emailaddress?.key;
|
getAddressKey = li => li?.emailaddress?.key,
|
||||||
|
|
||||||
|
parseEmailLine = line => addressparser(line).map(item =>
|
||||||
|
(item.name || item.email)
|
||||||
|
? new EmailModel(item.email, item.name) : null
|
||||||
|
).filter(v => v),
|
||||||
|
splitEmailLine = line => {
|
||||||
|
const result = [];
|
||||||
|
let exists = false;
|
||||||
|
addressparser(line).forEach(item => {
|
||||||
|
const address = (item.name || item.email)
|
||||||
|
? new EmailModel(item.email, item.name)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (address?.email) {
|
||||||
|
exists = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(address ? address.toLine() : item.name);
|
||||||
|
});
|
||||||
|
return exists ? result : null;
|
||||||
|
};
|
||||||
|
|
||||||
let dragAddress, datalist;
|
let dragAddress, datalist;
|
||||||
|
|
||||||
|
|
@ -154,8 +175,8 @@ export class EmailAddressesComponent {
|
||||||
if (val) {
|
if (val) {
|
||||||
const self = this,
|
const self = this,
|
||||||
v = val.trim(),
|
v = val.trim(),
|
||||||
hook = (v && [',', ';', '\n'].includes(v.slice(-1))) ? EmailModel.splitEmailLine(val) : null,
|
hook = (v && [',', ';', '\n'].includes(v.slice(-1))) ? splitEmailLine(val) : null,
|
||||||
values = (hook || [val]).map(value => EmailModel.parseEmailLine(value))
|
values = (hook || [val]).map(value => parseEmailLine(value))
|
||||||
.flat(Infinity)
|
.flat(Infinity)
|
||||||
.map(item => (item.toLine ? [item.toLine(), item] : [item, null]));
|
.map(item => (item.toLine ? [item.toLine(), item] : [item, null]));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ export class AbstractViewPopup extends AbstractView
|
||||||
super('Popups' + name, ViewTypePopup);
|
super('Popups' + name, ViewTypePopup);
|
||||||
this.keyScope.scope = name;
|
this.keyScope.scope = name;
|
||||||
this.modalVisible = ko.observable(false).extend({ rateLimit: 0 });
|
this.modalVisible = ko.observable(false).extend({ rateLimit: 0 });
|
||||||
|
this.close = () => this.modalVisible(false);
|
||||||
addShortcut('escape,close', '', name, () => {
|
addShortcut('escape,close', '', name, () => {
|
||||||
if (this.modalVisible() && false !== this.onClose()) {
|
if (this.modalVisible() && false !== this.onClose()) {
|
||||||
this.close();
|
this.close();
|
||||||
|
|
@ -78,8 +79,6 @@ export class AbstractViewPopup extends AbstractView
|
||||||
afterShow() {} // Happens after showModal() animation transitionend
|
afterShow() {} // Happens after showModal() animation transitionend
|
||||||
onHide() {} // Happens before animation transitionend
|
onHide() {} // Happens before animation transitionend
|
||||||
afterHide() {} // Happens after animation transitionend
|
afterHide() {} // Happens after animation transitionend
|
||||||
|
|
||||||
close() {}
|
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import ko from 'ko';
|
import ko from 'ko';
|
||||||
import { koComputable } from 'External/ko';
|
import { koComputable } from 'External/ko';
|
||||||
import { doc, $htmlCL, elementById, fireEvent } from 'Common/Globals';
|
import { doc, $htmlCL, elementById, createElement, fireEvent } from 'Common/Globals';
|
||||||
import { forEachObjectEntry } from 'Common/Utils';
|
import { forEachObjectEntry } from 'Common/Utils';
|
||||||
import { i18nToNodes } from 'Common/Translator';
|
import { i18nToNodes } from 'Common/Translator';
|
||||||
|
|
||||||
|
|
@ -40,22 +40,20 @@ const
|
||||||
ViewModelClass.__vm = vm;
|
ViewModelClass.__vm = vm;
|
||||||
|
|
||||||
if (vmPlace) {
|
if (vmPlace) {
|
||||||
vmDom = Element.fromHTML(dialog
|
vmDom = dialog
|
||||||
? '<dialog id="V-'+ id + '"></dialog>'
|
? createElement('dialog',{id:'V-'+id})
|
||||||
: '<div id="V-'+ id + '" hidden=""></div>');
|
: createElement('div',{id:'V-'+id,hidden:''})
|
||||||
vmPlace.append(vmDom);
|
vmPlace.append(vmDom);
|
||||||
|
|
||||||
vm.viewModelDom = ViewModelClass.__dom = vmDom;
|
vm.viewModelDom = ViewModelClass.__dom = vmDom;
|
||||||
|
|
||||||
if (dialog) {
|
if (dialog) {
|
||||||
vm.close = () => hideScreenPopup(ViewModelClass);
|
|
||||||
|
|
||||||
// Firefox < 98 / Safari < 15.4 HTMLDialogElement not defined
|
// Firefox < 98 / Safari < 15.4 HTMLDialogElement not defined
|
||||||
if (!vmDom.showModal) {
|
if (!vmDom.showModal) {
|
||||||
vmDom.className = 'polyfill';
|
vmDom.className = 'polyfill';
|
||||||
vmDom.showModal = () => {
|
vmDom.showModal = () => {
|
||||||
vmDom.backdrop ||
|
vmDom.backdrop ||
|
||||||
vmDom.before(vmDom.backdrop = Element.fromHTML('<div class="dialog-backdrop"></div>'));
|
vmDom.before(vmDom.backdrop = createElement('div',{class:'dialog-backdrop'}));
|
||||||
vmDom.setAttribute('open','');
|
vmDom.setAttribute('open','');
|
||||||
vmDom.open = true;
|
vmDom.open = true;
|
||||||
vmDom.returnValue = null;
|
vmDom.returnValue = null;
|
||||||
|
|
@ -155,36 +153,20 @@ const
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Function} ViewModelClassToHide
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
hideScreenPopup = ViewModelClassToHide =>
|
|
||||||
ViewModelClassToHide?.__vm && ViewModelClassToHide.__dom
|
|
||||||
&& ViewModelClassToHide.__vm.modalVisible(false),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} screenName
|
* @param {string} screenName
|
||||||
* @param {string} subPart
|
* @param {string} subPart
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
screenOnRoute = (screenName, subPart) => {
|
screenOnRoute = (screenName, subPart) => {
|
||||||
let vmScreen = null,
|
screenName = screenName || defaultScreenName;
|
||||||
isSameScreen = false;
|
if (screenName && fireEvent('sm-show-screen', screenName, 1)) {
|
||||||
|
|
||||||
if (null == screenName || '' == screenName) {
|
|
||||||
screenName = defaultScreenName;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fireEvent('sm-show-screen', screenName, 1)) {
|
|
||||||
|
|
||||||
// Close all popups
|
// Close all popups
|
||||||
for (let vm of visiblePopups) {
|
for (let vm of visiblePopups) {
|
||||||
(false === vm.onClose()) || vm.close();
|
(false === vm.onClose()) || vm.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screenName) {
|
let vmScreen = screen(screenName);
|
||||||
vmScreen = screen(screenName);
|
|
||||||
if (!vmScreen) {
|
if (!vmScreen) {
|
||||||
vmScreen = screen(defaultScreenName);
|
vmScreen = screen(defaultScreenName);
|
||||||
if (vmScreen) {
|
if (vmScreen) {
|
||||||
|
|
@ -194,7 +176,7 @@ const
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vmScreen?.__started) {
|
if (vmScreen?.__started) {
|
||||||
isSameScreen = currentScreen && vmScreen === currentScreen;
|
let isSameScreen = currentScreen && vmScreen === currentScreen;
|
||||||
|
|
||||||
if (!vmScreen.__builded) {
|
if (!vmScreen.__builded) {
|
||||||
vmScreen.__builded = true;
|
vmScreen.__builded = true;
|
||||||
|
|
@ -231,7 +213,6 @@ const
|
||||||
}, 1);
|
}, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -285,7 +266,7 @@ export const
|
||||||
});
|
});
|
||||||
|
|
||||||
const cross = new Crossroads();
|
const cross = new Crossroads();
|
||||||
cross.addRoute(/^([a-zA-Z0-9-]*)\/?(.*)$/, screenOnRoute);
|
cross.addRoute(/^([^/]*)\/?(.*)$/, screenOnRoute);
|
||||||
|
|
||||||
hasher.add(cross.parse.bind(cross));
|
hasher.add(cross.parse.bind(cross));
|
||||||
hasher.init();
|
hasher.init();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
|
|
||||||
import { ParseMime } from 'Mime/Parser';
|
import { ParseMime } from 'Mime/Parser';
|
||||||
import { AttachmentModel } from 'Model/Attachment';
|
import { AttachmentModel } from 'Model/Attachment';
|
||||||
import { EmailModel } from 'Model/Email';
|
|
||||||
import { FileInfo } from 'Common/File';
|
import { FileInfo } from 'Common/File';
|
||||||
import { BEGIN_PGP_MESSAGE } from 'Stores/User/Pgp';
|
import { BEGIN_PGP_MESSAGE } from 'Stores/User/Pgp';
|
||||||
|
|
||||||
|
|
@ -17,16 +16,10 @@ export function MimeToMessage(data, message)
|
||||||
let html = struct.getByContentType('text/html');
|
let html = struct.getByContentType('text/html');
|
||||||
html = html ? html.body : '';
|
html = html ? html.body : '';
|
||||||
|
|
||||||
if (struct.headers.subject) {
|
message.subject(struct.headerValue('subject') || '');
|
||||||
message.subject(struct.headers.subject.value);
|
|
||||||
}
|
// EmailCollectionModel
|
||||||
['from','to'].forEach(name => {
|
['from','to'].forEach(name => message[name].fromString(struct.headerValue(name)));
|
||||||
if (struct.headers[name] && !message[name].length) {
|
|
||||||
let mail = new EmailModel;
|
|
||||||
mail.parse(struct.headers[name].value);
|
|
||||||
message[name].push(mail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
struct.forEach(part => {
|
struct.forEach(part => {
|
||||||
let cd = part.header('content-disposition'),
|
let cd = part.header('content-disposition'),
|
||||||
|
|
@ -40,7 +33,7 @@ export function MimeToMessage(data, message)
|
||||||
attachment.fileNameExt = attachment.fileName.replace(/^.+(\.[a-z]+)$/, '$1');
|
attachment.fileNameExt = attachment.fileName.replace(/^.+(\.[a-z]+)$/, '$1');
|
||||||
attachment.fileType = FileInfo.getType('', type.value);
|
attachment.fileType = FileInfo.getType('', type.value);
|
||||||
attachment.url = part.dataUrl;
|
attachment.url = part.dataUrl;
|
||||||
attachment.friendlySize = FileInfo.friendlySize(part.body.length);
|
attachment.estimatedSize = part.body.length;
|
||||||
/*
|
/*
|
||||||
attachment.isThumbnail = false;
|
attachment.isThumbnail = false;
|
||||||
attachment.contentLocation = '';
|
attachment.contentLocation = '';
|
||||||
|
|
|
||||||
|
|
@ -18,157 +18,24 @@ import { AbstractModel } from 'Knoin/AbstractModel';
|
||||||
* @param {String} str Address field
|
* @param {String} str Address field
|
||||||
* @return {Array} An array of address objects
|
* @return {Array} An array of address objects
|
||||||
*/
|
*/
|
||||||
function addressparser(str) {
|
export function addressparser(str) {
|
||||||
var tokenizer = new Tokenizer(str);
|
str = (str || '').toString();
|
||||||
var tokens = tokenizer.tokenize();
|
|
||||||
var addresses = [];
|
|
||||||
var address = [];
|
|
||||||
var parsedAddresses = [];
|
|
||||||
|
|
||||||
tokens.forEach(token => {
|
let
|
||||||
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
|
endOperator = '',
|
||||||
address.length && addresses.push(address);
|
node = {
|
||||||
address = [];
|
type: 'text',
|
||||||
} else {
|
value: ''
|
||||||
address.push(token);
|
},
|
||||||
}
|
escaped = false,
|
||||||
});
|
address = [],
|
||||||
|
addresses = [];
|
||||||
|
|
||||||
address.length && addresses.push(address);
|
const
|
||||||
|
/*
|
||||||
addresses.forEach(address => {
|
|
||||||
address = _handleAddress(address);
|
|
||||||
if (address.length) {
|
|
||||||
parsedAddresses = parsedAddresses.concat(address);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return parsedAddresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts tokens for a single address into an address object
|
|
||||||
*
|
|
||||||
* @param {Array} tokens Tokens object
|
|
||||||
* @return {Object} Address object
|
|
||||||
*/
|
|
||||||
function _handleAddress(tokens) {
|
|
||||||
var isGroup = false;
|
|
||||||
var state = 'text';
|
|
||||||
var address = void 0;
|
|
||||||
var addresses = [];
|
|
||||||
var data = {
|
|
||||||
address: [],
|
|
||||||
comment: [],
|
|
||||||
group: [],
|
|
||||||
text: []
|
|
||||||
};
|
|
||||||
|
|
||||||
// Filter out <addresses>, (comments) and regular text
|
|
||||||
tokens.forEach(token => {
|
|
||||||
if (token.type === 'operator') {
|
|
||||||
switch (token.value) {
|
|
||||||
case '<':
|
|
||||||
state = 'address';
|
|
||||||
break;
|
|
||||||
case '(':
|
|
||||||
state = 'comment';
|
|
||||||
break;
|
|
||||||
case ':':
|
|
||||||
state = 'group';
|
|
||||||
isGroup = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
state = 'text';
|
|
||||||
}
|
|
||||||
} else if (token.value) {
|
|
||||||
data[state].push(token.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If there is no text but a comment, replace the two
|
|
||||||
if (!data.text.length && data.comment.length) {
|
|
||||||
data.text = data.comment;
|
|
||||||
data.comment = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isGroup) {
|
|
||||||
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
|
|
||||||
data.text = data.text.join(' ');
|
|
||||||
addresses.push({
|
|
||||||
name: data.text || address && address.name,
|
|
||||||
group: data.group.length ? addressparser(data.group.join(',')) : []
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If no address was found, try to detect one from regular text
|
|
||||||
if (!data.address.length && data.text.length) {
|
|
||||||
var i = data.text.length;
|
|
||||||
while (i--) {
|
|
||||||
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
|
|
||||||
data.address = data.text.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// still no address
|
|
||||||
if (!data.address.length) {
|
|
||||||
i = data.text.length;
|
|
||||||
while (i--) {
|
|
||||||
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, address => {
|
|
||||||
if (!data.address.length) {
|
|
||||||
data.address = [address.trim()];
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return address.trim();
|
|
||||||
});
|
|
||||||
if (data.address.length) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's still is no text but a comment exixts, replace the two
|
|
||||||
if (!data.text.length && data.comment.length) {
|
|
||||||
data.text = data.comment;
|
|
||||||
data.comment = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep only the first address occurence, push others to regular text
|
|
||||||
if (data.address.length > 1) {
|
|
||||||
data.text = data.text.concat(data.address.splice(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join values with spaces
|
|
||||||
data.text = data.text.join(' ');
|
|
||||||
data.address = data.address.join(' ');
|
|
||||||
|
|
||||||
if (!data.address && isGroup) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
address = {
|
|
||||||
address: data.address || data.text || '',
|
|
||||||
name: data.text || data.address || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
if (address.address === address.name) {
|
|
||||||
if ((address.address || '').match(/@/)) {
|
|
||||||
address.name = '';
|
|
||||||
} else {
|
|
||||||
address.address = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses.push(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
return addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Operator tokens and which tokens are expected to end the sequence
|
* Operator tokens and which tokens are expected to end the sequence
|
||||||
*/
|
*/
|
||||||
var OPERATORS = {
|
OPERATORS = {
|
||||||
'"': '"',
|
'"': '"',
|
||||||
'(': ')',
|
'(': ')',
|
||||||
'<': '>',
|
'<': '>',
|
||||||
|
|
@ -182,76 +49,155 @@ var OPERATORS = {
|
||||||
// comma in their UI, it makes sense to treat them the same as a comma
|
// comma in their UI, it makes sense to treat them the same as a comma
|
||||||
// when used outside of a group.
|
// when used outside of a group.
|
||||||
';': ''
|
';': ''
|
||||||
};
|
},
|
||||||
|
pushToken = token => {
|
||||||
class Tokenizer
|
token.value = (token.value || '').toString().trim();
|
||||||
{
|
token.value.length && address.push(token);
|
||||||
constructor(str) {
|
node = {
|
||||||
this.str = (str || '').toString();
|
|
||||||
this.operatorCurrent = '';
|
|
||||||
this.operatorExpecting = '';
|
|
||||||
this.node = null;
|
|
||||||
this.escaped = false;
|
|
||||||
this.list = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenize() {
|
|
||||||
var list = [];
|
|
||||||
[...this.str].forEach(c => this.checkChar(c));
|
|
||||||
|
|
||||||
this.list.forEach(node => {
|
|
||||||
node.value = (node.value || '').toString().trim();
|
|
||||||
node.value && list.push(node);
|
|
||||||
});
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkChar(chr) {
|
|
||||||
if ((chr in OPERATORS || chr === '\\') && this.escaped) {
|
|
||||||
this.escaped = false;
|
|
||||||
} else if (this.operatorExpecting && chr === this.operatorExpecting) {
|
|
||||||
this.node = {
|
|
||||||
type: 'operator',
|
|
||||||
value: chr
|
|
||||||
};
|
|
||||||
this.list.push(this.node);
|
|
||||||
this.node = null;
|
|
||||||
this.operatorExpecting = '';
|
|
||||||
this.escaped = false;
|
|
||||||
return;
|
|
||||||
} else if (!this.operatorExpecting && chr in OPERATORS) {
|
|
||||||
this.node = {
|
|
||||||
type: 'operator',
|
|
||||||
value: chr
|
|
||||||
};
|
|
||||||
this.list.push(this.node);
|
|
||||||
this.node = null;
|
|
||||||
this.operatorExpecting = OPERATORS[chr];
|
|
||||||
this.escaped = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.escaped && chr === '\\') {
|
|
||||||
this.escaped = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.node) {
|
|
||||||
this.node = {
|
|
||||||
type: 'text',
|
type: 'text',
|
||||||
value: ''
|
value: ''
|
||||||
|
},
|
||||||
|
escaped = false;
|
||||||
|
},
|
||||||
|
pushAddress = () => {
|
||||||
|
if (address.length) {
|
||||||
|
address = _handleAddress(address);
|
||||||
|
if (address.length) {
|
||||||
|
addresses = addresses.concat(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
address = [];
|
||||||
};
|
};
|
||||||
this.list.push(this.node);
|
|
||||||
|
[...str].forEach(chr => {
|
||||||
|
if (!escaped && (chr === endOperator || (!endOperator && chr in OPERATORS))) {
|
||||||
|
pushToken(node);
|
||||||
|
if (',' === chr || ';' === chr) {
|
||||||
|
pushAddress();
|
||||||
|
} else {
|
||||||
|
endOperator = endOperator ? '' : OPERATORS[chr];
|
||||||
|
if ('<' === chr) {
|
||||||
|
node.type = 'email';
|
||||||
|
} else if ('(' === chr) {
|
||||||
|
node.type = 'comment';
|
||||||
|
} else if (':' === chr) {
|
||||||
|
node.type = 'group';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
node.value += chr;
|
||||||
|
escaped = !escaped && '\\' === chr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
pushToken(node);
|
||||||
|
|
||||||
|
pushAddress();
|
||||||
|
|
||||||
|
return addresses;
|
||||||
|
// return addresses.map(item => (item.name || item.email) ? new EmailModel(item.email, item.name) : null).filter(v => v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts tokens for a single address into an address object
|
||||||
|
*
|
||||||
|
* @param {Array} tokens Tokens object
|
||||||
|
* @return {Object} Address object
|
||||||
|
*/
|
||||||
|
function _handleAddress(tokens) {
|
||||||
|
let
|
||||||
|
isGroup = false,
|
||||||
|
address = {},
|
||||||
|
addresses = [],
|
||||||
|
data = {
|
||||||
|
email: [],
|
||||||
|
comment: [],
|
||||||
|
group: [],
|
||||||
|
text: []
|
||||||
|
};
|
||||||
|
|
||||||
|
tokens.forEach(token => {
|
||||||
|
isGroup = isGroup || 'group' === token.type;
|
||||||
|
data[token.type].push(token.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// If there is no text but a comment, replace the two
|
||||||
|
if (!data.text.length && data.comment.length) {
|
||||||
|
data.text = data.comment;
|
||||||
|
data.comment = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.escaped && chr !== '\\') {
|
if (isGroup) {
|
||||||
this.node.value += '\\';
|
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
|
||||||
|
/*
|
||||||
|
addresses.push({
|
||||||
|
email: '',
|
||||||
|
name: data.text.join(' ').trim(),
|
||||||
|
group: addressparser(data.group.join(','))
|
||||||
|
// ,comment: data.comment.join(' ').trim()
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
addresses = addresses.concat(addressparser(data.group.join(',')));
|
||||||
|
} else {
|
||||||
|
// If no address was found, try to detect one from regular text
|
||||||
|
if (!data.email.length && data.text.length) {
|
||||||
|
var i = data.text.length;
|
||||||
|
while (i--) {
|
||||||
|
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
|
||||||
|
data.email = data.text.splice(i, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.node.value += chr;
|
// still no address
|
||||||
this.escaped = false;
|
if (!data.email.length) {
|
||||||
|
i = data.text.length;
|
||||||
|
while (i--) {
|
||||||
|
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, address => {
|
||||||
|
if (!data.email.length) {
|
||||||
|
data.email = [address.trim()];
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
return address.trim();
|
||||||
|
});
|
||||||
|
if (data.email.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's still no text but a comment exists, replace the two
|
||||||
|
if (!data.text.length && data.comment.length) {
|
||||||
|
data.text = data.comment;
|
||||||
|
data.comment = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep only the first address occurence, push others to regular text
|
||||||
|
if (data.email.length > 1) {
|
||||||
|
data.text = data.text.concat(data.email.splice(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
address = {
|
||||||
|
// Join values with spaces
|
||||||
|
email: data.email.join(' ').trim(),
|
||||||
|
name: data.text.join(' ').trim()
|
||||||
|
// ,comment: data.comment.join(' ').trim()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (address.email === address.name) {
|
||||||
|
if (address.email.includes('@')) {
|
||||||
|
address.name = '';
|
||||||
|
} else {
|
||||||
|
address.email = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// address.email = address.email.replace(/^[<]+(.*)[>]+$/g, '$1');
|
||||||
|
|
||||||
|
addresses.push(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EmailModel extends AbstractModel {
|
export class EmailModel extends AbstractModel {
|
||||||
|
|
@ -260,13 +206,12 @@ export class EmailModel extends AbstractModel {
|
||||||
* @param {string=} name = ''
|
* @param {string=} name = ''
|
||||||
* @param {string=} dkimStatus = 'none'
|
* @param {string=} dkimStatus = 'none'
|
||||||
*/
|
*/
|
||||||
constructor(email = '', name = '', dkimStatus = 'none') {
|
constructor(email, name, dkimStatus = 'none') {
|
||||||
super();
|
super();
|
||||||
this.email = email;
|
this.email = email || '';
|
||||||
this.name = name;
|
this.name = name || '';
|
||||||
this.dkimStatus = dkimStatus;
|
this.dkimStatus = dkimStatus;
|
||||||
|
this.cleanup();
|
||||||
this.clearDuplicateName();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -276,60 +221,34 @@ export class EmailModel extends AbstractModel {
|
||||||
*/
|
*/
|
||||||
static reviveFromJson(json) {
|
static reviveFromJson(json) {
|
||||||
const email = super.reviveFromJson(json);
|
const email = super.reviveFromJson(json);
|
||||||
email?.clearDuplicateName();
|
email?.cleanup();
|
||||||
return email;
|
return email?.valid() ? email : null;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
clear() {
|
|
||||||
this.email = '';
|
|
||||||
this.name = '';
|
|
||||||
|
|
||||||
this.dkimStatus = 'none';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
validate() {
|
valid() {
|
||||||
return this.name || this.email;
|
return this.name || this.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} withoutName = false
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
hash(withoutName = false) {
|
|
||||||
return '#' + (withoutName ? '' : this.name) + '#' + this.email + '#';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
clearDuplicateName() {
|
cleanup() {
|
||||||
if (this.name === this.email) {
|
if (this.name === this.email) {
|
||||||
this.name = '';
|
this.name = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} query
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
search(query) {
|
|
||||||
return (this.name + ' ' + this.email).toLowerCase().includes(query.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean} friendlyView = false
|
* @param {boolean} friendlyView = false
|
||||||
* @param {boolean} wrapWithLink = false
|
* @param {boolean} wrapWithLink = false
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
toLine(friendlyView, wrapWithLink) {
|
toLine(friendlyView, wrapWithLink) {
|
||||||
let result = this.email,
|
let name = this.name,
|
||||||
name = this.name,
|
result = this.email,
|
||||||
toLink = text =>
|
toLink = text =>
|
||||||
'<a href="mailto:'
|
'<a href="mailto:'
|
||||||
+ encodeHtml(result) + (name ? '?to=' + encodeURIComponent('"' + name + '" <' + result + '>') : '')
|
+ encodeHtml(result) + (name ? '?to=' + encodeURIComponent('"' + name + '" <' + result + '>') : '')
|
||||||
|
|
@ -348,47 +267,6 @@ export class EmailModel extends AbstractModel {
|
||||||
result = toLink();
|
result = toLink();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result || name;
|
||||||
}
|
|
||||||
|
|
||||||
static splitEmailLine(line) {
|
|
||||||
const result = [];
|
|
||||||
let exists = false;
|
|
||||||
addressparser(line).forEach(item => {
|
|
||||||
const address = item.address
|
|
||||||
? new EmailModel(item.address.replace(/^[<]+(.*)[>]+$/g, '$1'), item.name || '')
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (address?.email) {
|
|
||||||
exists = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push(address ? address.toLine() : item.name);
|
|
||||||
});
|
|
||||||
return exists ? result : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static parseEmailLine(line) {
|
|
||||||
return addressparser(line).map(item =>
|
|
||||||
item.address ? new EmailModel(item.address.replace(/^[<]+(.*)[>]+$/g, '$1'), item.name || '') : null
|
|
||||||
).filter(v => v);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} emailAddress
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
parse(emailAddress) {
|
|
||||||
emailAddress = emailAddress.trim();
|
|
||||||
if (emailAddress) {
|
|
||||||
const result = addressparser(emailAddress);
|
|
||||||
if (result.length) {
|
|
||||||
this.name = result[0].name || '';
|
|
||||||
this.email = result[0].address || '';
|
|
||||||
this.clearDuplicateName();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { AbstractCollectionModel } from 'Model/AbstractCollection';
|
import { AbstractCollectionModel } from 'Model/AbstractCollection';
|
||||||
import { EmailModel } from 'Model/Email';
|
import { EmailModel, addressparser } from 'Model/Email';
|
||||||
|
import { forEachObjectValue } from 'Common/Utils';
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
|
@ -13,6 +14,16 @@ export class EmailCollectionModel extends AbstractCollectionModel
|
||||||
return super.reviveFromJson(items, email => EmailModel.reviveFromJson(email));
|
return super.reviveFromJson(items, email => EmailModel.reviveFromJson(email));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @returns {EmailCollectionModel}
|
||||||
|
*/
|
||||||
|
static fromString(str) {
|
||||||
|
let list = new this();
|
||||||
|
list.fromString(str);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {boolean=} friendlyView = false
|
* @param {boolean=} friendlyView = false
|
||||||
* @param {boolean=} wrapWithLink = false
|
* @param {boolean=} wrapWithLink = false
|
||||||
|
|
@ -21,4 +32,23 @@ export class EmailCollectionModel extends AbstractCollectionModel
|
||||||
toString(friendlyView, wrapWithLink) {
|
toString(friendlyView, wrapWithLink) {
|
||||||
return this.map(email => email.toLine(friendlyView, wrapWithLink)).join(', ');
|
return this.map(email => email.toLine(friendlyView, wrapWithLink)).join(', ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
fromString(str) {
|
||||||
|
if (str) {
|
||||||
|
let items = {}, key;
|
||||||
|
addressparser(str).forEach(item => {
|
||||||
|
item = new EmailModel(item.email, item.name);
|
||||||
|
// Make them unique
|
||||||
|
key = item.email || item.name;
|
||||||
|
if (key && (item.name || !items[key])) {
|
||||||
|
items[key] = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
forEachObjectValue(items, item => this.push(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 { isFunction, forEachObjectEntry } from 'Common/Utils';
|
import { forEachObjectEntry } 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';
|
||||||
|
|
||||||
|
|
@ -25,6 +25,8 @@ import { LanguageStore } from 'Stores/Language';
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
const
|
const
|
||||||
|
msgHtml = msg => cleanHtml(msg.html(), msg.attachments(), '#rl-msg-' + msg.hash),
|
||||||
|
|
||||||
toggleTag = (message, keyword) => {
|
toggleTag = (message, keyword) => {
|
||||||
const lower = keyword.toLowerCase(),
|
const lower = keyword.toLowerCase(),
|
||||||
flags = message.flags,
|
flags = message.flags,
|
||||||
|
|
@ -212,6 +214,7 @@ export class MessageModel extends AbstractModel {
|
||||||
// this.attachments(AttachmentCollectionModel.reviveFromJson(json.attachments, this.foundCIDs));
|
// this.attachments(AttachmentCollectionModel.reviveFromJson(json.attachments, this.foundCIDs));
|
||||||
|
|
||||||
this.computeSenderEmail();
|
this.computeSenderEmail();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,7 +293,7 @@ export class MessageModel extends AbstractModel {
|
||||||
viewHtml() {
|
viewHtml() {
|
||||||
const body = this.body;
|
const body = this.body;
|
||||||
if (body && this.html()) {
|
if (body && this.html()) {
|
||||||
let result = cleanHtml(this.html(), this.attachments());
|
let result = msgHtml(this);
|
||||||
this.hasExternals(result.hasExternals);
|
this.hasExternals(result.hasExternals);
|
||||||
this.hasImages(body.rlHasImages = !!result.hasExternals);
|
this.hasImages(body.rlHasImages = !!result.hasExternals);
|
||||||
|
|
||||||
|
|
@ -300,9 +303,7 @@ export class MessageModel extends AbstractModel {
|
||||||
body.classList.toggle('plain', 0);
|
body.classList.toggle('plain', 0);
|
||||||
|
|
||||||
if (!this.isSpam() && FolderUserStore.spamFolder() != this.folder) {
|
if (!this.isSpam() && FolderUserStore.spamFolder() != this.folder) {
|
||||||
if (('dkim' === SettingsUserStore.viewImages() && 'pass' === this.dkim[0]?.[0])
|
if ('always' === SettingsUserStore.viewImages()) {
|
||||||
|| 'always' === SettingsUserStore.viewImages()
|
|
||||||
) {
|
|
||||||
this.showExternalImages();
|
this.showExternalImages();
|
||||||
}
|
}
|
||||||
if ('match' === SettingsUserStore.viewImages()) {
|
if ('match' === SettingsUserStore.viewImages()) {
|
||||||
|
|
@ -335,26 +336,30 @@ export class MessageModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
viewPopupMessage(print) {
|
viewPopupMessage(print) {
|
||||||
const timeStampInUTC = this.dateTimeStampInUTC() || 0,
|
const
|
||||||
|
timeStampInUTC = this.dateTimeStampInUTC() || 0,
|
||||||
ccLine = this.cc.toString(),
|
ccLine = this.cc.toString(),
|
||||||
|
bccLine = this.bcc.toString(),
|
||||||
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
|
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
|
||||||
win = open(''),
|
win = open('', 'sm-msg-'+this.requestHash
|
||||||
sdoc = win.document;
|
/*,newWindow ? 'innerWidth=' + elementById('V-MailMessageView').clientWidth : ''*/
|
||||||
let subject = encodeHtml(this.subject()),
|
),
|
||||||
|
sdoc = win.document,
|
||||||
|
subject = encodeHtml(this.subject()),
|
||||||
mode = this.isHtml() ? 'div' : 'pre',
|
mode = this.isHtml() ? 'div' : 'pre',
|
||||||
cc = ccLine ? `<div>${encodeHtml(i18n('GLOBAL/CC'))}: ${encodeHtml(ccLine)}</div>` : '',
|
to = `<div>${encodeHtml(i18n('GLOBAL/TO'))}: ${encodeHtml(this.to)}</div>`
|
||||||
|
+ (ccLine ? `<div>${encodeHtml(i18n('GLOBAL/CC'))}: ${encodeHtml(ccLine)}</div>` : '')
|
||||||
|
+ (bccLine ? `<div>${encodeHtml(i18n('GLOBAL/BCC'))}: ${encodeHtml(bccLine)}</div>` : ''),
|
||||||
style = getComputedStyle(doc.querySelector('.messageView')),
|
style = getComputedStyle(doc.querySelector('.messageView')),
|
||||||
prop = property => style.getPropertyValue(property);
|
prop = property => style.getPropertyValue(property);
|
||||||
sdoc.write(PreviewHTML
|
sdoc.write(PreviewHTML
|
||||||
.replace('<title>', '<title>'+subject)
|
.replace('<title>', '<title>'+subject)
|
||||||
// eslint-disable-next-line max-len
|
// eslint-disable-next-line max-len
|
||||||
.replace('<body>', `<body style="background-color:${prop('background-color')};color:${prop('color')}"><header><h1>${subject}</h1><time>${encodeHtml(m ? m.format('LLL',0,LanguageStore.hourCycle()) : '')}</time><div>${encodeHtml(this.from)}</div><div>${encodeHtml(i18n('GLOBAL/TO'))}: ${encodeHtml(this.to)}</div>${cc}</header><${mode}>${this.bodyAsHTML()}</${mode}>`)
|
.replace('<body>', `<body style="background-color:${prop('background-color')};color:${prop('color')}"><header><h1>${subject}</h1><time>${encodeHtml(m ? m.format('LLL',0,LanguageStore.hourCycle()) : '')}</time><div>${encodeHtml(this.from)}</div>${to}</header><${mode}>${this.bodyAsHTML()}</${mode}>`)
|
||||||
);
|
);
|
||||||
sdoc.close();
|
sdoc.close();
|
||||||
|
|
||||||
if (print) {
|
print && setTimeout(() => win.print(), 100);
|
||||||
setTimeout(() => win.print(), 100);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -376,14 +381,12 @@ export class MessageModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {MessageModel} message
|
|
||||||
* @returns {MessageModel}
|
* @returns {MessageModel}
|
||||||
*/
|
*//*
|
||||||
static fromMessageListItem(message) {
|
clone() {
|
||||||
let self = new MessageModel();
|
let self = new MessageModel();
|
||||||
if (message) {
|
|
||||||
// Clone message values
|
// Clone message values
|
||||||
forEachObjectEntry(message, (key, value) => {
|
forEachObjectEntry(this, (key, value) => {
|
||||||
if (ko.isObservable(value)) {
|
if (ko.isObservable(value)) {
|
||||||
ko.isComputed(value) || self[key](value());
|
ko.isComputed(value) || self[key](value());
|
||||||
} else if (!isFunction(value)) {
|
} else if (!isFunction(value)) {
|
||||||
|
|
@ -391,30 +394,37 @@ export class MessageModel extends AbstractModel {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
self.computeSenderEmail();
|
self.computeSenderEmail();
|
||||||
}
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
showExternalImages(regex) {
|
showExternalImages(regex) {
|
||||||
const body = this.body;
|
const body = this.body;
|
||||||
if (body && this.hasImages()) {
|
if (body && this.hasImages()) {
|
||||||
if (regex) {
|
if (regex) {
|
||||||
regex = SettingsUserStore.viewImagesWhitelist()
|
regex = [];
|
||||||
.trim()
|
SettingsUserStore.viewImagesWhitelist().trim().split(/[\s\r\n,;]+/g).forEach(rule => {
|
||||||
.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&')
|
rule = rule.split('+');
|
||||||
.replace(/[\s\r\n,;:]+/g, '|')
|
rule[0] = rule[0].trim();
|
||||||
.replace(/\|+/g, '|');
|
if (rule[0]
|
||||||
|
&& (!rule.includes('spf') || 'pass' === this.spf[0]?.[0])
|
||||||
|
&& (!rule.includes('dkim') || 'pass' === this.dkim[0]?.[0])
|
||||||
|
&& (!rule.includes('dmarc') || 'pass' === this.dmarc[0]?.[0])
|
||||||
|
) {
|
||||||
|
regex.push(rule[0].replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
regex = regex.join('|').replace(/\|+/g, '|');
|
||||||
if (regex) {
|
if (regex) {
|
||||||
console.log('whitelist images = '+regex);
|
console.log('whitelist images = '+regex);
|
||||||
regex = new RegExp(regex);
|
regex = new RegExp(regex);
|
||||||
if (this.from[0]?.email.match(regex)) {
|
if (this.from[0]?.email.match(regex)) {
|
||||||
regex = 0;
|
regex = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let hasImages = false,
|
let hasImages = false,
|
||||||
isValid = src => {
|
isValid = src => {
|
||||||
if (!regex || src.match(regex)) {
|
if (null == regex || (regex && src.match(regex))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
hasImages = true;
|
hasImages = true;
|
||||||
|
|
@ -452,7 +462,7 @@ export class MessageModel extends AbstractModel {
|
||||||
);
|
);
|
||||||
return clone.innerHTML;
|
return clone.innerHTML;
|
||||||
}
|
}
|
||||||
let result = cleanHtml(this.html(), this.attachments())
|
let result = msgHtml(this);
|
||||||
return result.html || plainToHtml(this.plain());
|
return result.html || plainToHtml(this.plain());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { AbstractCollectionModel } from 'Model/AbstractCollection';
|
import { AbstractCollectionModel } from 'Model/AbstractCollection';
|
||||||
import { MessageModel } from 'Model/Message';
|
import { MessageModel } from 'Model/Message';
|
||||||
|
import { MessageUserStore } from 'Stores/User/Message';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MessageFlagsCache
|
MessageFlagsCache
|
||||||
|
|
@ -31,8 +32,11 @@ export class MessageCollectionModel extends AbstractCollectionModel
|
||||||
* @returns {MessageCollectionModel}
|
* @returns {MessageCollectionModel}
|
||||||
*/
|
*/
|
||||||
static reviveFromJson(object, cached) {
|
static reviveFromJson(object, cached) {
|
||||||
|
let msg = MessageUserStore.message();
|
||||||
return super.reviveFromJson(object, message => {
|
return super.reviveFromJson(object, message => {
|
||||||
message = MessageModel.reviveFromJson(message);
|
// If message is currently viewed, use that.
|
||||||
|
// Maybe then use msg.revivePropertiesFromJson(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);
|
cached ? MessageFlagsCache.initMessage(message) : MessageFlagsCache.store(message);
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
import { Scope } from 'Common/Enums';
|
import { Scope } from 'Common/Enums';
|
||||||
import { Layout, ClientSideKeyNameMessageListSize } from 'Common/EnumsUser';
|
import { doc, createElement, leftPanelDisabled, Settings } from 'Common/Globals';
|
||||||
import { doc, createElement, leftPanelDisabled, Settings, elementById } from 'Common/Globals';
|
|
||||||
import { pString, pInt } from 'Common/Utils';
|
import { pString, pInt } from 'Common/Utils';
|
||||||
import { setLayoutResizer, moveAction } from 'Common/UtilsUser';
|
import { moveAction } from 'Common/UtilsUser';
|
||||||
import { getFolderFromCacheList, getFolderFullName, getFolderInboxName } from 'Common/Cache';
|
import { getFolderFromCacheList, getFolderFullName, getFolderInboxName } from 'Common/Cache';
|
||||||
import { i18n, initOnStartOrLangChange } from 'Common/Translator';
|
import { i18n, initOnStartOrLangChange } from 'Common/Translator';
|
||||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
|
||||||
|
|
||||||
import { AppUserStore } from 'Stores/User/App';
|
import { AppUserStore } from 'Stores/User/App';
|
||||||
import { AccountUserStore } from 'Stores/User/Account';
|
import { AccountUserStore } from 'Stores/User/Account';
|
||||||
|
|
@ -113,24 +111,6 @@ export class MailBoxUserScreen extends AbstractScreen {
|
||||||
* @returns {void}
|
* @returns {void}
|
||||||
*/
|
*/
|
||||||
onBuild() {
|
onBuild() {
|
||||||
setTimeout(() => {
|
|
||||||
// initMailboxLayoutResizer
|
|
||||||
const top = elementById('V-MailMessageList'),
|
|
||||||
bottom = elementById('V-MailMessageView'),
|
|
||||||
fToggle = () => {
|
|
||||||
let layout = SettingsUserStore.layout();
|
|
||||||
setLayoutResizer(top, bottom, ClientSideKeyNameMessageListSize,
|
|
||||||
(ThemeStore.isMobile() || Layout.NoPreview === layout)
|
|
||||||
? 0
|
|
||||||
: (Layout.SidePreview === layout ? 'Width' : 'Height')
|
|
||||||
);
|
|
||||||
};
|
|
||||||
if (top && bottom) {
|
|
||||||
fToggle();
|
|
||||||
addEventListener('rl-layout', fToggle);
|
|
||||||
}
|
|
||||||
}, 1);
|
|
||||||
|
|
||||||
doc.addEventListener('click', event =>
|
doc.addEventListener('click', event =>
|
||||||
event.target.closest('#rl-right') && moveAction(false)
|
event.target.closest('#rl-right') && moveAction(false)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ 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',
|
'viewHTML', 'viewImages', 'viewImagesWhitelist', 'removeColors', 'allowStyles',
|
||||||
'hideDeleted', 'listInlineAttachments', 'simpleAttachmentsList', 'collapseBlockquotes', 'maxBlockquotesLevel',
|
'hideDeleted', 'listInlineAttachments', 'simpleAttachmentsList', 'collapseBlockquotes', 'maxBlockquotesLevel',
|
||||||
'useCheckboxesInList', 'listGrouped', 'useThreads', 'replySameFolder', 'msgDefaultAction', 'allowSpellcheck'
|
'useCheckboxesInList', 'listGrouped', 'useThreads', 'replySameFolder', 'msgDefaultAction', 'allowSpellcheck'
|
||||||
].forEach(name => this[name] = SettingsUserStore[name]);
|
].forEach(name => this[name] = SettingsUserStore[name]);
|
||||||
|
|
@ -102,7 +102,7 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
||||||
this.addSetting('Layout');
|
this.addSetting('Layout');
|
||||||
this.addSetting('MaxBlockquotesLevel');
|
this.addSetting('MaxBlockquotesLevel');
|
||||||
|
|
||||||
this.addSettings(['ViewHTML', 'ViewImages', 'ViewImagesWhitelist', 'HideDeleted',
|
this.addSettings(['ViewHTML', 'ViewImages', 'ViewImagesWhitelist', 'HideDeleted', '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']);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export const SettingsUserStore = new class {
|
||||||
viewImages: 0,
|
viewImages: 0,
|
||||||
viewImagesWhitelist: '',
|
viewImagesWhitelist: '',
|
||||||
removeColors: 0,
|
removeColors: 0,
|
||||||
|
allowStyles: 0,
|
||||||
collapseBlockquotes: 1,
|
collapseBlockquotes: 1,
|
||||||
maxBlockquotesLevel: 0,
|
maxBlockquotesLevel: 0,
|
||||||
listInlineAttachments: 0,
|
listInlineAttachments: 0,
|
||||||
|
|
@ -88,6 +89,7 @@ export const SettingsUserStore = new class {
|
||||||
self.viewImages(SettingsGet('ViewImages'));
|
self.viewImages(SettingsGet('ViewImages'));
|
||||||
self.viewImagesWhitelist(SettingsGet('ViewImagesWhitelist'));
|
self.viewImagesWhitelist(SettingsGet('ViewImagesWhitelist'));
|
||||||
self.removeColors(SettingsGet('RemoveColors'));
|
self.removeColors(SettingsGet('RemoveColors'));
|
||||||
|
self.allowStyles(SettingsGet('AllowStyles'));
|
||||||
self.collapseBlockquotes(SettingsGet('CollapseBlockquotes'));
|
self.collapseBlockquotes(SettingsGet('CollapseBlockquotes'));
|
||||||
self.maxBlockquotesLevel(SettingsGet('MaxBlockquotesLevel'));
|
self.maxBlockquotesLevel(SettingsGet('MaxBlockquotesLevel'));
|
||||||
self.listInlineAttachments(SettingsGet('ListInlineAttachments'));
|
self.listInlineAttachments(SettingsGet('ListInlineAttachments'));
|
||||||
|
|
|
||||||
|
|
@ -62,9 +62,14 @@
|
||||||
#rl-left {
|
#rl-left {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
#rl-content, #rl-right {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.message-selected {
|
.message-selected {
|
||||||
.close,
|
.close,
|
||||||
.infoParent,
|
.infoParent,
|
||||||
|
.top-toolbar,
|
||||||
.messageView .controls-handle,
|
.messageView .controls-handle,
|
||||||
#tags-dropdown-id,
|
#tags-dropdown-id,
|
||||||
.message-fixed-button-toolbar,
|
.message-fixed-button-toolbar,
|
||||||
|
|
@ -75,6 +80,7 @@
|
||||||
}
|
}
|
||||||
#V-MailMessageView {
|
#V-MailMessageView {
|
||||||
left: 0;
|
left: 0;
|
||||||
|
overflow: initial;
|
||||||
position: initial;
|
position: initial;
|
||||||
}
|
}
|
||||||
.messageView,
|
.messageView,
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ option:disabled {
|
||||||
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
-webkit-tap-highlight-color: rgba(0,0,0,0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#rl-content {
|
#rl-content:not([hidden]) {
|
||||||
display:flex;
|
display:flex;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,13 @@
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#rl-right {
|
#rl-right:not([hidden]) {
|
||||||
|
display: flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
max-width: 100vw;
|
width: 20%;
|
||||||
position: relative;
|
}
|
||||||
z-index: 1;
|
.rl-bottom-preview-pane #rl-right {
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -58,82 +60,28 @@ html:not(.rl-left-panel-disabled) #rl-left {
|
||||||
}
|
}
|
||||||
|
|
||||||
#rl-left > .resizer,
|
#rl-left > .resizer,
|
||||||
.rl-side-preview-pane #V-MailMessageList > .resizer {
|
.rl-side-preview-pane #V-MailMessageList .resizer {
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
height: 100%;
|
cursor: col-resize;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
width: 5px;
|
width: 5px;
|
||||||
}
|
}
|
||||||
|
.rl-bottom-preview-pane #V-MailMessageList .resizer {
|
||||||
.rl-side-preview-pane #V-MailMessageList {
|
|
||||||
resize: horizontal; /*overflow: auto;*/
|
|
||||||
min-width: 320px;
|
|
||||||
max-width: 60%;
|
|
||||||
}
|
|
||||||
.rl-bottom-preview-pane #V-MailMessageList {
|
|
||||||
resize: vertical; /*overflow: auto;*/
|
|
||||||
min-height: 200px;
|
|
||||||
max-height: 60%;
|
|
||||||
}
|
|
||||||
.rl-bottom-preview-pane #V-MailMessageList > .resizer {
|
|
||||||
cursor: ns-resize;
|
cursor: ns-resize;
|
||||||
height: 5px;
|
cursor: row-resize;
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html:not(.rl-left-panel-disabled) #rl-left > .resizer,
|
html:not(.rl-left-panel-disabled) #rl-left > .resizer,
|
||||||
#V-MailMessageList > .resizer {
|
#V-MailMessageList .resizer {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#V-MailMessageList {
|
|
||||||
position: relative;
|
|
||||||
height: 100%;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#V-MailMessageView {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 13px;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
.b-message-view-backdrop {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background: rgba(0, 0, 0, .4);
|
|
||||||
z-index: 10;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.backdrop-message {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
color: white;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 30px;
|
|
||||||
background: rgba(0, 0, 0, .6);
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: var(--border-radius, 5px);
|
|
||||||
text-shadow: 0 1px 1px #000;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
top: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html.rl-side-preview-pane {
|
|
||||||
#V-MailMessageView {
|
|
||||||
left: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#top-system-dropdown-id::after,
|
#top-system-dropdown-id::after,
|
||||||
#button-add-prop-dropdown-id::after {
|
#button-add-prop-dropdown-id::after {
|
||||||
content: '▼';
|
content: '▼';
|
||||||
|
|
@ -201,16 +149,6 @@ html.rl-side-preview-pane {
|
||||||
#rl-left {
|
#rl-left {
|
||||||
width: @rlLeftWidth + 20;
|
width: @rlLeftWidth + 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
#V-MailMessageList {
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.rl-side-preview-pane {
|
|
||||||
#V-MailMessageView {
|
|
||||||
left: 40%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* mobile and tablet */
|
/* mobile and tablet */
|
||||||
|
|
@ -253,17 +191,6 @@ html.rl-left-panel-disabled {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html.rl-no-preview-pane {
|
|
||||||
|
|
||||||
#V-MailMessageList {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
html.rl-mobile #rl-left > .resizer,
|
html.rl-mobile #rl-left > .resizer,
|
||||||
html.rl-no-preview-pane #rl-right .resizer {
|
html.rl-no-preview-pane #rl-right .resizer {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|
@ -278,28 +205,13 @@ html.rl-mobile {
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 50vw;
|
width: 50vw;
|
||||||
z-index: 2;
|
z-index: 3;
|
||||||
}
|
}
|
||||||
&.rl-left-panel-disabled #rl-left {
|
&.rl-left-panel-disabled #rl-left {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html.rl-bottom-preview-pane {
|
|
||||||
|
|
||||||
#V-MailMessageList {
|
|
||||||
height: 300px;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#V-MailMessageView {
|
|
||||||
top: 300px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
html.rl-mobile .hide-mobile,
|
html.rl-mobile .hide-mobile,
|
||||||
html:not(.rl-mobile) .show-mobile {
|
html:not(.rl-mobile) .show-mobile {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,31 @@ html.rl-mobile, html.rl-no-preview-pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#V-MailMessageList {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.rl-side-preview-pane #V-MailMessageList {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.rl-no-preview-pane #V-MailMessageList {
|
||||||
|
height: 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;
|
||||||
box-shadow: var(--smMainShadow);
|
box-shadow: var(--smMainShadow);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,35 @@
|
||||||
|
|
||||||
|
#V-MailMessageView {
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: auto;
|
||||||
|
padding-bottom: @rlLowMargin;
|
||||||
|
|
||||||
|
.b-message-view-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, .4);
|
||||||
|
z-index: 10;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.backdrop-message {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 30px;
|
||||||
|
background: rgba(0, 0, 0, .6);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: var(--border-radius, 5px);
|
||||||
|
text-shadow: 0 1px 1px #000;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
html.rl-no-preview-pane {
|
html.rl-no-preview-pane {
|
||||||
#rl-right:not(.message-selected) #V-MailMessageView {
|
#rl-right:not(.message-selected) #V-MailMessageView {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
@ -317,13 +348,13 @@ html.rl-bottom-preview-pane #V-MailMessageView .top-toolbar {
|
||||||
|
|
||||||
.sm-bq-switcher > summary {
|
.sm-bq-switcher > summary {
|
||||||
border: 1px solid #999;
|
border: 1px solid #999;
|
||||||
display: block;
|
|
||||||
width: 3em;
|
|
||||||
line-height: 1em;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
line-height: 1em;
|
||||||
margin: 2em 0 10px;
|
margin: 2em 0 10px;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
text-align: center;
|
||||||
|
width: fit-content;
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@
|
||||||
#V-SettingsPane {
|
#V-SettingsPane {
|
||||||
height: calc(100% - 50px - @rlLowMargin);
|
height: calc(100% - 50px - @rlLowMargin);
|
||||||
margin: 50px @rlLowMargin 0 0;
|
margin: 50px @rlLowMargin 0 0;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.btn-toolbar {
|
.btn-toolbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||||
import Remote from 'Remote/User/Fetch';
|
import Remote from 'Remote/User/Fetch';
|
||||||
|
|
||||||
import { ComposeAttachmentModel } from 'Model/ComposeAttachment';
|
import { ComposeAttachmentModel } from 'Model/ComposeAttachment';
|
||||||
import { EmailModel } from 'Model/Email';
|
import { EmailModel, addressparser } from 'Model/Email';
|
||||||
|
|
||||||
import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin';
|
import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin';
|
||||||
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
import { AbstractViewPopup } from 'Knoin/AbstractViews';
|
||||||
|
|
@ -59,12 +59,7 @@ const
|
||||||
|
|
||||||
base64_encode = text => btoa(unescape(encodeURIComponent(text))).match(/.{1,76}/g).join('\r\n'),
|
base64_encode = text => btoa(unescape(encodeURIComponent(text))).match(/.{1,76}/g).join('\r\n'),
|
||||||
|
|
||||||
email = new EmailModel(),
|
getEmail = value => addressparser(value)[0]?.email || false,
|
||||||
getEmail = value => {
|
|
||||||
email.clear();
|
|
||||||
email.parse(value.trim());
|
|
||||||
return email.email || false;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array} aList
|
* @param {Array} aList
|
||||||
|
|
@ -72,7 +67,7 @@ const
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
emailArrayToStringLineHelper = (aList, bFriendly) =>
|
emailArrayToStringLineHelper = (aList, bFriendly) =>
|
||||||
aList.map(item => item.toLine(bFriendly)).join(', '),
|
aList.filter(item => item.email).map(item => item.toLine(bFriendly)).join(', '),
|
||||||
|
|
||||||
reloadDraftFolder = () => {
|
reloadDraftFolder = () => {
|
||||||
const draftsFolder = FolderUserStore.draftsFolder();
|
const draftsFolder = FolderUserStore.draftsFolder();
|
||||||
|
|
@ -205,10 +200,6 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.oEditor = null;
|
this.oEditor = null;
|
||||||
this.aDraftInfo = null;
|
|
||||||
this.sInReplyTo = '';
|
|
||||||
this.bFromDraft = false;
|
|
||||||
this.sReferences = '';
|
|
||||||
|
|
||||||
this.sLastFocusedField = 'to';
|
this.sLastFocusedField = 'to';
|
||||||
|
|
||||||
|
|
@ -854,33 +845,17 @@ export class ComposePopupView extends AbstractViewPopup {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ComposeType.Draft:
|
case ComposeType.Draft:
|
||||||
this.to(emailArrayToStringLineHelper(message.to));
|
|
||||||
this.cc(emailArrayToStringLineHelper(message.cc));
|
|
||||||
this.bcc(emailArrayToStringLineHelper(message.bcc));
|
|
||||||
this.replyTo(emailArrayToStringLineHelper(message.replyTo));
|
|
||||||
|
|
||||||
this.bFromDraft = true;
|
this.bFromDraft = true;
|
||||||
|
|
||||||
this.draftsFolder(message.folder);
|
this.draftsFolder(message.folder);
|
||||||
this.draftUid(message.uid);
|
this.draftUid(message.uid);
|
||||||
|
// fallthrough
|
||||||
this.subject(sSubject);
|
|
||||||
this.prepareMessageAttachments(message, msgComposeType);
|
|
||||||
|
|
||||||
this.aDraftInfo = 3 === arrayLength(aDraftInfo) ? aDraftInfo : null;
|
|
||||||
this.sInReplyTo = message.inReplyTo;
|
|
||||||
this.sReferences = message.references;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ComposeType.EditAsNew:
|
case ComposeType.EditAsNew:
|
||||||
this.to(emailArrayToStringLineHelper(message.to));
|
this.to(emailArrayToStringLineHelper(message.to));
|
||||||
this.cc(emailArrayToStringLineHelper(message.cc));
|
this.cc(emailArrayToStringLineHelper(message.cc));
|
||||||
this.bcc(emailArrayToStringLineHelper(message.bcc));
|
this.bcc(emailArrayToStringLineHelper(message.bcc));
|
||||||
this.replyTo(emailArrayToStringLineHelper(message.replyTo));
|
this.replyTo(emailArrayToStringLineHelper(message.replyTo));
|
||||||
|
|
||||||
this.subject(sSubject);
|
this.subject(sSubject);
|
||||||
this.prepareMessageAttachments(message, msgComposeType);
|
this.prepareMessageAttachments(message, msgComposeType);
|
||||||
|
|
||||||
this.aDraftInfo = 3 === arrayLength(aDraftInfo) ? aDraftInfo : null;
|
this.aDraftInfo = 3 === arrayLength(aDraftInfo) ? aDraftInfo : null;
|
||||||
this.sInReplyTo = message.inReplyTo;
|
this.sInReplyTo = message.inReplyTo;
|
||||||
this.sReferences = message.references;
|
this.sReferences = message.references;
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ export class ContactsPopupView extends AbstractViewPopup {
|
||||||
this.contactsCheckedOrSelected().forEach(oContact => {
|
this.contactsCheckedOrSelected().forEach(oContact => {
|
||||||
const data = oContact?.getNameAndEmailHelper(),
|
const data = oContact?.getNameAndEmailHelper(),
|
||||||
email = data ? new EmailModel(data[0], data[1]) : null;
|
email = data ? new EmailModel(data[0], data[1]) : null;
|
||||||
email?.validate() && aE.push(email);
|
email?.valid() && aE.push(email);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (arrayLength(aE)) {
|
if (arrayLength(aE)) {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,9 @@ import { ComposePopupView } from 'View/Popup/Compose';
|
||||||
|
|
||||||
import { MessageModel } from 'Model/Message';
|
import { MessageModel } from 'Model/Message';
|
||||||
|
|
||||||
|
import { Layout, ClientSideKeyNameMessageListSize } from 'Common/EnumsUser';
|
||||||
|
import { setLayoutResizer } from 'Common/UtilsUser';
|
||||||
|
|
||||||
const
|
const
|
||||||
canBeMovedHelper = () => MessagelistUserStore.hasCheckedOrSelected(),
|
canBeMovedHelper = () => MessagelistUserStore.hasCheckedOrSelected(),
|
||||||
|
|
||||||
|
|
@ -159,12 +162,12 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
let list = [], current, sort = FolderUserStore.sortMode() || 'DATE';
|
let list = [], current, sort = FolderUserStore.sortMode() || 'DATE';
|
||||||
if (sort.includes('FROM')) {
|
if (sort.includes('FROM')) {
|
||||||
MessagelistUserStore.forEach(msg => {
|
MessagelistUserStore.forEach(msg => {
|
||||||
let email = msg.from?.[0].email;
|
let email = msg.from[0].email;
|
||||||
if (!current || email != current.id) {
|
if (!current || email != current.id) {
|
||||||
current = {
|
current = {
|
||||||
id: email,
|
id: email,
|
||||||
label: msg.from[0].toLine(),
|
label: msg.from[0].toLine(),
|
||||||
search: 'from=' + msg.from[0].email,
|
search: 'from=' + email,
|
||||||
messages: []
|
messages: []
|
||||||
};
|
};
|
||||||
list.push(current);
|
list.push(current);
|
||||||
|
|
@ -232,7 +235,8 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
|
|
||||||
this.selector.on('ItemSelect', message => {
|
this.selector.on('ItemSelect', message => {
|
||||||
if (message) {
|
if (message) {
|
||||||
populateMessageBody(MessageModel.fromMessageListItem(message));
|
// populateMessageBody(message.clone());
|
||||||
|
populateMessageBody(message);
|
||||||
} else {
|
} else {
|
||||||
MessageUserStore.message(null);
|
MessageUserStore.message(null);
|
||||||
}
|
}
|
||||||
|
|
@ -564,6 +568,23 @@ export class MailMessageList extends AbstractViewRight {
|
||||||
const b_content = dom.querySelector('.b-content'),
|
const b_content = dom.querySelector('.b-content'),
|
||||||
eqs = (ev, s) => ev.target.closestWithin(s, dom);
|
eqs = (ev, s) => ev.target.closestWithin(s, dom);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
// initMailboxLayoutResizer
|
||||||
|
const top = dom.querySelector('.messageList'),
|
||||||
|
fToggle = () => {
|
||||||
|
let layout = SettingsUserStore.layout();
|
||||||
|
setLayoutResizer(top, ClientSideKeyNameMessageListSize,
|
||||||
|
(ThemeStore.isMobile() || Layout.NoPreview === layout)
|
||||||
|
? 0
|
||||||
|
: (Layout.SidePreview === layout ? 'Width' : 'Height')
|
||||||
|
);
|
||||||
|
};
|
||||||
|
if (top) {
|
||||||
|
fToggle();
|
||||||
|
addEventListener('rl-layout', fToggle);
|
||||||
|
}
|
||||||
|
}, 1);
|
||||||
|
|
||||||
this.selector.init(b_content, Scope.MessageList);
|
this.selector.init(b_content, Scope.MessageList);
|
||||||
|
|
||||||
addEventsListeners(dom, {
|
addEventsListeners(dom, {
|
||||||
|
|
|
||||||
|
|
@ -112,8 +112,7 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
|
|
||||||
// viewer
|
// viewer
|
||||||
viewFromShort: '',
|
viewFromShort: '',
|
||||||
viewFromDkimData: ['none', ''],
|
dkimData: ['none', '', '']
|
||||||
viewToShort: ''
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.moveAction = moveAction;
|
this.moveAction = moveAction;
|
||||||
|
|
@ -171,10 +170,10 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
|
|
||||||
canBeRepliedOrForwarded: () => !MessagelistUserStore.isDraftFolder() && this.messageVisibility(),
|
canBeRepliedOrForwarded: () => !MessagelistUserStore.isDraftFolder() && this.messageVisibility(),
|
||||||
|
|
||||||
viewFromDkimVisibility: () => 'none' !== this.viewFromDkimData()[0],
|
viewDkimIcon: () => 'none' !== this.dkimData()[0],
|
||||||
|
|
||||||
viewFromDkimStatusIconClass:() => {
|
dkimIconClass:() => {
|
||||||
switch (this.viewFromDkimData()[0]) {
|
switch (this.dkimData()[0]) {
|
||||||
case 'none':
|
case 'none':
|
||||||
return '';
|
return '';
|
||||||
case 'pass':
|
case 'pass':
|
||||||
|
|
@ -184,13 +183,9 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
viewFromDkimStatusTitle:() => {
|
dkimTitle:() => {
|
||||||
const status = this.viewFromDkimData();
|
const dkim = this.dkimData();
|
||||||
if (arrayLength(status) && status[0]) {
|
return dkim[0] ? dkim[2] || 'DKIM: ' + dkim[0] : '';
|
||||||
return status[1] || 'DKIM: ' + status[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
},
|
},
|
||||||
|
|
||||||
showWhitelistOptions: () => 'match' === SettingsUserStore.viewImages(),
|
showWhitelistOptions: () => 'match' === SettingsUserStore.viewImages(),
|
||||||
|
|
@ -212,10 +207,7 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
this.viewHash = message.hash;
|
this.viewHash = message.hash;
|
||||||
// TODO: make first param a user setting #683
|
// TODO: make first param a user setting #683
|
||||||
this.viewFromShort(message.from.toString(false, true));
|
this.viewFromShort(message.from.toString(false, true));
|
||||||
let dkim = 1 === arrayLength(message.from) && message.dkim
|
this.dkimData(message.dkim[0] || ['none', '', '']);
|
||||||
&& message.dkim.find(dkim => message.from[0].email.includes(dkim[1]));
|
|
||||||
this.viewFromDkimData(dkim ? [dkim[0], dkim[2]] : ['none', '']);
|
|
||||||
this.viewToShort(message.to.toString(true, true));
|
|
||||||
} else {
|
} else {
|
||||||
MessagelistUserStore.selectedMessage(null);
|
MessagelistUserStore.selectedMessage(null);
|
||||||
|
|
||||||
|
|
@ -503,6 +495,11 @@ export class MailMessageView extends AbstractViewRight {
|
||||||
|
|
||||||
whitelistText(txt) {
|
whitelistText(txt) {
|
||||||
let value = (SettingsUserStore.viewImagesWhitelist().trim() + '\n' + txt).trim();
|
let value = (SettingsUserStore.viewImagesWhitelist().trim() + '\n' + txt).trim();
|
||||||
|
/*
|
||||||
|
if ('pass' === currentMessage().spf[0]?.[0]) value += '+spf';
|
||||||
|
if ('pass' === currentMessage().dkim[0]?.[0]) value += '+dkim';
|
||||||
|
if ('pass' === currentMessage().dmarc[0]?.[0]) value += '+dmarc';
|
||||||
|
*/
|
||||||
SettingsUserStore.viewImagesWhitelist(value);
|
SettingsUserStore.viewImagesWhitelist(value);
|
||||||
Remote.saveSetting('ViewImagesWhitelist', value);
|
Remote.saveSetting('ViewImagesWhitelist', value);
|
||||||
currentMessage().showExternalImages(1);
|
currentMessage().showExternalImages(1);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
This app packages SnappyMail <upstream>2.25.4</upstream>.
|
This app packages SnappyMail <upstream>2.26.1</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.25.4
|
VERSION=2.26.1
|
||||||
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 && \
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ From that point, all instance-wide SnappyMail settings can be tweaked as you wis
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## App Integrations
|
||||||
|
### Contacts
|
||||||
|
SnappyMail automatically connects with the Nextcloud contacts app. Download and install the [contacts app](https://apps.nextcloud.com/apps/contacts) for SnappyMail to obtain access to all registered users on the Nextcloud system, as well as users' personal contacts saved in here.
|
||||||
|
|
||||||
## SnappyMail Settings, Where Are They?
|
## SnappyMail Settings, Where Are They?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.25.4</version>
|
<version>2.26.1</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.**
|
||||||
|
|
|
||||||
0
integrations/nextcloud/snappymail/appinfo/routes.php
Executable file → Normal file
0
integrations/nextcloud/snappymail/appinfo/routes.php
Executable file → Normal file
0
integrations/nextcloud/snappymail/templates/admin-local.php
Executable file → Normal file
0
integrations/nextcloud/snappymail/templates/admin-local.php
Executable file → Normal file
0
integrations/nextcloud/snappymail/templates/index.php
Executable file → Normal file
0
integrations/nextcloud/snappymail/templates/index.php
Executable file → Normal file
0
integrations/nextcloud/snappymail/templates/index_embed.php
Executable file → Normal file
0
integrations/nextcloud/snappymail/templates/index_embed.php
Executable file → Normal file
0
integrations/nextcloud/snappymail/templates/personal_settings.php
Executable file → Normal file
0
integrations/nextcloud/snappymail/templates/personal_settings.php
Executable file → Normal file
|
|
@ -20,7 +20,7 @@ return "SnappyMail Webmail is a browser-based multilingual IMAP client with an a
|
||||||
# script_snappymail_versions()
|
# script_snappymail_versions()
|
||||||
sub script_snappymail_versions
|
sub script_snappymail_versions
|
||||||
{
|
{
|
||||||
return ( "2.25.4" );
|
return ( "2.26.1" );
|
||||||
}
|
}
|
||||||
|
|
||||||
sub script_snappymail_version_desc
|
sub script_snappymail_version_desc
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"title": "SnappyMail",
|
"title": "SnappyMail",
|
||||||
"description": "Simple, modern & fast web-based email client",
|
"description": "Simple, modern & fast web-based email client",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.25.4",
|
"version": "2.26.1",
|
||||||
"homepage": "https://snappymail.eu",
|
"homepage": "https://snappymail.eu",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "DJ Maze",
|
"name": "DJ Maze",
|
||||||
|
|
|
||||||
|
|
@ -133,13 +133,17 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
|
||||||
$oParams->iLimit = 999; // Is the max
|
$oParams->iLimit = 999; // Is the max
|
||||||
$oMessageList = $this->MailClient()->MessageList($oParams);
|
$oMessageList = $this->MailClient()->MessageList($oParams);
|
||||||
foreach ($oMessageList as $oMessage) {
|
foreach ($oMessageList as $oMessage) {
|
||||||
|
try {
|
||||||
if ($rCsv) {
|
if ($rCsv) {
|
||||||
$oContact = $this->MessageAsContact($oMessage);
|
$oContact = $this->MessageAsContact($oMessage);
|
||||||
\RainLoop\Providers\AddressBook\Utils::VCardToCsv($rCsv, $oContact, $bCsvHeader);
|
\RainLoop\Providers\AddressBook\Utils::VCardToCsv($rCsv, $oContact->vCard, $bCsvHeader);
|
||||||
$bCsvHeader = false;
|
$bCsvHeader = false;
|
||||||
} else if ($xCard = $this->fetchXCardFromMessage($oMessage)) {
|
} else if ($xCard = $this->fetchXCardFromMessage($oMessage)) {
|
||||||
echo $xCard->serialize();
|
echo $xCard->serialize();
|
||||||
}
|
}
|
||||||
|
} catch (\Throwable $oExc) {
|
||||||
|
$this->oLogger && $this->oLogger->WriteException($oExc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (\Throwable $oException)
|
catch (\Throwable $oException)
|
||||||
|
|
@ -156,7 +160,7 @@ class KolabAddressBook implements \RainLoop\Providers\AddressBook\AddressBookInt
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = $oContact->id;
|
$id = \intval($oContact->id);
|
||||||
$sUID = '';
|
$sUID = '';
|
||||||
|
|
||||||
$oVCard = $oContact->vCard;
|
$oVCard = $oContact->vCard;
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ class KolabPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
const
|
const
|
||||||
NAME = 'Kolab',
|
NAME = 'Kolab',
|
||||||
VERSION = '2.5',
|
VERSION = '2.6',
|
||||||
RELEASE = '2023-01-17',
|
RELEASE = '2023-02-10',
|
||||||
CATEGORY = 'Contacts',
|
CATEGORY = 'Contacts',
|
||||||
DESCRIPTION = 'Use an Address Book of Kolab.',
|
DESCRIPTION = 'Use an Address Book of Kolab.',
|
||||||
REQUIRED = '2.24.6';
|
REQUIRED = '2.26.0';
|
||||||
|
|
||||||
public function Init() : void
|
public function Init() : void
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ class LoginOverridePlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
{
|
{
|
||||||
const
|
const
|
||||||
NAME = 'Login Override',
|
NAME = 'Login Override',
|
||||||
VERSION = '2.1',
|
VERSION = '2.2',
|
||||||
RELEASE = '2023-02-01',
|
RELEASE = '2023-02-08',
|
||||||
REQUIRED = '2.25.3',
|
REQUIRED = '2.25.3',
|
||||||
CATEGORY = 'Filters',
|
CATEGORY = 'Filters',
|
||||||
DESCRIPTION = 'Override IMAP/SMTP login credentials for specific users.';
|
DESCRIPTION = 'Override IMAP/SMTP login credentials for specific users.';
|
||||||
|
|
@ -22,7 +22,7 @@ class LoginOverridePlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||||
$sMapping = \trim($this->Config()->Get('plugin', 'email_mapping', ''));
|
$sMapping = \trim($this->Config()->Get('plugin', 'email_mapping', ''));
|
||||||
if (!empty($sMapping)) {
|
if (!empty($sMapping)) {
|
||||||
$aList = \preg_split('/\\R/', $sMapping);
|
$aList = \preg_split('/\\R/', $sMapping);
|
||||||
foreach ($aList as $line) {
|
foreach ($aList as $sLine) {
|
||||||
$aData = \explode(':', $sLine, 2);
|
$aData = \explode(':', $sLine, 2);
|
||||||
if (!empty($aData[1]) && $sEmail === \trim($aData[0])) {
|
if (!empty($aData[1]) && $sEmail === \trim($aData[0])) {
|
||||||
$sEmail = \trim($aData[1]);
|
$sEmail = \trim($aData[1]);
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,7 @@ abstract class HtmlUtils
|
||||||
$oBody = $oDoc->getElementsByTagName('body')->item(0);
|
$oBody = $oDoc->getElementsByTagName('body')->item(0);
|
||||||
|
|
||||||
$aRemoveTags = array(
|
$aRemoveTags = array(
|
||||||
'svg', 'link', 'base', 'meta', 'title', 'x-script', 'script', 'bgsound', 'keygen', 'source',
|
'svg', 'link', 'base', 'meta', 'title', 'script', 'bgsound', 'keygen', 'source',
|
||||||
'object', 'embed', 'applet', 'mocha', 'iframe', 'frame', 'frameset', 'video', 'audio', 'area', 'map',
|
'object', 'embed', 'applet', 'mocha', 'iframe', 'frame', 'frameset', 'video', 'audio', 'area', 'map',
|
||||||
'head', 'style'
|
'head', 'style'
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -185,7 +185,7 @@ class Http
|
||||||
static::$bCache = true;
|
static::$bCache = true;
|
||||||
\header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
\header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
|
||||||
\header('Last-Modified: '.\gmdate('D, d M Y H:i:s').' GMT');
|
\header('Last-Modified: '.\gmdate('D, d M Y H:i:s').' GMT');
|
||||||
\header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, post-check=0, pre-check=0');
|
\header('Cache-Control: no-store');
|
||||||
\header('Pragma: no-cache');
|
\header('Pragma: no-cache');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -617,6 +617,7 @@ class ImapClient extends \MailSo\Net\NetClient
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
// literal-string
|
// literal-string
|
||||||
|
$this->hasCapability('LITERAL+')
|
||||||
if (\preg_match('/[\r\n\x00\x80-\xFF]/', $sStringForEscape)) {
|
if (\preg_match('/[\r\n\x00\x80-\xFF]/', $sStringForEscape)) {
|
||||||
return \sprintf("{%d}\r\n%s", \strlen($sStringForEscape), $sStringForEscape);
|
return \sprintf("{%d}\r\n%s", \strlen($sStringForEscape), $sStringForEscape);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,7 @@ class ResponseCollection extends \MailSo\Base\Collection
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_array($aList) && \count($aList)) {
|
if (\is_array($aList) && \count($aList)) {
|
||||||
$aList = \array_map('strtoupper', $aList);
|
return \array_map('strtoupper', $aList);
|
||||||
\sort($aList);
|
|
||||||
return $aList;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -316,8 +316,12 @@ abstract class SearchCriterias
|
||||||
|
|
||||||
public static function escapeSearchString(\MailSo\Imap\ImapClient $oImapClient, string $sSearch) : string
|
public static function escapeSearchString(\MailSo\Imap\ImapClient $oImapClient, string $sSearch) : string
|
||||||
{
|
{
|
||||||
return !\MailSo\Base\Utils::IsAscii($sSearch)
|
// https://github.com/the-djmaze/snappymail/issues/836
|
||||||
? '{'.\strlen($sSearch).'}'."\r\n".$sSearch : $oImapClient->EscapeString($sSearch);
|
// return $oImapClient->EscapeString($sSearch);
|
||||||
|
// return \MailSo\Base\Utils::IsAscii($sSearch) || $oImapClient->hasCapability('QQMail'))
|
||||||
|
return (\MailSo\Base\Utils::IsAscii($sSearch) || !$oImapClient->hasCapability('LITERAL+'))
|
||||||
|
? $oImapClient->EscapeString($sSearch)
|
||||||
|
: '{'.\strlen($sSearch).'}'."\r\n{$sSearch}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function parseSearchDate(string $sDate) : int
|
private static function parseSearchDate(string $sDate) : int
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class Email implements \JsonSerializable
|
||||||
*/
|
*/
|
||||||
function __construct(string $sEmail, string $sDisplayName = '')
|
function __construct(string $sEmail, string $sDisplayName = '')
|
||||||
{
|
{
|
||||||
if (!\strlen(\trim($sEmail))) {
|
if (!\strlen(\trim($sEmail)) && !\strlen(\trim($sDisplayName))) {
|
||||||
throw new \InvalidArgumentException;
|
throw new \InvalidArgumentException;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,12 +76,9 @@ class HeaderCollection extends \MailSo\Base\Collection
|
||||||
|
|
||||||
public function GetAsEmailCollection(string $sHeaderName, bool $bCharsetAutoDetect = false) : ?EmailCollection
|
public function GetAsEmailCollection(string $sHeaderName, bool $bCharsetAutoDetect = false) : ?EmailCollection
|
||||||
{
|
{
|
||||||
$oResult = null;
|
return new EmailCollection(
|
||||||
$sValue = $this->ValueByName($sHeaderName, $bCharsetAutoDetect);
|
$this->ValueByName($sHeaderName, $bCharsetAutoDetect)
|
||||||
if (\strlen($sValue)) {
|
);
|
||||||
$oResult = new EmailCollection($sValue);
|
|
||||||
}
|
|
||||||
return $oResult && $oResult->count() ? $oResult : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ParametersByName(string $sHeaderName) : ?ParameterCollection
|
public function ParametersByName(string $sHeaderName) : ?ParameterCollection
|
||||||
|
|
|
||||||
|
|
@ -179,16 +179,19 @@ class Message extends Part
|
||||||
|
|
||||||
public function SetInReplyTo(string $sInReplyTo) : self
|
public function SetInReplyTo(string $sInReplyTo) : self
|
||||||
{
|
{
|
||||||
|
$sInReplyTo = \trim($sInReplyTo);
|
||||||
|
if (\strlen($sInReplyTo)) {
|
||||||
$this->aHeadersValue[Enumerations\Header::IN_REPLY_TO] = $sInReplyTo;
|
$this->aHeadersValue[Enumerations\Header::IN_REPLY_TO] = $sInReplyTo;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function SetReferences(string $sReferences) : self
|
public function SetReferences(string $sReferences) : self
|
||||||
{
|
{
|
||||||
$this->aHeadersValue[Enumerations\Header::REFERENCES] =
|
$sReferences = \MailSo\Base\Utils::StripSpaces($sReferences);
|
||||||
\MailSo\Base\Utils::StripSpaces($sReferences);
|
if (\strlen($sReferences)) {
|
||||||
|
$this->aHeadersValue[Enumerations\Header::REFERENCES] = $sReferences;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,8 +242,9 @@ class Message extends Part
|
||||||
|
|
||||||
public function SetTo(EmailCollection $oEmails) : self
|
public function SetTo(EmailCollection $oEmails) : self
|
||||||
{
|
{
|
||||||
|
if ($oEmails->count()) {
|
||||||
$this->aHeadersValue[Enumerations\Header::TO_] = $oEmails;
|
$this->aHeadersValue[Enumerations\Header::TO_] = $oEmails;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -253,22 +257,25 @@ class Message extends Part
|
||||||
|
|
||||||
public function SetReplyTo(EmailCollection $oEmails) : self
|
public function SetReplyTo(EmailCollection $oEmails) : self
|
||||||
{
|
{
|
||||||
|
if ($oEmails->count()) {
|
||||||
$this->aHeadersValue[Enumerations\Header::REPLY_TO] = $oEmails;
|
$this->aHeadersValue[Enumerations\Header::REPLY_TO] = $oEmails;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function SetCc(EmailCollection $oEmails) : self
|
public function SetCc(EmailCollection $oEmails) : self
|
||||||
{
|
{
|
||||||
|
if ($oEmails->count()) {
|
||||||
$this->aHeadersValue[Enumerations\Header::CC] = $oEmails;
|
$this->aHeadersValue[Enumerations\Header::CC] = $oEmails;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function SetBcc(EmailCollection $oEmails) : self
|
public function SetBcc(EmailCollection $oEmails) : self
|
||||||
{
|
{
|
||||||
|
if ($oEmails->count()) {
|
||||||
$this->aHeadersValue[Enumerations\Header::BCC] = $oEmails;
|
$this->aHeadersValue[Enumerations\Header::BCC] = $oEmails;
|
||||||
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,8 @@ abstract class NetClient
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->IsConnected()) {
|
if ($this->IsConnected()) {
|
||||||
$this->writeLogException(new Exceptions\SocketAlreadyConnectedException, \LOG_ERR);
|
$this->writeLogException(new Exceptions\SocketAlreadyConnectedException, \LOG_ERR, false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->Settings = $oSettings;
|
$this->Settings = $oSettings;
|
||||||
|
|
@ -214,14 +215,6 @@ abstract class NetClient
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws \MailSo\Net\Exceptions\SocketConnectionDoesNotAvailableException
|
|
||||||
*/
|
|
||||||
public function IsConnectedWithException() : void
|
|
||||||
{
|
|
||||||
$this->IsConnected(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function StreamContextParams() : array
|
public function StreamContextParams() : array
|
||||||
{
|
{
|
||||||
return $this->rConnect && \MailSo\Base\Utils::FunctionCallable('stream_context_get_options')
|
return $this->rConnect && \MailSo\Base\Utils::FunctionCallable('stream_context_get_options')
|
||||||
|
|
|
||||||
|
|
@ -689,6 +689,7 @@ class Actions
|
||||||
'ViewImages' => 'ask',
|
'ViewImages' => 'ask',
|
||||||
'ViewImagesWhitelist' => '',
|
'ViewImagesWhitelist' => '',
|
||||||
'RemoveColors' => (bool) $oConfig->Get('defaults', 'remove_colors', false),
|
'RemoveColors' => (bool) $oConfig->Get('defaults', 'remove_colors', false),
|
||||||
|
'AllowStyles' => false,
|
||||||
'ListInlineAttachments' => false,
|
'ListInlineAttachments' => false,
|
||||||
'CollapseBlockquotes' => true,
|
'CollapseBlockquotes' => true,
|
||||||
'MaxBlockquotesLevel' => 0,
|
'MaxBlockquotesLevel' => 0,
|
||||||
|
|
@ -801,6 +802,7 @@ class Actions
|
||||||
$aResult['ViewImages'] = $oSettings->GetConf('ViewImages', $show_images ? 'always' : 'ask');
|
$aResult['ViewImages'] = $oSettings->GetConf('ViewImages', $show_images ? 'always' : 'ask');
|
||||||
$aResult['ViewImagesWhitelist'] = $oSettings->GetConf('ViewImagesWhitelist', '');
|
$aResult['ViewImagesWhitelist'] = $oSettings->GetConf('ViewImagesWhitelist', '');
|
||||||
$aResult['RemoveColors'] = (bool)$oSettings->GetConf('RemoveColors', $aResult['RemoveColors']);
|
$aResult['RemoveColors'] = (bool)$oSettings->GetConf('RemoveColors', $aResult['RemoveColors']);
|
||||||
|
$aResult['AllowStyles'] = (bool)$oSettings->GetConf('AllowStyles', $aResult['AllowStyles']);
|
||||||
$aResult['ListInlineAttachments'] = (bool)$oSettings->GetConf('ListInlineAttachments', $aResult['ListInlineAttachments']);
|
$aResult['ListInlineAttachments'] = (bool)$oSettings->GetConf('ListInlineAttachments', $aResult['ListInlineAttachments']);
|
||||||
$aResult['CollapseBlockquotes'] = (bool)$oSettings->GetConf('CollapseBlockquotes', $aResult['CollapseBlockquotes']);
|
$aResult['CollapseBlockquotes'] = (bool)$oSettings->GetConf('CollapseBlockquotes', $aResult['CollapseBlockquotes']);
|
||||||
$aResult['MaxBlockquotesLevel'] = (int)$oSettings->GetConf('MaxBlockquotesLevel', $aResult['MaxBlockquotesLevel']);
|
$aResult['MaxBlockquotesLevel'] = (int)$oSettings->GetConf('MaxBlockquotesLevel', $aResult['MaxBlockquotesLevel']);
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,8 @@ trait Contacts
|
||||||
\ini_set('auto_detect_line_endings', '1');
|
\ini_set('auto_detect_line_endings', '1');
|
||||||
$mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName);
|
$mData = $this->FilesProvider()->GetFile($oAccount, $sSavedName);
|
||||||
if ($mData) {
|
if ($mData) {
|
||||||
$sFileStart = \fread($mData, 20);
|
$sFileStart = \fread($mData, 128);
|
||||||
\rewind($mData);
|
\rewind($mData);
|
||||||
|
|
||||||
if (false !== $sFileStart) {
|
if (false !== $sFileStart) {
|
||||||
$sFileStart = \trim($sFileStart);
|
$sFileStart = \trim($sFileStart);
|
||||||
if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) {
|
if (false !== \strpos($sFileStart, 'BEGIN:VCARD')) {
|
||||||
|
|
@ -255,64 +254,45 @@ trait Contacts
|
||||||
private function importContactsFromVcfFile(\RainLoop\Model\Account $oAccount, /*resource*/ $rFile): int
|
private function importContactsFromVcfFile(\RainLoop\Model\Account $oAccount, /*resource*/ $rFile): int
|
||||||
{
|
{
|
||||||
$iCount = 0;
|
$iCount = 0;
|
||||||
if ($oAccount && \is_resource($rFile)) {
|
|
||||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||||
if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) {
|
if (\is_resource($rFile) && $oAddressBookProvider && $oAddressBookProvider->IsActive()) {
|
||||||
$sFile = \stream_get_contents($rFile);
|
try
|
||||||
if (\is_resource($rFile)) {
|
{
|
||||||
\fclose($rFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_string($sFile) && 5 < \strlen($sFile)) {
|
|
||||||
$this->Logger()->Write('Import contacts from vcf');
|
$this->Logger()->Write('Import contacts from vcf');
|
||||||
$iCount = $oAddressBookProvider->ImportVcfFile($sFile);
|
foreach (\RainLoop\Providers\AddressBook\Utils::VcfStreamToContacts($rFile) as $oContact) {
|
||||||
|
if ($oAddressBookProvider->ContactSave($oContact)) {
|
||||||
|
++$iCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (\Throwable $oExc)
|
||||||
|
{
|
||||||
|
$this->Logger()->WriteException($oExc);
|
||||||
|
}
|
||||||
|
}
|
||||||
return $iCount;
|
return $iCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function importContactsFromCsvFile(\RainLoop\Model\Account $oAccount, /*resource*/ $rFile, string $sFileStart): int
|
private function importContactsFromCsvFile(\RainLoop\Model\Account $oAccount, /*resource*/ $rFile, string $sFileStart): int
|
||||||
{
|
{
|
||||||
$iCount = 0;
|
$iCount = 0;
|
||||||
$aHeaders = null;
|
|
||||||
$aData = array();
|
|
||||||
|
|
||||||
if ($oAccount && \is_resource($rFile)) {
|
|
||||||
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
$oAddressBookProvider = $this->AddressBookProvider($oAccount);
|
||||||
if ($oAddressBookProvider && $oAddressBookProvider->IsActive()) {
|
if (\is_resource($rFile) && $oAddressBookProvider && $oAddressBookProvider->IsActive()) {
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$this->Logger()->Write('Import contacts from csv');
|
||||||
$sDelimiter = ((int)\strpos($sFileStart, ',') > (int)\strpos($sFileStart, ';')) ? ',' : ';';
|
$sDelimiter = ((int)\strpos($sFileStart, ',') > (int)\strpos($sFileStart, ';')) ? ',' : ';';
|
||||||
|
foreach (\RainLoop\Providers\AddressBook\Utils::CsvStreamToContacts($rFile, $sDelimiter) as $oContact) {
|
||||||
\setlocale(LC_CTYPE, 'en_US.UTF-8');
|
if ($oAddressBookProvider->ContactSave($oContact)) {
|
||||||
while (false !== ($mRow = \fgetcsv($rFile, 5000, $sDelimiter, '"'))) {
|
++$iCount;
|
||||||
if (null === $aHeaders) {
|
|
||||||
if (3 >= \count($mRow)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aHeaders = $mRow;
|
|
||||||
|
|
||||||
foreach ($aHeaders as $iIndex => $sHeaderValue) {
|
|
||||||
$aHeaders[$iIndex] = \MailSo\Base\Utils::Utf8Clear($sHeaderValue);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$aNewItem = array();
|
|
||||||
foreach ($aHeaders as $iIndex => $sHeaderValue) {
|
|
||||||
$aNewItem[$sHeaderValue] = isset($mRow[$iIndex]) ? $mRow[$iIndex] : '';
|
|
||||||
}
|
|
||||||
|
|
||||||
$aData[] = $aNewItem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (\count($aData)) {
|
|
||||||
$this->oLogger->Write('Import contacts from csv');
|
|
||||||
$iCount = $oAddressBookProvider->ImportCsvArray($aData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (\Throwable $oExc)
|
||||||
|
{
|
||||||
|
$this->Logger()->WriteException($oExc);
|
||||||
|
}
|
||||||
|
}
|
||||||
return $iCount;
|
return $iCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -989,10 +989,7 @@ trait Messages
|
||||||
$oFrom = $oMessage->GetFrom();
|
$oFrom = $oMessage->GetFrom();
|
||||||
$oMessage->RegenerateMessageId($oFrom ? $oFrom->GetDomain() : '');
|
$oMessage->RegenerateMessageId($oFrom ? $oFrom->GetDomain() : '');
|
||||||
|
|
||||||
$oReplyTo = new \MailSo\Mime\EmailCollection($this->GetActionParam('replyTo', ''));
|
$oMessage->SetReplyTo(new \MailSo\Mime\EmailCollection($this->GetActionParam('replyTo', '')));
|
||||||
if ($oReplyTo->count()) {
|
|
||||||
$oMessage->SetReplyTo($oReplyTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!empty($this->GetActionParam('readReceiptRequest', 0))) {
|
if (!empty($this->GetActionParam('readReceiptRequest', 0))) {
|
||||||
// Read Receipts Reference Main Account Email, Not Identities #147
|
// Read Receipts Reference Main Account Email, Not Identities #147
|
||||||
|
|
@ -1010,35 +1007,17 @@ trait Messages
|
||||||
|
|
||||||
$oMessage->SetSubject($this->GetActionParam('subject', ''));
|
$oMessage->SetSubject($this->GetActionParam('subject', ''));
|
||||||
|
|
||||||
$oToEmails = new \MailSo\Mime\EmailCollection($this->GetActionParam('to', ''));
|
$oMessage->SetTo(new \MailSo\Mime\EmailCollection($this->GetActionParam('to', '')));
|
||||||
if ($oToEmails->count()) {
|
$oMessage->SetCc(new \MailSo\Mime\EmailCollection($this->GetActionParam('cc', '')));
|
||||||
$oMessage->SetTo($oToEmails);
|
$oMessage->SetBcc(new \MailSo\Mime\EmailCollection($this->GetActionParam('bcc', '')));
|
||||||
}
|
|
||||||
|
|
||||||
$oCcEmails = new \MailSo\Mime\EmailCollection($this->GetActionParam('cc', ''));
|
|
||||||
if ($oCcEmails->count()) {
|
|
||||||
$oMessage->SetCc($oCcEmails);
|
|
||||||
}
|
|
||||||
|
|
||||||
$oBccEmails = new \MailSo\Mime\EmailCollection($this->GetActionParam('bcc', ''));
|
|
||||||
if ($oBccEmails->count()) {
|
|
||||||
$oMessage->SetBcc($oBccEmails);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aDraftInfo = $this->GetActionParam('draftInfo', null);
|
$aDraftInfo = $this->GetActionParam('draftInfo', null);
|
||||||
if ($bWithDraftInfo && \is_array($aDraftInfo) && !empty($aDraftInfo[0]) && !empty($aDraftInfo[1]) && !empty($aDraftInfo[2])) {
|
if ($bWithDraftInfo && \is_array($aDraftInfo) && !empty($aDraftInfo[0]) && !empty($aDraftInfo[1]) && !empty($aDraftInfo[2])) {
|
||||||
$oMessage->SetDraftInfo($aDraftInfo[0], $aDraftInfo[1], $aDraftInfo[2]);
|
$oMessage->SetDraftInfo($aDraftInfo[0], $aDraftInfo[1], $aDraftInfo[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$sInReplyTo = $this->GetActionParam('inReplyTo', '');
|
$oMessage->SetInReplyTo($this->GetActionParam('inReplyTo', ''));
|
||||||
if (\strlen($sInReplyTo)) {
|
$oMessage->SetReferences($this->GetActionParam('references', ''));
|
||||||
$oMessage->SetInReplyTo($sInReplyTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
$sReferences = $this->GetActionParam('references', '');
|
|
||||||
if (\strlen($sReferences)) {
|
|
||||||
$oMessage->SetReferences($sReferences);
|
|
||||||
}
|
|
||||||
|
|
||||||
$aFoundCids = array();
|
$aFoundCids = array();
|
||||||
$aFoundDataURL = array();
|
$aFoundDataURL = array();
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ trait Themes
|
||||||
&& ($oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount))) {
|
&& ($oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount))) {
|
||||||
$sTheme = (string) $oSettingsLocal->GetConf('Theme', $sTheme);
|
$sTheme = (string) $oSettingsLocal->GetConf('Theme', $sTheme);
|
||||||
}
|
}
|
||||||
$sTheme = $this->ValidateTheme($sTheme) ?: 'Default';
|
$sTheme = $this->ValidateTheme($sTheme);
|
||||||
}
|
}
|
||||||
return $sTheme;
|
return $sTheme;
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +92,13 @@ trait Themes
|
||||||
|
|
||||||
public function ValidateTheme(string $sTheme): string
|
public function ValidateTheme(string $sTheme): string
|
||||||
{
|
{
|
||||||
return \in_array($sTheme, $this->GetThemes()) ? $sTheme : $this->Config()->Get('themes', 'default', 'Default');
|
if (!\in_array($sTheme, $this->GetThemes())) {
|
||||||
|
$sTheme = $this->Config()->Get('webmail', 'theme', 'Default');
|
||||||
|
if (!\in_array($sTheme, $this->GetThemes())) {
|
||||||
|
$sTheme = 'Default';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $sTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function compileCss(string $sTheme, bool $bAdmin, bool $bMinified = false) : string
|
public function compileCss(string $sTheme, bool $bAdmin, bool $bMinified = false) : string
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,7 @@ trait User
|
||||||
$this->setSettingsFromParams($oSettings, 'ViewImages', 'string');
|
$this->setSettingsFromParams($oSettings, 'ViewImages', 'string');
|
||||||
$this->setSettingsFromParams($oSettings, 'ViewImagesWhitelist', 'string');
|
$this->setSettingsFromParams($oSettings, 'ViewImagesWhitelist', 'string');
|
||||||
$this->setSettingsFromParams($oSettings, 'RemoveColors', 'bool');
|
$this->setSettingsFromParams($oSettings, 'RemoveColors', 'bool');
|
||||||
|
$this->setSettingsFromParams($oSettings, 'AllowStyles', 'bool');
|
||||||
$this->setSettingsFromParams($oSettings, 'ListInlineAttachments', 'bool');
|
$this->setSettingsFromParams($oSettings, 'ListInlineAttachments', 'bool');
|
||||||
$this->setSettingsFromParams($oSettings, 'CollapseBlockquotes', 'bool');
|
$this->setSettingsFromParams($oSettings, 'CollapseBlockquotes', 'bool');
|
||||||
$this->setSettingsFromParams($oSettings, 'MaxBlockquotesLevel', 'int');
|
$this->setSettingsFromParams($oSettings, 'MaxBlockquotesLevel', 'int');
|
||||||
|
|
|
||||||
|
|
@ -6,50 +6,24 @@ use MailSo\Base\Utils;
|
||||||
|
|
||||||
class Identity implements \JsonSerializable
|
class Identity implements \JsonSerializable
|
||||||
{
|
{
|
||||||
/**
|
private string $sId;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $sId;
|
|
||||||
|
|
||||||
/**
|
private string $sEmail;
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $sEmail;
|
|
||||||
|
|
||||||
/**
|
private string $sName = '';
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $sName;
|
|
||||||
|
|
||||||
/**
|
private string $sReplyTo = '';
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $sReplyTo;
|
|
||||||
|
|
||||||
/**
|
private string $sBcc = '';
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $sBcc;
|
|
||||||
|
|
||||||
/**
|
private string $sSignature = '';
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
private $sSignature;
|
|
||||||
|
|
||||||
/**
|
private bool $bSignatureInsertBefore = false;
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
private $bSignatureInsertBefore;
|
|
||||||
|
|
||||||
function __construct(string $sId = '', string $sEmail = '')
|
function __construct(string $sId = '', string $sEmail = '')
|
||||||
{
|
{
|
||||||
$this->sId = $sId;
|
$this->sId = $sId;
|
||||||
$this->sEmail = $sEmail;
|
$this->sEmail = $sEmail;
|
||||||
$this->sName = '';
|
|
||||||
$this->sReplyTo = '';
|
|
||||||
$this->sBcc = '';
|
|
||||||
$this->sSignature = '';
|
|
||||||
$this->bSignatureInsertBefore = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Id(bool $bFillOnEmpty = false): string
|
public function Id(bool $bFillOnEmpty = false): string
|
||||||
|
|
@ -84,16 +58,6 @@ class Identity implements \JsonSerializable
|
||||||
return $this->sBcc;
|
return $this->sBcc;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Signature(): string
|
|
||||||
{
|
|
||||||
return $this->sSignature;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function SignatureInsertBefore(): bool
|
|
||||||
{
|
|
||||||
return $this->bSignatureInsertBefore;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function SetId(string $sId): Identity
|
public function SetId(string $sId): Identity
|
||||||
{
|
{
|
||||||
$this->sId = $sId;
|
$this->sId = $sId;
|
||||||
|
|
@ -151,13 +115,13 @@ class Identity implements \JsonSerializable
|
||||||
public function ToSimpleJSON(): array
|
public function ToSimpleJSON(): array
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
'Id' => $this->Id(),
|
'Id' => $this->sId,
|
||||||
'Email' => $this->Email(),
|
'Email' => $this->sEmail,
|
||||||
'Name' => $this->Name(),
|
'Name' => $this->sName,
|
||||||
'ReplyTo' => $this->ReplyTo(),
|
'ReplyTo' => $this->sReplyTo,
|
||||||
'Bcc' => $this->Bcc(),
|
'Bcc' => $this->sBcc,
|
||||||
'Signature' => $this->Signature(),
|
'Signature' => $this->sSignature,
|
||||||
'SignatureInsertBefore' => $this->SignatureInsertBefore()
|
'SignatureInsertBefore' => $this->bSignatureInsertBefore
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,13 +129,13 @@ class Identity implements \JsonSerializable
|
||||||
public function jsonSerialize()
|
public function jsonSerialize()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
'Id' => $this->Id(),
|
'Id' => $this->sId,
|
||||||
'Email' => Utils::IdnToUtf8($this->Email()),
|
'Email' => Utils::IdnToUtf8($this->sEmail),
|
||||||
'Name' => $this->Name(),
|
'Name' => $this->sName,
|
||||||
'ReplyTo' => $this->ReplyTo(),
|
'ReplyTo' => $this->sReplyTo,
|
||||||
'Bcc' => $this->Bcc(),
|
'Bcc' => $this->sBcc,
|
||||||
'Signature' => $this->Signature(),
|
'Signature' => $this->sSignature,
|
||||||
'SignatureInsertBefore' => $this->SignatureInsertBefore()
|
'SignatureInsertBefore' => $this->bSignatureInsertBefore
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,37 +82,4 @@ class AddressBook extends AbstractProvider
|
||||||
{
|
{
|
||||||
return $this->IsActive() ? $this->oDriver->IncFrec($aEmails, $bCreateAuto) : false;
|
return $this->IsActive() ? $this->oDriver->IncFrec($aEmails, $bCreateAuto) : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function ImportCsvArray(array $aCsvData) : int
|
|
||||||
{
|
|
||||||
$iCount = 0;
|
|
||||||
if ($this->IsActive()) {
|
|
||||||
foreach (AddressBook\Utils::CsvArrayToContacts($aCsvData) as $oContact) {
|
|
||||||
if ($this->ContactSave($oContact)) {
|
|
||||||
++$iCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $iCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function ImportVcfFile(string $sVcfData) : int
|
|
||||||
{
|
|
||||||
$iCount = 0;
|
|
||||||
if ($this->IsActive()) {
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (AddressBook\Utils::VcfFileToContacts($sVcfData) as $oContact) {
|
|
||||||
if ($this->ContactSave($oContact)) {
|
|
||||||
++$iCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (\Throwable $oExc)
|
|
||||||
{
|
|
||||||
$this->Logger()->WriteException($oExc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $iCount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,17 @@ class PdoAddressBook
|
||||||
$sDsn = 'sqlite:' . APP_PRIVATE_DATA . 'AddressBook.sqlite';
|
$sDsn = 'sqlite:' . APP_PRIVATE_DATA . 'AddressBook.sqlite';
|
||||||
/*
|
/*
|
||||||
// TODO: use local db?
|
// TODO: use local db?
|
||||||
|
$oAccount = \RainLoop\Api::Actions()->getMainAccountFromToken(false);
|
||||||
|
if ($oAccount) {
|
||||||
$homedir = \RainLoop\Api::Actions()->StorageProvider()->GenerateFilePath(
|
$homedir = \RainLoop\Api::Actions()->StorageProvider()->GenerateFilePath(
|
||||||
$oAccount,
|
$oAccount,
|
||||||
\RainLoop\Providers\Storage\Enumerations\StorageType::ROOT
|
\RainLoop\Providers\Storage\Enumerations\StorageType::ROOT
|
||||||
);
|
);
|
||||||
$sDsn = 'sqlite:' . $homedir . '/AddressBook.sqlite';
|
if (!\is_file($homedir . 'AddressBook.sqlite') && \is_file(APP_PRIVATE_DATA . '/AddressBook.sqlite')) {
|
||||||
|
\copy(APP_PRIVATE_DATA . '/AddressBook.sqlite', $homedir . 'AddressBook.sqlite');
|
||||||
|
}
|
||||||
|
$sDsn = 'sqlite:' . $homedir . 'AddressBook.sqlite';
|
||||||
|
}
|
||||||
*/
|
*/
|
||||||
} else {
|
} else {
|
||||||
$sDsn = \trim($oConfig->Get('contacts', 'pdo_dsn', ''));
|
$sDsn = \trim($oConfig->Get('contacts', 'pdo_dsn', ''));
|
||||||
|
|
@ -101,9 +107,12 @@ class PdoAddressBook
|
||||||
private function prepareDatabaseSyncData() : array
|
private function prepareDatabaseSyncData() : array
|
||||||
{
|
{
|
||||||
$aResult = array();
|
$aResult = array();
|
||||||
$oStmt = $this->prepareAndExecute('SELECT id_contact, id_contact_str, changed, deleted, etag FROM rainloop_ab_contacts WHERE id_user = :id_user', array(
|
$oStmt = $this->prepareAndExecute('SELECT id_contact, id_contact_str, changed, deleted, etag
|
||||||
':id_user' => array($this->iUserID, \PDO::PARAM_INT)
|
FROM rainloop_ab_contacts
|
||||||
));
|
WHERE id_user = :id_user
|
||||||
|
ORDER BY deleted DESC',
|
||||||
|
array(':id_user' => array($this->iUserID, \PDO::PARAM_INT))
|
||||||
|
);
|
||||||
|
|
||||||
if ($oStmt) {
|
if ($oStmt) {
|
||||||
$aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
|
$aFetch = $oStmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||||
|
|
@ -308,6 +317,7 @@ class PdoAddressBook
|
||||||
public function Export(string $sType = 'vcf') : bool
|
public function Export(string $sType = 'vcf') : bool
|
||||||
{
|
{
|
||||||
if (1 > $this->iUserID) {
|
if (1 > $this->iUserID) {
|
||||||
|
\SnappyMail\Log::warning('PdoAddressBook', 'Export() invalid $iUserID');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -317,17 +327,22 @@ class PdoAddressBook
|
||||||
$aDatabaseSyncData = $this->prepareDatabaseSyncData();
|
$aDatabaseSyncData = $this->prepareDatabaseSyncData();
|
||||||
if (\count($aDatabaseSyncData)) {
|
if (\count($aDatabaseSyncData)) {
|
||||||
foreach ($aDatabaseSyncData as $mData) {
|
foreach ($aDatabaseSyncData as $mData) {
|
||||||
if ($mData && isset($mData['id_contact'], $mData['deleted']) && !$mData['deleted']) {
|
try {
|
||||||
|
// if ($mData && isset($mData['id_contact'], $mData['deleted']) && !$mData['deleted']) {
|
||||||
|
if ($mData && !empty($mData['id_contact'])) {
|
||||||
$oContact = $this->GetContactByID($mData['id_contact']);
|
$oContact = $this->GetContactByID($mData['id_contact']);
|
||||||
if ($oContact) {
|
if ($oContact) {
|
||||||
if ($rCsv) {
|
if ($rCsv) {
|
||||||
Utils::VCardToCsv($rCsv, $oContact, $bCsvHeader);
|
Utils::VCardToCsv($rCsv, $oContact->vCard, $bCsvHeader);
|
||||||
$bCsvHeader = false;
|
$bCsvHeader = false;
|
||||||
} else {
|
} else {
|
||||||
echo $oContact->vCard->serialize();
|
echo $oContact->vCard->serialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (\Throwable $oExc) {
|
||||||
|
$this->oLogger && $this->oLogger->WriteException($oExc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -337,6 +352,7 @@ class PdoAddressBook
|
||||||
public function ContactSave(Contact $oContact) : bool
|
public function ContactSave(Contact $oContact) : bool
|
||||||
{
|
{
|
||||||
if (1 > $this->iUserID) {
|
if (1 > $this->iUserID) {
|
||||||
|
\SnappyMail\Log::warning('PdoAddressBook', 'ContactSave() invalid $iUserID');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -436,6 +452,7 @@ class PdoAddressBook
|
||||||
public function DeleteContacts(array $aContactIds) : bool
|
public function DeleteContacts(array $aContactIds) : bool
|
||||||
{
|
{
|
||||||
if (1 > $this->iUserID) {
|
if (1 > $this->iUserID) {
|
||||||
|
\SnappyMail\Log::warning('PdoAddressBook', 'DeleteContacts() invalid $iUserID');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -744,7 +761,7 @@ class PdoAddressBook
|
||||||
p.prop_value as jcard
|
p.prop_value as jcard
|
||||||
FROM rainloop_ab_contacts AS c
|
FROM rainloop_ab_contacts AS c
|
||||||
LEFT JOIN rainloop_ab_properties AS p ON (p.id_contact = c.id_contact AND p.prop_type = :prop_type)
|
LEFT JOIN rainloop_ab_properties AS p ON (p.id_contact = c.id_contact AND p.prop_type = :prop_type)
|
||||||
WHERE c.deleted = 0 AND c.id_user = :id_user';
|
WHERE c.id_user = :id_user AND c.deleted = 0';
|
||||||
|
|
||||||
$aParams = array(
|
$aParams = array(
|
||||||
':id_user' => array($this->iUserID, \PDO::PARAM_INT),
|
':id_user' => array($this->iUserID, \PDO::PARAM_INT),
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace RainLoop\Providers\AddressBook;
|
namespace RainLoop\Providers\AddressBook;
|
||||||
|
|
||||||
|
use Sabre\VObject\Component\VCard;
|
||||||
|
|
||||||
class Utils
|
class Utils
|
||||||
{
|
{
|
||||||
private static $aMap = array(
|
private static $aMap = array(
|
||||||
|
|
@ -22,20 +24,29 @@ class Utils
|
||||||
'namesuffix' => 4,
|
'namesuffix' => 4,
|
||||||
'shortname' => 'NICKNAME',
|
'shortname' => 'NICKNAME',
|
||||||
'nickname' => 'NICKNAME',
|
'nickname' => 'NICKNAME',
|
||||||
|
'birthday' => 'BDAY',
|
||||||
|
|
||||||
|
'mobile' => array('TEL', 'CELL'),
|
||||||
|
'mobilephone' => array('TEL', 'CELL'),
|
||||||
|
|
||||||
|
'businessemail' => array('EMAIL', 'WORK'),
|
||||||
|
'businessemail2' => array('EMAIL', 'WORK'),
|
||||||
|
'businessemail3' => array('EMAIL', 'WORK'),
|
||||||
'businessphone' => array('TEL', 'WORK'),
|
'businessphone' => array('TEL', 'WORK'),
|
||||||
'businessphone2' => array('TEL', 'WORK'),
|
'businessphone2' => array('TEL', 'WORK'),
|
||||||
'businessphone3' => array('TEL', 'WORK'),
|
'businessphone3' => array('TEL', 'WORK'),
|
||||||
|
'businessmobile' => array('TEL', 'WORK,CELL'),
|
||||||
|
'businessmobilephone' => array('TEL', 'WORK,CELL'),
|
||||||
|
'businessweb' => array('URL', 'WORK'),
|
||||||
|
'businesswebpage' => array('URL', 'WORK'),
|
||||||
|
'businesswebsite' => array('URL', 'WORK'),
|
||||||
'companyphone' => array('TEL', 'WORK'),
|
'companyphone' => array('TEL', 'WORK'),
|
||||||
'companymainphone' => array('TEL', 'WORK'),
|
'companymainphone' => array('TEL', 'WORK'),
|
||||||
|
|
||||||
|
'primaryphone' => array('TEL', 'PREF,HOME'),
|
||||||
'homephone' => array('TEL', 'HOME'),
|
'homephone' => array('TEL', 'HOME'),
|
||||||
'homephone2' => array('TEL', 'HOME'),
|
'homephone2' => array('TEL', 'HOME'),
|
||||||
'homephone3' => array('TEL', 'HOME'),
|
'homephone3' => array('TEL', 'HOME'),
|
||||||
'mobile' => array('TEL', 'CELL'),
|
|
||||||
'mobilephone' => array('TEL', 'CELL'),
|
|
||||||
'businessmobile' => array('TEL', 'WORK,CELL'),
|
|
||||||
'businessmobilephone' => array('TEL', 'WORK,CELL'),
|
|
||||||
'otherphone' => 'TEL',
|
|
||||||
'primaryphone' => array('TEL', 'PREF,HOME'),
|
|
||||||
'email' => array('EMAIL', 'HOME'),
|
'email' => array('EMAIL', 'HOME'),
|
||||||
'email2' => array('EMAIL', 'HOME'),
|
'email2' => array('EMAIL', 'HOME'),
|
||||||
'email3' => array('EMAIL', 'HOME'),
|
'email3' => array('EMAIL', 'HOME'),
|
||||||
|
|
@ -48,48 +59,69 @@ class Utils
|
||||||
'emailaddress' => array('EMAIL', 'HOME'),
|
'emailaddress' => array('EMAIL', 'HOME'),
|
||||||
'email2address' => array('EMAIL', 'HOME'),
|
'email2address' => array('EMAIL', 'HOME'),
|
||||||
'email3address' => array('EMAIL', 'HOME'),
|
'email3address' => array('EMAIL', 'HOME'),
|
||||||
'otheremail' => 'EMAIL',
|
|
||||||
'businessemail' => array('EMAIL', 'WORK'),
|
|
||||||
'businessemail2' => array('EMAIL', 'WORK'),
|
|
||||||
'businessemail3' => array('EMAIL', 'WORK'),
|
|
||||||
'personalemail' => array('EMAIL', 'HOME'),
|
'personalemail' => array('EMAIL', 'HOME'),
|
||||||
'personalemail2' => array('EMAIL', 'HOME'),
|
'personalemail2' => array('EMAIL', 'HOME'),
|
||||||
'personalemail3' => array('EMAIL', 'HOME'),
|
'personalemail3' => array('EMAIL', 'HOME'),
|
||||||
|
'personalwebsite' => array('URL', 'HOME'),
|
||||||
|
|
||||||
|
'otheremail' => 'EMAIL',
|
||||||
|
'otherphone' => 'TEL',
|
||||||
'notes' => 'NOTE',
|
'notes' => 'NOTE',
|
||||||
'web' => 'URL',
|
'web' => 'URL',
|
||||||
'businessweb' => array('URL', 'WORK'),
|
|
||||||
'webpage' => 'URL',
|
'webpage' => 'URL',
|
||||||
'businesswebpage' => array('URL', 'WORK'),
|
'website' => 'URL'
|
||||||
'website' => 'URL',
|
/*
|
||||||
'businesswebsite' => array('URL', 'WORK'),
|
TODO:
|
||||||
'personalwebsite' => 'URL',
|
'company' => '',
|
||||||
'birthday' => 'BDAY'
|
'department' => '',
|
||||||
|
'jobtitle' => '',
|
||||||
|
'officelocation' => '',
|
||||||
|
'homestreet' => '',
|
||||||
|
'homecity' => '',
|
||||||
|
'homestate' => '',
|
||||||
|
'homepostalcode' => '',
|
||||||
|
'homecountry' => '',
|
||||||
|
'businessstreet' => '',
|
||||||
|
'businesscity' => '',
|
||||||
|
'businessstate' => '',
|
||||||
|
'businesspostalcode' => '',
|
||||||
|
'businesscountry' => '',
|
||||||
|
*/
|
||||||
);
|
);
|
||||||
|
|
||||||
public static function CsvArrayToContacts(array $aCsvData) : iterable
|
public static function CsvStreamToContacts(/*resource*/ $rFile, string $sDelimiter) : iterable
|
||||||
{
|
{
|
||||||
foreach ($aCsvData as $aItem) {
|
\setlocale(LC_CTYPE, 'en_US.UTF-8');
|
||||||
|
|
||||||
|
$aHeaders = \fgetcsv($rFile, 5000, $sDelimiter, '"');
|
||||||
|
if (!$aHeaders || 3 >= \count($aHeaders)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach ($aHeaders as $iIndex => $sItemName) {
|
||||||
|
$sItemName = \MailSo\Base\Utils::Utf8Clear($sItemName);
|
||||||
|
$sItemName = \strtoupper(\trim(\preg_replace('/[\s\-]+/', '', $sItemName)));
|
||||||
|
if (!\array_key_exists($sItemName, VCard::$propertyMap)) {
|
||||||
|
$sItemName = \strtolower($sItemName);
|
||||||
|
$sItemName = isset(static::$aMap[$sItemName]) ? static::$aMap[$sItemName] : null;
|
||||||
|
}
|
||||||
|
$aHeaders[$iIndex] = $sItemName;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (false !== ($mRow = \fgetcsv($rFile, 5000, $sDelimiter, '"'))) {
|
||||||
\MailSo\Base\Utils::ResetTimeLimit();
|
\MailSo\Base\Utils::ResetTimeLimit();
|
||||||
$iCount = 0;
|
$iCount = 0;
|
||||||
$oVCard = new \Sabre\VObject\Component\VCard;
|
$oVCard = new VCard;
|
||||||
$aName = ['','','','',''];
|
$aName = ['','','','',''];
|
||||||
foreach ($aItem as $sItemName => $sItemValue) {
|
foreach ($mRow as $iIndex => $sItemValue) {
|
||||||
$sItemName = \strtoupper(\trim(\preg_replace('/[\s\-]+/', '', $sItemName)));
|
$sItemName = $aHeaders[$iIndex];
|
||||||
$sItemValue = \trim($sItemValue);
|
$sItemValue = \trim($sItemValue);
|
||||||
if (!empty($sItemName) && !empty($sItemValue)) {
|
if (isset($sItemName) && !empty($sItemValue)) {
|
||||||
if (\array_key_exists($sItemName, \Sabre\VObject\Component\VCard::$propertyMap)) {
|
$mType = \is_array($sItemName) ? $sItemName[0] : $sItemName;
|
||||||
$mData = $sItemName;
|
|
||||||
} else {
|
|
||||||
$sItemName = \strtolower($sItemName);
|
|
||||||
$mData = !empty($sItemName) && isset($aMap[$sItemName]) ? $aMap[$sItemName] : null;
|
|
||||||
}
|
|
||||||
if ($mData) {
|
|
||||||
$mType = \is_array($mData) ? $mData[0] : $mData;
|
|
||||||
++$iCount;
|
++$iCount;
|
||||||
if (\is_int($mType)) {
|
if (\is_int($mType)) {
|
||||||
$aName[$mType] = $sItemValue;
|
$aName[$mType] = $sItemValue;
|
||||||
} else if (\is_array($mData)) {
|
} else if (\is_array($sItemName)) {
|
||||||
$oVCard->add($mType, $sItemValue, ['type' => $mData[1]]);
|
$oVCard->add($mType, $sItemValue, ['type' => $sItemName[1]]);
|
||||||
} else if ('FN' === $mType || 'NICKNAME' === $mType) {
|
} else if ('FN' === $mType || 'NICKNAME' === $mType) {
|
||||||
$oVCard->$mType = $sItemValue;
|
$oVCard->$mType = $sItemValue;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -97,7 +129,6 @@ class Utils
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if ($iCount) {
|
if ($iCount) {
|
||||||
if ('' !== \implode('', $aName)) {
|
if ('' !== \implode('', $aName)) {
|
||||||
$oVCard->N = $aName;
|
$oVCard->N = $aName;
|
||||||
|
|
@ -109,7 +140,7 @@ class Utils
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function VCardToCsv($stream, Classes\Contact $oContact, bool $bWithHeader = false)/* : int|false*/
|
public static function VCardToCsv($stream, VCard $oVCard, bool $bWithHeader = false)/* : int|false*/
|
||||||
{
|
{
|
||||||
$aData = array();
|
$aData = array();
|
||||||
if ($bWithHeader) {
|
if ($bWithHeader) {
|
||||||
|
|
@ -124,8 +155,6 @@ class Utils
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
$oVCard = $oContact->vCard;
|
|
||||||
|
|
||||||
$aName = isset($oVCard->N) ? $oVCard->N->getParts() : ['','','','',''];
|
$aName = isset($oVCard->N) ? $oVCard->N->getParts() : ['','','','',''];
|
||||||
|
|
||||||
$adrHome = $oVCard->getByType('ADR', 'HOME');
|
$adrHome = $oVCard->getByType('ADR', 'HOME');
|
||||||
|
|
@ -169,16 +198,12 @@ class Utils
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function VcfFileToContacts(string $sVcfData) : iterable
|
public static function VcfStreamToContacts(/*resource*/ $rFile) : iterable
|
||||||
{
|
{
|
||||||
$sVcfData = \trim($sVcfData);
|
$oVCardSplitter = new \Sabre\VObject\Splitter\VCard($rFile);
|
||||||
if ("\xef\xbb\xbf" === \substr($sVcfData, 0, 3)) {
|
|
||||||
$sVcfData = \substr($sVcfData, 3);
|
|
||||||
}
|
|
||||||
$oVCardSplitter = new \Sabre\VObject\Splitter\VCard($sVcfData);
|
|
||||||
if ($oVCardSplitter) {
|
if ($oVCardSplitter) {
|
||||||
while ($oVCard = $oVCardSplitter->getNext()) {
|
while ($oVCard = $oVCardSplitter->getNext()) {
|
||||||
if ($oVCard instanceof \Sabre\VObject\Component\VCard) {
|
if ($oVCard instanceof VCard) {
|
||||||
\MailSo\Base\Utils::ResetTimeLimit();
|
\MailSo\Base\Utils::ResetTimeLimit();
|
||||||
$oContact = new Classes\Contact();
|
$oContact = new Classes\Contact();
|
||||||
$oContact->setVCard($oVCard);
|
$oContact->setVCard($oVCard);
|
||||||
|
|
|
||||||
|
|
@ -551,7 +551,7 @@ class ServiceActions
|
||||||
{
|
{
|
||||||
$this->oHttp->ServerNoCache();
|
$this->oHttp->ServerNoCache();
|
||||||
$sTo = \trim($_GET['to'] ?? '');
|
$sTo = \trim($_GET['to'] ?? '');
|
||||||
if (!empty($sTo) && \preg_match('/^mailto:/i', $sTo)) {
|
if (\preg_match('/^mailto:/i', $sTo)) {
|
||||||
\SnappyMail\Cookies::set(
|
\SnappyMail\Cookies::set(
|
||||||
Actions::AUTH_MAILTO_TOKEN_KEY,
|
Actions::AUTH_MAILTO_TOKEN_KEY,
|
||||||
Utils::EncodeKeyValuesQ(array(
|
Utils::EncodeKeyValuesQ(array(
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ class CSP
|
||||||
{
|
{
|
||||||
public
|
public
|
||||||
$base = ["'self'"],
|
$base = ["'self'"],
|
||||||
$default = ["'self'"],
|
$default = ["'self'", 'data:'],
|
||||||
// Knockout.js requires eval() for observable binding purposes
|
// Knockout.js requires eval() for observable binding purposes
|
||||||
// Safari < 15.4 does not support strict-dynamic
|
// Safari < 15.4 does not support strict-dynamic
|
||||||
// $script = ["'strict-dynamic'", "'unsafe-eval'"],
|
// $script = ["'strict-dynamic'", "'unsafe-eval'"],
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ abstract class Stream
|
||||||
// We just fake Drupal https://www.drupal.org/docs/8/core/modules/big-pipe/bigpipe-environment-requirements
|
// We just fake Drupal https://www.drupal.org/docs/8/core/modules/big-pipe/bigpipe-environment-requirements
|
||||||
\header('Surrogate-Control: no-store, content="BigPipe/1.0"');
|
\header('Surrogate-Control: no-store, content="BigPipe/1.0"');
|
||||||
// Explicitly disable caching so Varnish and other upstreams won't cache.
|
// Explicitly disable caching so Varnish and other upstreams won't cache.
|
||||||
\header('Cache-Control: no-store, no-cache, must-revalidate');
|
\header('Cache-Control: no-store');
|
||||||
\header('Pragma: no-cache');
|
\header('Pragma: no-cache');
|
||||||
|
|
||||||
// Nginx: disable fastcgi_buffering and disable gzip for this request.
|
// Nginx: disable fastcgi_buffering and disable gzip for this request.
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class ZIP
|
||||||
while ($i-- && \ob_end_clean());
|
while ($i-- && \ob_end_clean());
|
||||||
\ob_get_level() || \header('Content-Encoding: ');
|
\ob_get_level() || \header('Content-Encoding: ');
|
||||||
}
|
}
|
||||||
\header('Cache-Control: no-store, no-cache, must-revalidate');
|
\header('Cache-Control: no-store');
|
||||||
\header('Pragma: no-cache');
|
\header('Pragma: no-cache');
|
||||||
\header('Content-Transfer-Encoding: binary');
|
\header('Content-Transfer-Encoding: binary');
|
||||||
$name = "{$name}.zip";
|
$name = "{$name}.zip";
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ abstract class Upgrade
|
||||||
new \RecursiveDirectoryIterator("{$dir}/{$folder}", \FilesystemIterator::SKIP_DOTS),
|
new \RecursiveDirectoryIterator("{$dir}/{$folder}", \FilesystemIterator::SKIP_DOTS),
|
||||||
\RecursiveIteratorIterator::SELF_FIRST
|
\RecursiveIteratorIterator::SELF_FIRST
|
||||||
);
|
);
|
||||||
foreach ($items as $item) {
|
foreach ($iterator as $item) {
|
||||||
if ($item->isDir()) {
|
if ($item->isDir()) {
|
||||||
\chmod($item, 0755);
|
\chmod($item, 0755);
|
||||||
} else if ($item->isFile()) {
|
} else if ($item->isFile()) {
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "الرسائل في الصفحة",
|
"MESSAGE_PER_PAGE": "الرسائل في الصفحة",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Съобщения на страница",
|
"MESSAGE_PER_PAGE": "Съобщения на страница",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Zpráv na stranu",
|
"MESSAGE_PER_PAGE": "Zpráv na stranu",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Meddelelser per side",
|
"MESSAGE_PER_PAGE": "Meddelelser per side",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "HTML gegenüber Klartext bevorzugen",
|
"PREFER_HTML": "HTML gegenüber Klartext bevorzugen",
|
||||||
"PREFER_HTML_INFO": "Nachrichten können manchmal in beiden Formaten empfangen werden. Diese Option setzt, ob die Nachricht standardmäßig in HTML oder als Klartext angezeigt wird.",
|
"PREFER_HTML_INFO": "Nachrichten können manchmal in beiden Formaten empfangen werden. Diese Option setzt, ob die Nachricht standardmäßig in HTML oder als Klartext angezeigt wird.",
|
||||||
"REMOVE_COLORS": "Entferne Hintergrund- und Textfarben aus der Nachricht",
|
"REMOVE_COLORS": "Entferne Hintergrund- und Textfarben aus der Nachricht",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Nachrichten pro Seite",
|
"MESSAGE_PER_PAGE": "Nachrichten pro Seite",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Nachricht als gelesen markieren nach",
|
"MARK_MESSAGE_READ_AFTER": "Nachricht als gelesen markieren nach",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Μηνύματα στη σελίδα",
|
"MESSAGE_PER_PAGE": "Μηνύματα στη σελίδα",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Messages on page",
|
"MESSAGE_PER_PAGE": "Messages on page",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Messages on page",
|
"MESSAGE_PER_PAGE": "Messages on page",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"MOVE_TO": "Mover",
|
"MOVE_TO": "Mover",
|
||||||
"NAME": "Nombre",
|
"NAME": "Nombre",
|
||||||
"NONE": "Ninguno",
|
"NONE": "Ninguno",
|
||||||
"NOT_SPAM": "Correo no deseado",
|
"NOT_SPAM": "Es correo deseado",
|
||||||
"USERNAME": "Nombre de usuario",
|
"USERNAME": "Nombre de usuario",
|
||||||
"PASSWORD": "Contraseña",
|
"PASSWORD": "Contraseña",
|
||||||
"REMEMBER": "Remember",
|
"REMEMBER": "Remember",
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
"SAVE": "Guardar",
|
"SAVE": "Guardar",
|
||||||
"SAVE_CHANGES": "Save changes?",
|
"SAVE_CHANGES": "Save changes?",
|
||||||
"SEARCH": "Buscar",
|
"SEARCH": "Buscar",
|
||||||
"SPAM": "Es correo deseado",
|
"SPAM": "Correo no deseado",
|
||||||
"SUBJECT": "Asunto",
|
"SUBJECT": "Asunto",
|
||||||
"TEST": "Probar",
|
"TEST": "Probar",
|
||||||
"TO": "Para",
|
"TO": "Para",
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Eliminar el fondo y los colores del texto del mensaje",
|
"REMOVE_COLORS": "Eliminar el fondo y los colores del texto del mensaje",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Mensajes en página",
|
"MESSAGE_PER_PAGE": "Mensajes en página",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Marcar el mensaje como leído después de",
|
"MARK_MESSAGE_READ_AFTER": "Marcar el mensaje como leído después de",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Lehel kuvatavate kirjade arv",
|
"MESSAGE_PER_PAGE": "Lehel kuvatavate kirjade arv",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Lehenetsi HTMLa testu arruntari",
|
"PREFER_HTML": "Lehenetsi HTMLa testu arruntari",
|
||||||
"PREFER_HTML_INFO": "Mezuak batzuetan bi formatuetan datoz. Aukera honekin HTML edo testu arrunt partea erakustea aukera dezakezu.",
|
"PREFER_HTML_INFO": "Mezuak batzuetan bi formatuetan datoz. Aukera honekin HTML edo testu arrunt partea erakustea aukera dezakezu.",
|
||||||
"REMOVE_COLORS": "Kendu atzealdeko irudia eta testuaren kolorean mezuaren gorputzetik",
|
"REMOVE_COLORS": "Kendu atzealdeko irudia eta testuaren kolorean mezuaren gorputzetik",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Orrialdeko mezuak",
|
"MESSAGE_PER_PAGE": "Orrialdeko mezuak",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Markatu mezua irakurrita ondoren",
|
"MARK_MESSAGE_READ_AFTER": "Markatu mezua irakurrita ondoren",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "پیامها در صفحه",
|
"MESSAGE_PER_PAGE": "پیامها در صفحه",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Viestiä sivulla",
|
"MESSAGE_PER_PAGE": "Viestiä sivulla",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -173,11 +173,11 @@
|
||||||
"CONTACTS": {
|
"CONTACTS": {
|
||||||
"TAB_CONTACT": "Contact",
|
"TAB_CONTACT": "Contact",
|
||||||
"TAB_LOCATIONS": "Localisations",
|
"TAB_LOCATIONS": "Localisations",
|
||||||
"TAB_BUSINESS": "Métier",
|
"TAB_BUSINESS": "Professionel",
|
||||||
"LABEL_ORGANIZATION": "Entreprise",
|
"LABEL_ORGANIZATION": "Entreprise",
|
||||||
"LABEL_DEPARTMENT": "Department",
|
"LABEL_DEPARTMENT": "Service",
|
||||||
"LABEL_TITLE": "Titre",
|
"LABEL_TITLE": "Titre",
|
||||||
"TAB_CRYPTO": "Chiffre",
|
"TAB_CRYPTO": "Chiffrement",
|
||||||
"ASK": "Demander",
|
"ASK": "Demander",
|
||||||
"NEVER": "Jamais",
|
"NEVER": "Jamais",
|
||||||
"ALWAYS": "Toujours",
|
"ALWAYS": "Toujours",
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Préférer le HTML au texte brut",
|
"PREFER_HTML": "Préférer le HTML au texte brut",
|
||||||
"PREFER_HTML_INFO": "Les messages arrivent parfois dans les deux formats. Cette option contrôle si vous voulez que la partie HTML ou la partie texte brut soit affichée.",
|
"PREFER_HTML_INFO": "Les messages arrivent parfois dans les deux formats. Cette option contrôle si vous voulez que la partie HTML ou la partie texte brut soit affichée.",
|
||||||
"REMOVE_COLORS": "Supprimer les couleurs d'arrière-plan et de texte du message",
|
"REMOVE_COLORS": "Supprimer les couleurs d'arrière-plan et de texte du message",
|
||||||
|
"ALLOW_STYLES": "Autoriser <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Messages par page",
|
"MESSAGE_PER_PAGE": "Messages par page",
|
||||||
"CHECK_MAIL_INTERVAL": "Vérifier le messagerie à l'intervalle",
|
"CHECK_MAIL_INTERVAL": "Vérifier le messagerie à l'intervalle",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Marquer le message comme lu après",
|
"MARK_MESSAGE_READ_AFTER": "Marquer le message comme lu après",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "Images externes",
|
"EXTERNAL_IMAGES": "Images externes",
|
||||||
"IMAGES_OPTION_ASK": "Toujours demander",
|
"IMAGES_OPTION_ASK": "Toujours demander",
|
||||||
"IMAGES_OPTION_WHITELIST": "Ajouter à la liste blanche ou demander",
|
"IMAGES_OPTION_WHITELIST": "Ajouter à la liste blanche ou demander",
|
||||||
"IMAGES_OPTION_DKIM": "Toujours afficher quand DKIM est valide",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Toujours afficher",
|
"IMAGES_OPTION_ALWAYS": "Toujours afficher",
|
||||||
"IMAGES_WHITELIST": "Liste blanche Images"
|
"IMAGES_WHITELIST": "Liste blanche Images"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Üzenetek egy oldalon",
|
"MESSAGE_PER_PAGE": "Üzenetek egy oldalon",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Jelölje meg az üzenetet olvasottként",
|
"MARK_MESSAGE_READ_AFTER": "Jelölje meg az üzenetet olvasottként",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Pesan di halaman",
|
"MESSAGE_PER_PAGE": "Pesan di halaman",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Skilaboð á síðu",
|
"MESSAGE_PER_PAGE": "Skilaboð á síðu",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"TEST": "Test",
|
"TEST": "Test",
|
||||||
"UPDATE": "Aggiorna",
|
"UPDATE": "Aggiorna",
|
||||||
"USERNAME": "Nome utente",
|
"USERNAME": "Nome utente",
|
||||||
"PASSWORD": "Parola d'ordine",
|
"PASSWORD": "Password",
|
||||||
"CANCEL": "Annulla"
|
"CANCEL": "Annulla"
|
||||||
},
|
},
|
||||||
"LOGIN": {
|
"LOGIN": {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
"NONE": "Nessuno",
|
"NONE": "Nessuno",
|
||||||
"NOT_SPAM": "Segnala come non spam",
|
"NOT_SPAM": "Segnala come non spam",
|
||||||
"USERNAME": "Nome utente",
|
"USERNAME": "Nome utente",
|
||||||
"PASSWORD": "Parola d'ordine",
|
"PASSWORD": "Password",
|
||||||
"REMEMBER": "Remember",
|
"REMEMBER": "Remember",
|
||||||
"REPLY_TO": "Rispondi a",
|
"REPLY_TO": "Rispondi a",
|
||||||
"SAVE": "Salva",
|
"SAVE": "Salva",
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Usa HTML predefinito anziché formato testo",
|
"PREFER_HTML": "Usa HTML predefinito anziché formato testo",
|
||||||
"PREFER_HTML_INFO": "Il messaggio a volte contiene entrambi i formati. Questa opzione agisce sulla visualizzazione del messaggio in HTML o della versione in formato testo.",
|
"PREFER_HTML_INFO": "Il messaggio a volte contiene entrambi i formati. Questa opzione agisce sulla visualizzazione del messaggio in HTML o della versione in formato testo.",
|
||||||
"REMOVE_COLORS": "Rimuovi lo sfondo e i colori del testo dal messaggio",
|
"REMOVE_COLORS": "Rimuovi lo sfondo e i colori del testo dal messaggio",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Messaggi per pagina",
|
"MESSAGE_PER_PAGE": "Messaggi per pagina",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Contrassegna il messaggio come letto dopo",
|
"MARK_MESSAGE_READ_AFTER": "Contrassegna il messaggio come letto dopo",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "プレーンテキストよりHTMLを優先する",
|
"PREFER_HTML": "プレーンテキストよりHTMLを優先する",
|
||||||
"PREFER_HTML_INFO": "メッセージがHTMLとプレーンテキストの両方の形式を含むことがあります。このオプションはそのような場合にどちらの形式を表示するかを制御します。",
|
"PREFER_HTML_INFO": "メッセージがHTMLとプレーンテキストの両方の形式を含むことがあります。このオプションはそのような場合にどちらの形式を表示するかを制御します。",
|
||||||
"REMOVE_COLORS": "本文の文字色及び背景色の設定を無視する",
|
"REMOVE_COLORS": "本文の文字色及び背景色の設定を無視する",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "1ページに表示する件数",
|
"MESSAGE_PER_PAGE": "1ページに表示する件数",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "メッセージが既読となるまでの秒数",
|
"MARK_MESSAGE_READ_AFTER": "メッセージが既読となるまでの秒数",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "페이지 당 메시지 개수",
|
"MESSAGE_PER_PAGE": "페이지 당 메시지 개수",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Žinučių lape",
|
"MESSAGE_PER_PAGE": "Žinučių lape",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Ziņojumi lapā",
|
"MESSAGE_PER_PAGE": "Ziņojumi lapā",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Foretrekk HTML fremfor",
|
"PREFER_HTML": "Foretrekk HTML fremfor",
|
||||||
"PREFER_HTML_INFO": "Meldinger kan ankomme i både HTML og ren tekst. Dette valget styrer hvilken av dem du vil se.",
|
"PREFER_HTML_INFO": "Meldinger kan ankomme i både HTML og ren tekst. Dette valget styrer hvilken av dem du vil se.",
|
||||||
"REMOVE_COLORS": "Fjern bakgrunn og tekstfarger fra brødtekst",
|
"REMOVE_COLORS": "Fjern bakgrunn og tekstfarger fra brødtekst",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Meldinger per side",
|
"MESSAGE_PER_PAGE": "Meldinger per side",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Marker melding som lest etter",
|
"MARK_MESSAGE_READ_AFTER": "Marker melding som lest etter",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Geef de voorkeur aan HTML boven platte tekst",
|
"PREFER_HTML": "Geef de voorkeur aan HTML boven platte tekst",
|
||||||
"PREFER_HTML_INFO": "Berichten komen soms in beide formaten. Deze optie bepaalt of u het HTML-gedeelte of het platte tekstgedeelte wilt weergeven.",
|
"PREFER_HTML_INFO": "Berichten komen soms in beide formaten. Deze optie bepaalt of u het HTML-gedeelte of het platte tekstgedeelte wilt weergeven.",
|
||||||
"REMOVE_COLORS": "Verwijder achtergrond- en tekstkleuren uit het bericht",
|
"REMOVE_COLORS": "Verwijder achtergrond- en tekstkleuren uit het bericht",
|
||||||
|
"ALLOW_STYLES": "CSS <style> toestaan",
|
||||||
"MESSAGE_PER_PAGE": "Berichten op pagina",
|
"MESSAGE_PER_PAGE": "Berichten op pagina",
|
||||||
"CHECK_MAIL_INTERVAL": "Controleer mail interval",
|
"CHECK_MAIL_INTERVAL": "Controleer mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Bericht markeren als gelezen na",
|
"MARK_MESSAGE_READ_AFTER": "Bericht markeren als gelezen na",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Wiadomości na stronę",
|
"MESSAGE_PER_PAGE": "Wiadomości na stronę",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Mensagens por Página",
|
"MESSAGE_PER_PAGE": "Mensagens por Página",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Preferir HTML em vez de texto simples",
|
"PREFER_HTML": "Preferir HTML em vez de texto simples",
|
||||||
"PREFER_HTML_INFO": "Por vezes as mensagens são recebidas em ambos os formatos. Esta opção determina se prefere apresentar o conteúdo em HTML ou em texto simples.",
|
"PREFER_HTML_INFO": "Por vezes as mensagens são recebidas em ambos os formatos. Esta opção determina se prefere apresentar o conteúdo em HTML ou em texto simples.",
|
||||||
"REMOVE_COLORS": "Retirar imagens de fundo e coloração de texto do corpo das mensagens",
|
"REMOVE_COLORS": "Retirar imagens de fundo e coloração de texto do corpo das mensagens",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Mensagens por página",
|
"MESSAGE_PER_PAGE": "Mensagens por página",
|
||||||
"CHECK_MAIL_INTERVAL": "Intervalo de verificação do correio",
|
"CHECK_MAIL_INTERVAL": "Intervalo de verificação do correio",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Marcar mensagem como lida após",
|
"MARK_MESSAGE_READ_AFTER": "Marcar mensagem como lida após",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "Imagens externas",
|
"EXTERNAL_IMAGES": "Imagens externas",
|
||||||
"IMAGES_OPTION_ASK": "Perguntar sempre",
|
"IMAGES_OPTION_ASK": "Perguntar sempre",
|
||||||
"IMAGES_OPTION_WHITELIST": "Lista Branca ou perguntar",
|
"IMAGES_OPTION_WHITELIST": "Lista Branca ou perguntar",
|
||||||
"IMAGES_OPTION_DKIM": "Mostrar sempre que DKIM for válido",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Mostrar sempre",
|
"IMAGES_OPTION_ALWAYS": "Mostrar sempre",
|
||||||
"IMAGES_WHITELIST": "Lista Branca de imagens"
|
"IMAGES_WHITELIST": "Lista Branca de imagens"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Preferir HTML em vez de texto simples",
|
"PREFER_HTML": "Preferir HTML em vez de texto simples",
|
||||||
"PREFER_HTML_INFO": "Por vezes as mensagens são recebidas em ambos os formatos. Esta opção determina se prefere apresentar o conteúdo em HTML ou em texto simples.",
|
"PREFER_HTML_INFO": "Por vezes as mensagens são recebidas em ambos os formatos. Esta opção determina se prefere apresentar o conteúdo em HTML ou em texto simples.",
|
||||||
"REMOVE_COLORS": "Retirar imagens de fundo e coloração de texto do corpo das mensagens",
|
"REMOVE_COLORS": "Retirar imagens de fundo e coloração de texto do corpo das mensagens",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Mensagens por página",
|
"MESSAGE_PER_PAGE": "Mensagens por página",
|
||||||
"CHECK_MAIL_INTERVAL": "Intervalo de verificação do correio",
|
"CHECK_MAIL_INTERVAL": "Intervalo de verificação do correio",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Marcar mensagem como lida após",
|
"MARK_MESSAGE_READ_AFTER": "Marcar mensagem como lida após",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "Imagens externas",
|
"EXTERNAL_IMAGES": "Imagens externas",
|
||||||
"IMAGES_OPTION_ASK": "Perguntar sempre",
|
"IMAGES_OPTION_ASK": "Perguntar sempre",
|
||||||
"IMAGES_OPTION_WHITELIST": "Lista Branca ou perguntar",
|
"IMAGES_OPTION_WHITELIST": "Lista Branca ou perguntar",
|
||||||
"IMAGES_OPTION_DKIM": "Mostrar sempre que DKIM for válido",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Mostrar sempre",
|
"IMAGES_OPTION_ALWAYS": "Mostrar sempre",
|
||||||
"IMAGES_WHITELIST": "Lista Branca de imagens"
|
"IMAGES_WHITELIST": "Lista Branca de imagens"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Mesaje pe o pagină",
|
"MESSAGE_PER_PAGE": "Mesaje pe o pagină",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Cообщений на одной странице",
|
"MESSAGE_PER_PAGE": "Cообщений на одной странице",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Správ na stranu",
|
"MESSAGE_PER_PAGE": "Správ na stranu",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Sporočil na strani",
|
"MESSAGE_PER_PAGE": "Sporočil na strani",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Meddelanden per sida",
|
"MESSAGE_PER_PAGE": "Meddelanden per sida",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Markera meddelandet som läst efter",
|
"MARK_MESSAGE_READ_AFTER": "Markera meddelandet som läst efter",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,7 @@
|
||||||
"PREFER_HTML": "Prefer HTML to plain text",
|
"PREFER_HTML": "Prefer HTML to plain text",
|
||||||
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
"PREFER_HTML_INFO": "Messages sometimes come in both formats. This option controls whether you want the HTML part or the plain text part to be displayed.",
|
||||||
"REMOVE_COLORS": "Remove background and text colors from message body",
|
"REMOVE_COLORS": "Remove background and text colors from message body",
|
||||||
|
"ALLOW_STYLES": "Allow <style> CSS",
|
||||||
"MESSAGE_PER_PAGE": "Messages on page",
|
"MESSAGE_PER_PAGE": "Messages on page",
|
||||||
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
"CHECK_MAIL_INTERVAL": "Check mail interval",
|
||||||
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
"MARK_MESSAGE_READ_AFTER": "Mark message as read after",
|
||||||
|
|
@ -421,7 +422,6 @@
|
||||||
"EXTERNAL_IMAGES": "External images",
|
"EXTERNAL_IMAGES": "External images",
|
||||||
"IMAGES_OPTION_ASK": "Always ask",
|
"IMAGES_OPTION_ASK": "Always ask",
|
||||||
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
"IMAGES_OPTION_WHITELIST": "Whitelist or ask",
|
||||||
"IMAGES_OPTION_DKIM": "Show always when DKIM is valid",
|
|
||||||
"IMAGES_OPTION_ALWAYS": "Show always",
|
"IMAGES_OPTION_ALWAYS": "Show always",
|
||||||
"IMAGES_WHITELIST": "Images whitelist"
|
"IMAGES_WHITELIST": "Images whitelist"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
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