mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-30 04:29:21 +03:00
Merge branch 'master' into addressbook
# Conflicts: # snappymail/v/0.0.0/app/templates/Views/User/PopupsContacts.html
This commit is contained in:
commit
f46e996570
138 changed files with 2587 additions and 1016 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import 'External/User/ko';
|
||||
|
||||
import { SMAudio } from 'Common/Audio';
|
||||
import { isArray, pString, changeTheme } from 'Common/Utils';
|
||||
import { mailToHelper, setLayoutResizer, dropdownsDetectVisibility } from 'Common/UtilsUser';
|
||||
|
||||
|
|
@ -24,8 +25,6 @@ import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
|||
|
||||
import {
|
||||
MessageFlagsCache,
|
||||
setFolderHash,
|
||||
getFolderHash,
|
||||
getFolderInboxName,
|
||||
getFolderFromCacheList
|
||||
} from 'Common/Cache';
|
||||
|
|
@ -222,38 +221,33 @@ export class AppUser extends AbstractApp {
|
|||
(iError, data) => {
|
||||
if (!iError && data.Result) {
|
||||
const result = data.Result,
|
||||
hash = getFolderHash(result.Folder),
|
||||
folderFromCache = getFolderFromCacheList(result.Folder);
|
||||
if (folderFromCache) {
|
||||
const oldHash = folderFromCache.hash,
|
||||
unreadCountChange = (folderFromCache.unreadEmails() !== result.unreadEmails);
|
||||
|
||||
// folderFromCache.revivePropertiesFromJson(result);
|
||||
folderFromCache.expires = Date.now();
|
||||
|
||||
setFolderHash(result.Folder, result.Hash);
|
||||
|
||||
folderFromCache.messageCountAll(result.MessageCount);
|
||||
|
||||
let unreadCountChange = (folderFromCache.messageCountUnread() !== result.MessageUnseenCount);
|
||||
|
||||
folderFromCache.messageCountUnread(result.MessageUnseenCount);
|
||||
folderFromCache.uidNext = result.UidNext;
|
||||
folderFromCache.hash = result.Hash;
|
||||
folderFromCache.totalEmails(result.totalEmails);
|
||||
folderFromCache.unreadEmails(result.unreadEmails);
|
||||
|
||||
if (unreadCountChange) {
|
||||
MessageFlagsCache.clearFolder(folderFromCache.fullName);
|
||||
}
|
||||
|
||||
if (result.Flags.length) {
|
||||
result.Flags.forEach(message =>
|
||||
if (result.MessagesFlags.length) {
|
||||
result.MessagesFlags.forEach(message =>
|
||||
MessageFlagsCache.setFor(folderFromCache.fullName, message.Uid.toString(), message.Flags)
|
||||
);
|
||||
|
||||
MessagelistUserStore.reloadFlagsAndCachedMessage();
|
||||
}
|
||||
|
||||
MessagelistUserStore.initUidNextAndNewMessages(
|
||||
folderFromCache.fullName,
|
||||
result.UidNext,
|
||||
result.NewMessages
|
||||
);
|
||||
MessagelistUserStore.notifyNewMessages(folderFromCache.fullName, result.NewMessages);
|
||||
|
||||
if (!hash || unreadCountChange || result.Hash !== hash) {
|
||||
if (!oldHash || unreadCountChange || result.Hash !== oldHash) {
|
||||
if (folderFromCache.fullName === FolderUserStore.currentFolderFullName()) {
|
||||
MessagelistUserStore.reload();
|
||||
} else if (getFolderInboxName() === folderFromCache.fullName) {
|
||||
|
|
@ -296,8 +290,8 @@ export class AppUser extends AbstractApp {
|
|||
if (SettingsGet('Auth')) {
|
||||
rl.setWindowTitle(i18n('GLOBAL/LOADING'));
|
||||
|
||||
NotificationUserStore.enableSoundNotification(!!SettingsGet('SoundNotification'));
|
||||
NotificationUserStore.enableDesktopNotification(!!SettingsGet('DesktopNotifications'));
|
||||
SMAudio.notifications(!!SettingsGet('SoundNotification'));
|
||||
NotificationUserStore.enabled(!!SettingsGet('DesktopNotifications'));
|
||||
|
||||
AccountUserStore.email(SettingsGet('Email'));
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import * as Links from 'Common/Links';
|
||||
import { doc, SettingsGet, fireEvent, addEventsListener } from 'Common/Globals';
|
||||
import { addObservablesTo } from 'External/ko';
|
||||
|
||||
let notificator = null,
|
||||
player = null,
|
||||
|
|
@ -45,12 +46,12 @@ let notificator = null,
|
|||
'keydown','keyup'
|
||||
],
|
||||
unlock = () => {
|
||||
unlockEvents.forEach(type => doc.removeEventListener(type, unlock, true));
|
||||
if (audioCtx) {
|
||||
console.log('AudioContext ' + audioCtx.state);
|
||||
audioCtx.resume();
|
||||
}
|
||||
unlockEvents.forEach(type => doc.removeEventListener(type, unlock, true));
|
||||
// setTimeout(()=>Audio.playNotification(1),1);
|
||||
// setTimeout(()=>SMAudio.playNotification(0,1),1);
|
||||
};
|
||||
|
||||
if (audioCtx) {
|
||||
|
|
@ -76,6 +77,10 @@ export const SMAudio = new class {
|
|||
addEventsListener(player, ['ended','error'], stopFn);
|
||||
addEventListener('audio.api.stop', stopFn);
|
||||
}
|
||||
|
||||
addObservablesTo(this, {
|
||||
notifications: false
|
||||
});
|
||||
}
|
||||
|
||||
paused() {
|
||||
|
|
@ -103,18 +108,23 @@ export const SMAudio = new class {
|
|||
this.supportedWav && play(url, name);
|
||||
}
|
||||
|
||||
playNotification(silent) {
|
||||
if ('running' == audioCtx.state && (this.supportedMp3 || this.supportedOgg)) {
|
||||
notificator = notificator || createNewObject();
|
||||
if (notificator) {
|
||||
notificator.src = Links.staticLink('sounds/'
|
||||
+ SettingsGet('NotificationSound')
|
||||
+ (this.supportedMp3 ? '.mp3' : '.ogg'));
|
||||
notificator.volume = silent ? 0.01 : 1;
|
||||
notificator.play();
|
||||
/**
|
||||
* Used with SoundNotification setting
|
||||
*/
|
||||
playNotification(force, silent) {
|
||||
if (force || this.notifications()) {
|
||||
if ('running' == audioCtx.state && (this.supportedMp3 || this.supportedOgg)) {
|
||||
notificator = notificator || createNewObject();
|
||||
if (notificator) {
|
||||
notificator.src = Links.staticLink('sounds/'
|
||||
+ SettingsGet('NotificationSound')
|
||||
+ (this.supportedMp3 ? '.mp3' : '.ogg'));
|
||||
notificator.volume = silent ? 0.01 : 1;
|
||||
notificator.play();
|
||||
}
|
||||
} else {
|
||||
console.log('No audio: ' + audioCtx.state);
|
||||
}
|
||||
} else {
|
||||
console.log('No audio: ' + audioCtx.state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import { isArray } from 'Common/Utils';
|
|||
let FOLDERS_CACHE = {},
|
||||
FOLDERS_NAME_CACHE = {},
|
||||
MESSAGE_FLAGS_CACHE = {},
|
||||
NEW_MESSAGE_CACHE = {},
|
||||
REQUESTED_MESSAGE_CACHE = {},
|
||||
inboxFolderName = 'INBOX';
|
||||
|
||||
export const
|
||||
|
|
@ -16,8 +14,6 @@ export const
|
|||
FOLDERS_CACHE = {};
|
||||
FOLDERS_NAME_CACHE = {};
|
||||
MESSAGE_FLAGS_CACHE = {};
|
||||
NEW_MESSAGE_CACHE = {};
|
||||
REQUESTED_MESSAGE_CACHE = {};
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -27,42 +23,6 @@ export const
|
|||
*/
|
||||
getMessageKey = (folderFullName, uid) => folderFullName + '#' + uid,
|
||||
|
||||
/**
|
||||
* @param {string} folder
|
||||
* @param {string} uid
|
||||
*/
|
||||
addRequestedMessage = (folder, uid) => REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)] = true,
|
||||
|
||||
/**
|
||||
* @param {string} folder
|
||||
* @param {string} uid
|
||||
* @returns {boolean}
|
||||
*/
|
||||
hasRequestedMessage = (folder, uid) => true === REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)],
|
||||
|
||||
/**
|
||||
* @param {string} folderFullName
|
||||
* @param {string} uid
|
||||
*/
|
||||
addNewMessageCache = (folderFullName, uid) => NEW_MESSAGE_CACHE[getMessageKey(folderFullName, uid)] = true,
|
||||
|
||||
/**
|
||||
* @param {string} folderFullName
|
||||
* @param {string} uid
|
||||
*/
|
||||
hasNewMessageAndRemoveFromCache = (folderFullName, uid) => {
|
||||
if (NEW_MESSAGE_CACHE[getMessageKey(folderFullName, uid)]) {
|
||||
NEW_MESSAGE_CACHE[getMessageKey(folderFullName, uid)] = null;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* @returns {void}
|
||||
*/
|
||||
clearNewMessageCache = () => NEW_MESSAGE_CACHE = {},
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
|
|
@ -91,13 +51,6 @@ export const
|
|||
FOLDERS_NAME_CACHE[folder.fullNameHash] = folder.fullName;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {string} folderFullName
|
||||
* @returns {string}
|
||||
*/
|
||||
getFolderHash = folderFullName =>
|
||||
FOLDERS_CACHE[folderFullName] ? FOLDERS_CACHE[folderFullName].hash : '',
|
||||
|
||||
/**
|
||||
* @param {string} folderFullName
|
||||
* @param {string} folderHash
|
||||
|
|
@ -105,20 +58,6 @@ export const
|
|||
setFolderHash = (folderFullName, folderHash) =>
|
||||
FOLDERS_CACHE[folderFullName] && (FOLDERS_CACHE[folderFullName].hash = folderHash),
|
||||
|
||||
/**
|
||||
* @param {string} folderFullName
|
||||
* @returns {string}
|
||||
*/
|
||||
getFolderUidNext = folderFullName =>
|
||||
FOLDERS_CACHE[folderFullName] ? FOLDERS_CACHE[folderFullName].uidNext : 0,
|
||||
|
||||
/**
|
||||
* @param {string} folderFullName
|
||||
* @param {string} uidNext
|
||||
*/
|
||||
setFolderUidNext = (folderFullName, uidNext) =>
|
||||
FOLDERS_CACHE[folderFullName] && (FOLDERS_CACHE[folderFullName].uidNext = uidNext),
|
||||
|
||||
/**
|
||||
* @param {string} folderFullName
|
||||
* @returns {?FolderModel}
|
||||
|
|
@ -182,23 +121,19 @@ export class MessageFlagsCache
|
|||
if (message) {
|
||||
const uid = message.uid,
|
||||
flags = this.getFor(message.folder, uid),
|
||||
thread = message.threads;
|
||||
thread = message.threads();
|
||||
|
||||
if (isArray(flags)) {
|
||||
message.flags(flags);
|
||||
}
|
||||
|
||||
if (thread.length) {
|
||||
const unseenSubUid = thread.find(iSubUid =>
|
||||
message.hasUnseenSubMessage(!!thread.find(iSubUid =>
|
||||
(uid !== iSubUid) && !this.hasFlag(message.folder, iSubUid, '\\seen')
|
||||
);
|
||||
|
||||
const flaggedSubUid = thread.find(iSubUid =>
|
||||
));
|
||||
message.hasFlaggedSubMessage(!!thread.find(iSubUid =>
|
||||
(uid !== iSubUid) && this.hasFlag(message.folder, iSubUid, '\\flagged')
|
||||
);
|
||||
|
||||
message.hasUnseenSubMessage(!!unseenSubUid);
|
||||
message.hasFlaggedSubMessage(!!flaggedSubUid);
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -207,9 +142,7 @@ export class MessageFlagsCache
|
|||
* @param {(MessageModel|null)} message
|
||||
*/
|
||||
static store(message) {
|
||||
if (message) {
|
||||
this.setFor(message.folder, message.uid, message.flags());
|
||||
}
|
||||
message && this.setFor(message.folder, message.uid, message.flags());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@ import { isArray, arrayLength } from 'Common/Utils';
|
|||
import {
|
||||
MessageFlagsCache,
|
||||
setFolderHash,
|
||||
getFolderHash,
|
||||
getFolderInboxName,
|
||||
getFolderFromCacheList,
|
||||
getFolderUidNext
|
||||
getFolderFromCacheList
|
||||
} from 'Common/Cache';
|
||||
import { SettingsUserStore } from 'Stores/User/Settings';
|
||||
import { FolderUserStore } from 'Stores/User/Folder';
|
||||
|
|
@ -34,17 +32,18 @@ sortFolders = folders => {
|
|||
*/
|
||||
fetchFolderInformation = (fCallback, folder, list = []) => {
|
||||
let fetch = !arrayLength(list);
|
||||
const uids = [];
|
||||
const uids = [],
|
||||
folderFromCache = getFolderFromCacheList(folder);
|
||||
|
||||
if (!fetch) {
|
||||
list.forEach(messageListItem => {
|
||||
if (!MessageFlagsCache.getFor(messageListItem.folder, messageListItem.uid)) {
|
||||
if (!MessageFlagsCache.getFor(folder, messageListItem.uid)) {
|
||||
uids.push(messageListItem.uid);
|
||||
}
|
||||
|
||||
if (messageListItem.threads.length) {
|
||||
messageListItem.threads.forEach(uid => {
|
||||
if (!MessageFlagsCache.getFor(messageListItem.folder, uid)) {
|
||||
if (!MessageFlagsCache.getFor(folder, uid)) {
|
||||
uids.push(uid);
|
||||
}
|
||||
});
|
||||
|
|
@ -57,7 +56,7 @@ fetchFolderInformation = (fCallback, folder, list = []) => {
|
|||
Remote.request('FolderInformation', fCallback, {
|
||||
Folder: folder,
|
||||
FlagsUids: uids,
|
||||
UidNext: getFolderUidNext(folder) // Used to check for new messages
|
||||
UidNext: (folderFromCache && folderFromCache.uidNext) || 0 // Used to check for new messages
|
||||
});
|
||||
} else if (SettingsUserStore.useThreads()) {
|
||||
MessagelistUserStore.reloadFlagsAndCachedMessage();
|
||||
|
|
@ -140,25 +139,23 @@ folderInformationMultiply = (boot = false) => {
|
|||
if (!iError && arrayLength(oData.Result)) {
|
||||
const utc = Date.now();
|
||||
oData.Result.forEach(item => {
|
||||
const hash = getFolderHash(item.Folder),
|
||||
folder = getFolderFromCacheList(item.Folder);
|
||||
const folder = getFolderFromCacheList(item.Folder);
|
||||
|
||||
if (folder) {
|
||||
const oldHash = folder.hash,
|
||||
unreadCountChange = folder.unreadEmails() !== item.unreadEmails;
|
||||
|
||||
// folder.revivePropertiesFromJson(item);
|
||||
folder.expires = utc;
|
||||
|
||||
setFolderHash(item.Folder, item.Hash);
|
||||
|
||||
folder.messageCountAll(item.MessageCount);
|
||||
|
||||
let unreadCountChange = folder.messageCountUnread() !== item.MessageUnseenCount;
|
||||
|
||||
folder.messageCountUnread(item.MessageUnseenCount);
|
||||
folder.hash = item.Hash;
|
||||
folder.totalEmails(item.totalEmails);
|
||||
folder.unreadEmails(item.unreadEmails);
|
||||
|
||||
if (unreadCountChange) {
|
||||
MessageFlagsCache.clearFolder(folder.fullName);
|
||||
}
|
||||
|
||||
if (!hash || item.Hash !== hash) {
|
||||
if (!oldHash || item.Hash !== oldHash) {
|
||||
if (folder.fullName === FolderUserStore.currentFolderFullName()) {
|
||||
MessagelistUserStore.reload();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ const
|
|||
stripTracking = text => text
|
||||
.replace(/tracking\.(printabout\.nl[^?]+)\?.*/gsi, (...m) => m[1])
|
||||
.replace(/^.+awstrack\.me\/.+(https:%2F%2F[^/]+)/gsi, (...m) => decodeURIComponent(m[1]))
|
||||
.replace(/([?&])utm_[a-z]+=[^&?#]*/gsi, '$1')
|
||||
.replace(/([?&])utm_[a-z]+=[^&?#]*/gsi, '$1') // Urchin Tracking Module
|
||||
.replace(/([?&])ec_[a-z]+=[^&?#]*/gsi, '$1') // Sitecore
|
||||
.replace(/&&+/, '');
|
||||
|
||||
export const
|
||||
|
|
@ -99,7 +100,7 @@ export const
|
|||
'BGSOUND','KEYGEN','SOURCE','OBJECT','EMBED','APPLET','IFRAME','FRAME','FRAMESET','VIDEO','AUDIO','AREA','MAP'
|
||||
],
|
||||
nonEmptyTags = [
|
||||
'A','B','EM','I','SPAN','STRONG','O:P','TABLE'
|
||||
'A','B','EM','I','SPAN','STRONG','TABLE'
|
||||
];
|
||||
|
||||
tpl.innerHTML = html
|
||||
|
|
@ -131,13 +132,12 @@ export const
|
|||
// || (oStyle.maxHeight && 1 > parseFloat(oStyle.maxHeight)
|
||||
// || (oStyle.maxWidth && 1 > parseFloat(oStyle.maxWidth)
|
||||
// || ('0' === oStyle.opacity
|
||||
|| (nonEmptyTags.includes(name) && ('' == oElement.textContent.trim() && !oElement.querySelector('img')))
|
||||
) {
|
||||
oElement.remove();
|
||||
return;
|
||||
}
|
||||
// if (['CENTER','FORM'].includes(name)) {
|
||||
if ('FORM' === name || 'O:P' === name) {
|
||||
if ('FORM' === name || 'O:P' === name || (nonEmptyTags.includes(name) && ('' == oElement.textContent.trim()))) {
|
||||
replaceWithChildren(oElement);
|
||||
return;
|
||||
}
|
||||
|
|
@ -180,6 +180,7 @@ export const
|
|||
let value;
|
||||
|
||||
// if ('TABLE' === name || 'TD' === name || 'TH' === name) {
|
||||
if (!oStyle.backgroundImage) {
|
||||
if (hasAttribute('width')) {
|
||||
value = getAttribute('width');
|
||||
oStyle.width = value.includes('%') ? value : value + 'px';
|
||||
|
|
@ -201,6 +202,7 @@ export const
|
|||
if (value && !value.includes('%')) {
|
||||
oStyle.maxHeight = value;
|
||||
}
|
||||
}
|
||||
// } else
|
||||
if ('A' === name) {
|
||||
value = oElement.href;
|
||||
|
|
@ -301,17 +303,16 @@ export const
|
|||
oStyle.removeProperty('cursor');
|
||||
oStyle.removeProperty('min-width');
|
||||
|
||||
const urls = {
|
||||
remote: [], // 'data-x-style-url'
|
||||
broken: [] // 'data-x-broken-style-src'
|
||||
};
|
||||
const
|
||||
urls_remote = [], // 'data-x-style-url'
|
||||
urls_broken = []; // 'data-x-broken-style-src'
|
||||
['backgroundImage', 'listStyleImage', 'content'].forEach(property => {
|
||||
if (oStyle[property]) {
|
||||
let value = oStyle[property],
|
||||
found = value.match(/url\s*\(([^)]+)\)/gi);
|
||||
found = value.match(/url\s*\(([^)]+)\)/i);
|
||||
if (found) {
|
||||
oStyle[property] = null;
|
||||
found = found[0].replace(/^["'\s]+|["'\s]+$/g, '');
|
||||
found = found[1].replace(/^["'\s]+|["'\s]+$/g, '');
|
||||
let lowerUrl = found.toLowerCase();
|
||||
if ('cid:' === lowerUrl.slice(0, 4)) {
|
||||
const attachment = findAttachmentByCid(found);
|
||||
|
|
@ -320,13 +321,13 @@ export const
|
|||
attachment.isInline(true);
|
||||
attachment.isLinked(true);
|
||||
}
|
||||
} else if (/http[s]?:\/\//.test(lowerUrl) || '//' === found.slice(0, 2)) {
|
||||
} else if (/^(https?:)?\/\//.test(lowerUrl)) {
|
||||
result.hasExternals = true;
|
||||
urls.remote[property] = useProxy ? proxy(found) : found;
|
||||
urls_remote.push([property, useProxy ? proxy(found) : value]);
|
||||
} else if ('data:image/' === lowerUrl.slice(0, 11)) {
|
||||
oStyle[property] = value;
|
||||
} else {
|
||||
urls.broken[property] = found;
|
||||
urls_broken.push([property, found]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -334,11 +335,11 @@ export const
|
|||
// oStyle.removeProperty('background-image');
|
||||
// oStyle.removeProperty('list-style-image');
|
||||
|
||||
if (urls.remote.length) {
|
||||
setAttribute('data-x-style-url', JSON.stringify(urls.remote));
|
||||
if (urls_remote.length) {
|
||||
setAttribute('data-x-style-url', JSON.stringify(urls_remote));
|
||||
}
|
||||
if (urls.broken.length) {
|
||||
setAttribute('data-x-style-broken-urls', JSON.stringify(urls.broken));
|
||||
if (urls_broken.length) {
|
||||
setAttribute('data-x-style-broken-urls', JSON.stringify(urls_broken));
|
||||
}
|
||||
|
||||
if (11 > pInt(oStyle.fontSize)) {
|
||||
|
|
@ -407,9 +408,6 @@ export const
|
|||
.replace(/<t[dh](\s[\s\S]*?)?>/gi, '\t')
|
||||
.replace(/<\/tr(\s[\s\S]*?)?>/gi, '\n');
|
||||
|
||||
// Convert line-breaks
|
||||
forEach('br', br => br.replaceWith('\n'));
|
||||
|
||||
// lines
|
||||
forEach('hr', node => node.replaceWith(`\n\n${hr}\n\n`));
|
||||
|
||||
|
|
@ -456,10 +454,17 @@ export const
|
|||
// Italic
|
||||
forEach('i,em', i => i.replaceWith(`*${i.textContent}*`));
|
||||
|
||||
// Convert line-breaks
|
||||
tpl.innerHTML = tpl.innerHTML
|
||||
.replace(/\n{3,}/gm, '\n\n')
|
||||
.replace(/\n<br[^>]*>/g, '\n')
|
||||
.replace(/<br[^>]*>\n/g, '\n');
|
||||
forEach('br', br => br.replaceWith('\n'));
|
||||
|
||||
// Blockquotes must be last
|
||||
blockquotes(tpl.content);
|
||||
|
||||
return (tpl.content.textContent || '').replace(/\n{3,}/gm, '\n\n').trim();
|
||||
return (tpl.content.textContent || '').trim();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { MessageFlagsCache, addRequestedMessage } from 'Common/Cache';
|
||||
import { MessageFlagsCache } from 'Common/Cache';
|
||||
import { Notification } from 'Common/Enums';
|
||||
import { MessageSetAction, ComposeType/*, FolderType*/ } from 'Common/EnumsUser';
|
||||
import { doc, createElement, elementById, dropdowns, dropdownVisibility } from 'Common/Globals';
|
||||
import { doc, createElement, elementById, dropdowns, dropdownVisibility, SettingsGet } from 'Common/Globals';
|
||||
import { plainToHtml } from 'Common/Html';
|
||||
import { getNotification } from 'Common/Translator';
|
||||
import { EmailModel } from 'Model/Email';
|
||||
|
|
@ -181,20 +181,34 @@ setLayoutResizer = (source, target, sClientSideKeyName, mode) =>
|
|||
target.removeAttribute('style');
|
||||
source.removeAttribute('style');
|
||||
}
|
||||
source.observer && source.observer.disconnect();
|
||||
// source.classList.toggle('resizable', mode);
|
||||
if (mode) {
|
||||
const length = Local.get(sClientSideKeyName+mode);
|
||||
const length = Local.get(sClientSideKeyName + mode) || SettingsGet('Resizer' + sClientSideKeyName + mode),
|
||||
setTargetPos = mode => {
|
||||
let value;
|
||||
if ('Width' == mode) {
|
||||
value = source.offsetWidth;
|
||||
target.style.left = value + 'px';
|
||||
} else {
|
||||
value = source.offsetHeight;
|
||||
target.style.top = (4 + source.offsetTop + value) + 'px';
|
||||
}
|
||||
return value;
|
||||
};
|
||||
if (length) {
|
||||
source.style[mode.toLowerCase()] = length + 'px';
|
||||
setTargetPos(mode);
|
||||
}
|
||||
if (!source.layoutResizer) {
|
||||
const resizer = createElement('div', {'class':'resizer'}),
|
||||
save = (data => Remote.saveSettings(0, data)).debounce(500),
|
||||
size = {},
|
||||
store = () => {
|
||||
if ('Width' == resizer.mode) {
|
||||
target.style.left = source.offsetWidth + 'px';
|
||||
Local.set(resizer.key+resizer.mode, source.offsetWidth);
|
||||
} else {
|
||||
target.style.top = (4 + source.offsetTop + source.offsetHeight) + 'px';
|
||||
Local.set(resizer.key+resizer.mode, source.offsetHeight);
|
||||
}
|
||||
const value = setTargetPos(resizer.mode),
|
||||
prop = resizer.key + resizer.mode;
|
||||
(value == Local.get(prop)) || Local.set(prop, value);
|
||||
(value == SettingsGet('Resizer' + prop)) || save({['Resizer' + prop]: value});
|
||||
},
|
||||
cssint = s => {
|
||||
let value = getComputedStyle(source, null)[s].replace('px', '');
|
||||
|
|
@ -235,11 +249,6 @@ setLayoutResizer = (source, target, sClientSideKeyName, mode) =>
|
|||
source.layoutResizer.mode = mode;
|
||||
source.layoutResizer.key = sClientSideKeyName;
|
||||
source.observer && source.observer.observe(source, { box: 'border-box' });
|
||||
if (length) {
|
||||
source.style[mode] = length + 'px';
|
||||
}
|
||||
} else {
|
||||
source.observer && source.observer.disconnect();
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -290,7 +299,6 @@ populateMessageBody = (oMessage, preload) => {
|
|||
}
|
||||
*/
|
||||
isNew || message.revivePropertiesFromJson(json);
|
||||
addRequestedMessage(message.folder, message.uid);
|
||||
if (messagesDom) {
|
||||
let id = 'rl-msg-' + message.hash.replace(/[^a-zA-Z0-9]/g, ''),
|
||||
body = elementById(id);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { AbstractCollectionModel } from 'Model/AbstractCollection';
|
|||
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
|
||||
import { isArray, getKeyByValue, forEachObjectEntry, b64EncodeJSONSafe } from 'Common/Utils';
|
||||
import { ClientSideKeyNameExpandedFolders, FolderType, FolderMetadataKeys } from 'Common/EnumsUser';
|
||||
import { getFolderFromCacheList, setFolder, setFolderInboxName, setFolderHash } from 'Common/Cache';
|
||||
import { getFolderFromCacheList, setFolder, setFolderInboxName } from 'Common/Cache';
|
||||
import { Settings, SettingsGet, fireEvent } from 'Common/Globals';
|
||||
|
||||
import * as Local from 'Storage/Client';
|
||||
|
|
@ -138,13 +138,24 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
|||
let oCacheFolder = getFolderFromCacheList(oFolder.FullName),
|
||||
type = FolderType[getKeyByValue(SystemFolders, oFolder.FullName)];
|
||||
|
||||
if (!oCacheFolder) {
|
||||
if (oCacheFolder) {
|
||||
// oCacheFolder.revivePropertiesFromJson(oFolder);
|
||||
if (oFolder.Hash) {
|
||||
oCacheFolder.hash = oFolder.Hash;
|
||||
}
|
||||
if (null != oFolder.totalEmails) {
|
||||
oCacheFolder.totalEmails(oFolder.totalEmails);
|
||||
}
|
||||
if (null != oFolder.unreadEmails) {
|
||||
oCacheFolder.unreadEmails(oFolder.unreadEmails);
|
||||
}
|
||||
} else {
|
||||
oCacheFolder = FolderModel.reviveFromJson(oFolder);
|
||||
if (!oCacheFolder)
|
||||
return null;
|
||||
|
||||
if (1 == type) {
|
||||
oCacheFolder.type(FolderType.Inbox);
|
||||
oCacheFolder.type(type);
|
||||
setFolderInboxName(oFolder.FullName);
|
||||
}
|
||||
setFolder(oCacheFolder);
|
||||
|
|
@ -158,19 +169,6 @@ export class FolderCollectionModel extends AbstractCollectionModel
|
|||
|| !isArray(expandedFolders)
|
||||
|| !expandedFolders.includes(oCacheFolder.fullName));
|
||||
|
||||
if (oFolder.Extended) {
|
||||
if (oFolder.Extended.Hash) {
|
||||
setFolderHash(oCacheFolder.fullName, oFolder.Extended.Hash);
|
||||
}
|
||||
|
||||
if (null != oFolder.Extended.MessageCount) {
|
||||
oCacheFolder.messageCountAll(oFolder.Extended.MessageCount);
|
||||
}
|
||||
|
||||
if (null != oFolder.Extended.MessageUnseenCount) {
|
||||
oCacheFolder.messageCountUnread(oFolder.Extended.MessageUnseenCount);
|
||||
}
|
||||
}
|
||||
return oCacheFolder;
|
||||
});
|
||||
|
||||
|
|
@ -241,8 +239,9 @@ export class FolderModel extends AbstractModel {
|
|||
|
||||
this.exists = true;
|
||||
|
||||
// this.hash = '';
|
||||
// this.uidNext = 0;
|
||||
this.hash = '';
|
||||
// this.id = null;
|
||||
this.uidNext = null;
|
||||
|
||||
this.addObservables({
|
||||
name: '',
|
||||
|
|
@ -252,27 +251,71 @@ export class FolderModel extends AbstractModel {
|
|||
focused: false,
|
||||
selected: false,
|
||||
edited: false,
|
||||
subscribed: true,
|
||||
isSubscribed: true,
|
||||
checkable: false, // Check for new messages
|
||||
askDelete: false,
|
||||
|
||||
nameForEdit: '',
|
||||
errorMsg: '',
|
||||
|
||||
privateMessageCountAll: 0,
|
||||
privateMessageCountUnread: 0,
|
||||
totalEmailsValue: 0,
|
||||
unreadEmailsValue: 0,
|
||||
|
||||
kolabType: null,
|
||||
|
||||
collapsed: true
|
||||
collapsed: true,
|
||||
|
||||
tagsAllowed: false
|
||||
});
|
||||
|
||||
this.flags = ko.observableArray();
|
||||
this.permanentFlags = ko.observableArray();
|
||||
|
||||
this.addSubscribables({
|
||||
kolabType: sValue => this.metadata[FolderMetadataKeys.KolabFolderType] = sValue
|
||||
kolabType: sValue => this.metadata[FolderMetadataKeys.KolabFolderType] = sValue,
|
||||
permanentFlags: aValue => this.tagsAllowed(aValue.includes('\\*'))
|
||||
});
|
||||
|
||||
this.subFolders = ko.observableArray(new FolderCollectionModel);
|
||||
this.actionBlink = ko.observable(false).extend({ falseTimeout: 1000 });
|
||||
|
||||
this.totalEmails = koComputable({
|
||||
read: this.totalEmailsValue,
|
||||
write: (iValue) => {
|
||||
if (isPosNumeric(iValue)) {
|
||||
this.totalEmailsValue(iValue);
|
||||
} else {
|
||||
this.totalEmailsValue.valueHasMutated();
|
||||
}
|
||||
}
|
||||
})
|
||||
.extend({ notify: 'always' });
|
||||
|
||||
this.unreadEmails = koComputable({
|
||||
read: this.unreadEmailsValue,
|
||||
write: (value) => {
|
||||
if (isPosNumeric(value)) {
|
||||
this.unreadEmailsValue(value);
|
||||
} else {
|
||||
this.unreadEmailsValue.valueHasMutated();
|
||||
}
|
||||
}
|
||||
})
|
||||
.extend({ notify: 'always' });
|
||||
/*
|
||||
https://www.rfc-editor.org/rfc/rfc8621.html#section-2
|
||||
"myRights": {
|
||||
"mayAddItems": true,
|
||||
"mayRename": false,
|
||||
"maySubmit": true,
|
||||
"mayDelete": false,
|
||||
"maySetKeywords": true,
|
||||
"mayRemoveItems": true,
|
||||
"mayCreateChild": true,
|
||||
"maySetSeen": true,
|
||||
"mayReadItems": true
|
||||
},
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -303,30 +346,6 @@ export class FolderModel extends AbstractModel {
|
|||
|
||||
type && 'mail' != type && folder.kolabType(type);
|
||||
|
||||
folder.messageCountAll = koComputable({
|
||||
read: folder.privateMessageCountAll,
|
||||
write: (iValue) => {
|
||||
if (isPosNumeric(iValue)) {
|
||||
folder.privateMessageCountAll(iValue);
|
||||
} else {
|
||||
folder.privateMessageCountAll.valueHasMutated();
|
||||
}
|
||||
}
|
||||
})
|
||||
.extend({ notify: 'always' });
|
||||
|
||||
folder.messageCountUnread = koComputable({
|
||||
read: folder.privateMessageCountUnread,
|
||||
write: (value) => {
|
||||
if (isPosNumeric(value)) {
|
||||
folder.privateMessageCountUnread(value);
|
||||
} else {
|
||||
folder.privateMessageCountUnread.valueHasMutated();
|
||||
}
|
||||
}
|
||||
})
|
||||
.extend({ notify: 'always' });
|
||||
|
||||
folder.addComputables({
|
||||
|
||||
isInbox: () => FolderType.Inbox === folder.type(),
|
||||
|
|
@ -336,7 +355,7 @@ export class FolderModel extends AbstractModel {
|
|||
|
||||
hasVisibleSubfolders: () => !!folder.subFolders().find(folder => folder.visible()),
|
||||
|
||||
hasSubscriptions: () => folder.subscribed() | !!folder.subFolders().find(
|
||||
hasSubscriptions: () => folder.isSubscribed() | !!folder.subFolders().find(
|
||||
oFolder => {
|
||||
const subscribed = oFolder.hasSubscriptions();
|
||||
return !oFolder.isSystemFolder() && subscribed;
|
||||
|
|
@ -359,21 +378,21 @@ export class FolderModel extends AbstractModel {
|
|||
* - hasVisibleSubfolders()
|
||||
* Or when all below conditions are true:
|
||||
* - selectable()
|
||||
* - subscribed() OR hideUnsubscribed = false
|
||||
* - isSubscribed() OR hideUnsubscribed = false
|
||||
* - FolderType.User
|
||||
* - not kolabType()
|
||||
*/
|
||||
visible: () => {
|
||||
const selectable = folder.canBeSelected(),
|
||||
visible = (folder.subscribed() | !SettingsUserStore.hideUnsubscribed()) && selectable;
|
||||
visible = (folder.isSubscribed() | !SettingsUserStore.hideUnsubscribed()) && selectable;
|
||||
return folder.hasVisibleSubfolders() | visible;
|
||||
},
|
||||
|
||||
hidden: () => !folder.selectable() && (folder.isSystemFolder() | !folder.hasVisibleSubfolders()),
|
||||
|
||||
printableUnreadCount: () => {
|
||||
const count = folder.messageCountAll(),
|
||||
unread = folder.messageCountUnread(),
|
||||
const count = folder.totalEmails(),
|
||||
unread = folder.unreadEmails(),
|
||||
type = folder.type();
|
||||
|
||||
if (count) {
|
||||
|
|
@ -413,7 +432,7 @@ export class FolderModel extends AbstractModel {
|
|||
return '';
|
||||
},
|
||||
|
||||
hasUnreadMessages: () => 0 < folder.messageCountUnread() && folder.printableUnreadCount(),
|
||||
hasUnreadMessages: () => 0 < folder.unreadEmails() && folder.printableUnreadCount(),
|
||||
|
||||
hasSubscribedUnreadMessagesSubfolders: () =>
|
||||
!!folder.subFolders().find(
|
||||
|
|
@ -428,7 +447,7 @@ export class FolderModel extends AbstractModel {
|
|||
|
||||
edited: value => value && folder.nameForEdit(folder.name()),
|
||||
|
||||
messageCountUnread: unread => {
|
||||
unreadEmails: unread => {
|
||||
if (FolderType.Inbox === folder.type()) {
|
||||
fireEvent('mailbox.inbox-unread-count', unread);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ import { AbstractModel } from 'Knoin/AbstractModel';
|
|||
|
||||
import PreviewHTML from 'Html/PreviewMessage.html';
|
||||
|
||||
//import { MessageFlagsCache } from 'Common/Cache';
|
||||
import Remote from 'Remote/User/Fetch';
|
||||
|
||||
const
|
||||
// eslint-disable-next-line max-len
|
||||
url = /(^|[\s\n]|\/?>)(https:\/\/[-A-Z0-9+\u0026\u2019#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026#/%=~()_|])/gi,
|
||||
|
|
@ -32,6 +35,60 @@ const
|
|||
return result;
|
||||
},
|
||||
|
||||
ignoredTags = [
|
||||
// rfc5788
|
||||
'$forwarded',
|
||||
'$mdnsent',
|
||||
'$submitpending',
|
||||
'$submitted',
|
||||
// rfc9051
|
||||
'$junk',
|
||||
'$notjunk',
|
||||
'$phishing',
|
||||
// Mailo
|
||||
'sent',
|
||||
// KMail
|
||||
'$attachment',
|
||||
'$encrypted',
|
||||
'$error',
|
||||
'$ignored',
|
||||
'$invitation',
|
||||
'$queued',
|
||||
'$replied',
|
||||
'$sent',
|
||||
'$signed',
|
||||
'$todo',
|
||||
'$watched',
|
||||
// GMail
|
||||
'$replied',
|
||||
'$attachment',
|
||||
'$notphishing',
|
||||
'junk',
|
||||
'nonjunk',
|
||||
// Others
|
||||
'$readreceipt'
|
||||
],
|
||||
|
||||
toggleTag = (message, keyword) => {
|
||||
const lower = keyword.toLowerCase(),
|
||||
isSet = message.flags().includes(lower);
|
||||
Remote.request('MessageSetKeyword', iError => {
|
||||
if (!iError) {
|
||||
if (isSet) {
|
||||
message.flags.remove(lower);
|
||||
} else {
|
||||
message.flags.push(lower);
|
||||
}
|
||||
// MessageFlagsCache.setFor(message.folder, message.uid, message.flags());
|
||||
}
|
||||
}, {
|
||||
Folder: message.folder,
|
||||
Uids: message.uid,
|
||||
Keyword: keyword,
|
||||
SetAction: isSet ? 0 : 1
|
||||
})
|
||||
},
|
||||
|
||||
replyHelper = (emails, unic, localEmails) => {
|
||||
emails.forEach(email => {
|
||||
if (undefined === unic[email.email]) {
|
||||
|
|
@ -99,9 +156,33 @@ export class MessageModel extends AbstractModel {
|
|||
|
||||
isUnseen: () => !this.flags().includes('\\seen'),
|
||||
isFlagged: () => this.flags().includes('\\flagged'),
|
||||
isReadReceipt: () => this.flags().includes('$mdnsent')
|
||||
isReadReceipt: () => this.flags().includes('$mdnsent'),
|
||||
// isJunk: () => this.flags().includes('$junk') && !this.flags().includes('$nonjunk'),
|
||||
// isPhishing: () => this.flags().includes('$phishing')
|
||||
// isPhishing: () => this.flags().includes('$phishing'),
|
||||
|
||||
tagsToHTML: () => this.flags().map(value =>
|
||||
('\\' == value[0] || ignoredTags.includes(value))
|
||||
? ''
|
||||
: '<span class="focused msgflag-'+value+'">' + i18n('MESSAGE_TAGS/'+value,0,value) + '</span>'
|
||||
).join(' '),
|
||||
|
||||
tagOptions: () => {
|
||||
const tagOptions = [];
|
||||
FolderUserStore.currentFolder().permanentFlags.forEach(value => {
|
||||
let lower = value.toLowerCase();
|
||||
if ('\\' != value[0] && !ignoredTags.includes(lower)) {
|
||||
tagOptions.push({
|
||||
css: 'msgflag-' + lower,
|
||||
value: value,
|
||||
checked: this.flags().includes(lower),
|
||||
label: i18n('MESSAGE_TAGS/'+lower, 0, lower),
|
||||
toggle: (/*obj*/) => toggleTag(this, value)
|
||||
});
|
||||
}
|
||||
});
|
||||
return tagOptions
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -299,7 +380,7 @@ export class MessageModel extends AbstractModel {
|
|||
hasUnseenSubMessage: this.hasUnseenSubMessage(),
|
||||
hasFlaggedSubMessage: this.hasFlaggedSubMessage()
|
||||
}, (key, value) => value && classes.push(key));
|
||||
this.flags().forEach(value => classes.push('flag-'+value));
|
||||
this.flags().forEach(value => classes.push('msgflag-'+value));
|
||||
return classes.join(' ');
|
||||
}
|
||||
|
||||
|
|
@ -545,7 +626,7 @@ export class MessageModel extends AbstractModel {
|
|||
});
|
||||
|
||||
body.querySelectorAll('[data-x-style-url]').forEach(node => {
|
||||
forEachObjectEntry(JSON.parse(node.dataset.xStyleUrl), (name, url) => node.style[name] = "url('" + url + "')");
|
||||
JSON.parse(node.dataset.xStyleUrl).forEach(data => node.style[data[0]] = "url('" + data[1] + "')");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@ import { AbstractCollectionModel } from 'Model/AbstractCollection';
|
|||
import { MessageModel } from 'Model/Message';
|
||||
|
||||
import {
|
||||
MessageFlagsCache,
|
||||
hasNewMessageAndRemoveFromCache
|
||||
MessageFlagsCache
|
||||
} from 'Common/Cache';
|
||||
|
||||
'use strict';
|
||||
|
|
@ -16,15 +15,16 @@ export class MessageCollectionModel extends AbstractCollectionModel
|
|||
this.Filtered
|
||||
this.Folder
|
||||
this.FolderHash
|
||||
this.Limit
|
||||
this.MessageCount
|
||||
this.MessageUnseenCount
|
||||
this.FolderInfo
|
||||
this.totalEmails
|
||||
this.unreadEmails
|
||||
this.MessageResultCount
|
||||
this.UidNext
|
||||
this.ThreadUid
|
||||
this.NewMessages
|
||||
this.Offset
|
||||
this.Limit
|
||||
this.Search
|
||||
this.ThreadUid
|
||||
this.UidNext
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
@ -33,16 +33,10 @@ export class MessageCollectionModel extends AbstractCollectionModel
|
|||
* @returns {MessageCollectionModel}
|
||||
*/
|
||||
static reviveFromJson(object, cached) {
|
||||
let newCount = 0;
|
||||
return super.reviveFromJson(object, message => {
|
||||
message = MessageModel.reviveFromJson(message);
|
||||
if (message) {
|
||||
if (hasNewMessageAndRemoveFromCache(message.folder, message.uid) && 5 >= newCount) {
|
||||
++newCount;
|
||||
}
|
||||
|
||||
message.deleted(false);
|
||||
|
||||
cached ? MessageFlagsCache.initMessage(message) : MessageFlagsCache.store(message);
|
||||
return message;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import { pString, pInt, b64EncodeJSONSafe } from 'Common/Utils';
|
||||
|
||||
import {
|
||||
getFolderHash,
|
||||
getFolderUidNext,
|
||||
getFolderFromCacheList
|
||||
} from 'Common/Cache';
|
||||
|
||||
|
|
@ -25,13 +23,14 @@ class RemoteUserFetch extends AbstractFetchRemote {
|
|||
messageList(fCallback, params, bSilent = false) {
|
||||
const
|
||||
sFolderFullName = pString(params.Folder),
|
||||
folderHash = getFolderHash(sFolderFullName);
|
||||
folder = getFolderFromCacheList(sFolderFullName),
|
||||
folderHash = (folder && folder.hash) || '';
|
||||
|
||||
params = Object.assign({
|
||||
Offset: 0,
|
||||
Limit: SettingsUserStore.messagesPerPage(),
|
||||
Search: '',
|
||||
UidNext: getFolderUidNext(sFolderFullName), // Used to check for new messages
|
||||
UidNext: (folder && folder.uidNext) || 0, // Used to check for new messages
|
||||
Sort: FolderUserStore.sortMode(),
|
||||
Hash: folderHash + SettingsGet('AccountHash')
|
||||
}, params);
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
|
|||
Remote.abort('Folders').post('FolderRename', FolderUserStore.foldersRenaming, {
|
||||
Folder: folder.fullName,
|
||||
NewFolderName: nameToEdit,
|
||||
Subscribe: folder.subscribed() ? 1 : 0
|
||||
Subscribe: folder.isSubscribed() ? 1 : 0
|
||||
})
|
||||
.then(data => {
|
||||
folder.name(nameToEdit/*data.Name*/);
|
||||
|
|
@ -125,7 +125,7 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
|
|||
&& folderToRemove.canBeDeleted()
|
||||
&& folderToRemove.askDelete()
|
||||
) {
|
||||
if (0 < folderToRemove.privateMessageCountAll()) {
|
||||
if (0 < folderToRemove.totalEmails()) {
|
||||
// FolderUserStore.folderListError(getNotification(Notification.CantDeleteNonEmptyFolder));
|
||||
folderToRemove.errorMsg(getNotification(Notification.CantDeleteNonEmptyFolder));
|
||||
} else {
|
||||
|
|
@ -138,7 +138,7 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
|
|||
() => {
|
||||
// folderToRemove.flags.push('\\nonexistent');
|
||||
folderToRemove.selectable(false);
|
||||
// folderToRemove.subscribed(false);
|
||||
// folderToRemove.isSubscribed(false);
|
||||
// folderToRemove.checkable(false);
|
||||
if (!folderToRemove.subFolders.length) {
|
||||
removeFolderFromCacheList(folderToRemove.fullName);
|
||||
|
|
@ -170,12 +170,12 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
|
|||
}
|
||||
|
||||
toggleFolderSubscription(folder) {
|
||||
let subscribe = !folder.subscribed();
|
||||
let subscribe = !folder.isSubscribed();
|
||||
Remote.request('FolderSubscribe', null, {
|
||||
Folder: folder.fullName,
|
||||
Subscribe: subscribe ? 1 : 0
|
||||
});
|
||||
folder.subscribed(subscribe);
|
||||
folder.isSubscribed(subscribe);
|
||||
}
|
||||
|
||||
toggleFolderCheckable(folder) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import ko from 'ko';
|
||||
|
||||
import { SMAudio } from 'Common/Audio';
|
||||
import { SaveSettingsStep } from 'Common/Enums';
|
||||
import { EditorDefaultType, Layout } from 'Common/EnumsUser';
|
||||
import { Settings, SettingsGet } from 'Common/Globals';
|
||||
|
|
@ -35,12 +36,12 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
|||
this.editorDefaultType = SettingsUserStore.editorDefaultType;
|
||||
this.layout = SettingsUserStore.layout;
|
||||
|
||||
this.soundNotification = NotificationUserStore.enableSoundNotification;
|
||||
this.soundNotification = SMAudio.notifications;
|
||||
this.notificationSound = ko.observable(SettingsGet('NotificationSound'));
|
||||
this.notificationSounds = ko.observableArray(SettingsGet('NewMailSounds'));
|
||||
|
||||
this.desktopNotification = NotificationUserStore.enableDesktopNotification;
|
||||
this.isDesktopNotificationAllowed = NotificationUserStore.isDesktopNotificationAllowed;
|
||||
this.desktopNotification = NotificationUserStore.enabled;
|
||||
this.isDesktopNotificationAllowed = NotificationUserStore.allowed;
|
||||
|
||||
this.viewHTML = SettingsUserStore.viewHTML;
|
||||
this.showImages = SettingsUserStore.showImages;
|
||||
|
|
@ -133,11 +134,11 @@ export class UserSettingsGeneral extends AbstractViewSettings {
|
|||
}
|
||||
|
||||
testSoundNotification() {
|
||||
NotificationUserStore.playSoundNotification(true);
|
||||
SMAudio.playNotification(true);
|
||||
}
|
||||
|
||||
testSystemNotification() {
|
||||
NotificationUserStore.displayDesktopNotification('SnappyMail', 'Test notification');
|
||||
NotificationUserStore.display('SnappyMail', 'Test notification');
|
||||
}
|
||||
|
||||
selectLanguage() {
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ export const FolderUserStore = new class {
|
|||
folder.selectable() &&
|
||||
folder.exists &&
|
||||
timeout > folder.expires &&
|
||||
(folder.isSystemFolder() || (folder.subscribed() && (folder.checkable() || !bDisplaySpecSetting)))
|
||||
(folder.isSystemFolder() || (folder.isSubscribed() && (folder.checkable() || !bDisplaySpecSetting)))
|
||||
) {
|
||||
timeouts.push([folder.expires, folder.fullName]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { koComputable } from 'External/ko';
|
||||
|
||||
import { SMAudio } from 'Common/Audio';
|
||||
import { Notification } from 'Common/Enums';
|
||||
import { MessageSetAction } from 'Common/EnumsUser';
|
||||
import { $htmlCL } from 'Common/Globals';
|
||||
|
|
@ -8,12 +9,9 @@ import { addObservablesTo, addComputablesTo } from 'External/ko';
|
|||
|
||||
import {
|
||||
getFolderInboxName,
|
||||
addNewMessageCache,
|
||||
setFolderUidNext,
|
||||
getFolderFromCacheList,
|
||||
setFolderHash,
|
||||
MessageFlagsCache,
|
||||
clearNewMessageCache
|
||||
MessageFlagsCache
|
||||
} from 'Common/Cache';
|
||||
|
||||
import { mailBox } from 'Common/Links';
|
||||
|
|
@ -114,45 +112,40 @@ MessagelistUserStore.hasCheckedOrSelected = koComputable(() =>
|
|||
|| MessagelistUserStore.find(item => item.checked()))
|
||||
).extend({ rateLimit: 50 });
|
||||
|
||||
MessagelistUserStore.initUidNextAndNewMessages = (folder, uidNext, newMessages) => {
|
||||
if (getFolderInboxName() === folder && uidNext) {
|
||||
if (arrayLength(newMessages)) {
|
||||
newMessages.forEach(item => addNewMessageCache(folder, item.Uid));
|
||||
MessagelistUserStore.notifyNewMessages = (folder, newMessages) => {
|
||||
if (getFolderInboxName() === folder && arrayLength(newMessages)) {
|
||||
|
||||
NotificationUserStore.playSoundNotification();
|
||||
SMAudio.playNotification();
|
||||
|
||||
const len = newMessages.length;
|
||||
if (3 < len) {
|
||||
NotificationUserStore.displayDesktopNotification(
|
||||
AccountUserStore.email(),
|
||||
i18n('MESSAGE_LIST/NEW_MESSAGE_NOTIFICATION', {
|
||||
COUNT: len
|
||||
}),
|
||||
{ Url: mailBox(newMessages[0].Folder) }
|
||||
const len = newMessages.length;
|
||||
if (3 < len) {
|
||||
NotificationUserStore.display(
|
||||
AccountUserStore.email(),
|
||||
i18n('MESSAGE_LIST/NEW_MESSAGE_NOTIFICATION', {
|
||||
COUNT: len
|
||||
}),
|
||||
{ Url: mailBox(newMessages[0].Folder) }
|
||||
);
|
||||
} else {
|
||||
newMessages.forEach(item => {
|
||||
NotificationUserStore.display(
|
||||
EmailCollectionModel.reviveFromJson(item.From).toString(),
|
||||
item.subject,
|
||||
{ Folder: item.Folder, Uid: item.Uid }
|
||||
);
|
||||
} else {
|
||||
newMessages.forEach(item => {
|
||||
NotificationUserStore.displayDesktopNotification(
|
||||
EmailCollectionModel.reviveFromJson(item.From).toString(),
|
||||
item.Subject,
|
||||
{ Folder: item.Folder, Uid: item.Uid }
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setFolderUidNext(folder, uidNext);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean=} bDropPagePosition = false
|
||||
* @param {boolean=} bDropCurrenFolderCache = false
|
||||
* @param {boolean=} bDropCurrentFolderCache = false
|
||||
*/
|
||||
MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrenFolderCache = false) => {
|
||||
MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrentFolderCache = false) => {
|
||||
let iOffset = (MessagelistUserStore.page() - 1) * SettingsUserStore.messagesPerPage();
|
||||
|
||||
if (bDropCurrenFolderCache) {
|
||||
if (bDropCurrentFolderCache) {
|
||||
setFolderHash(FolderUserStore.currentFolderFullName(), '');
|
||||
}
|
||||
|
||||
|
|
@ -186,27 +179,42 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrenFolderCache
|
|||
let unreadCountChange = false;
|
||||
|
||||
const
|
||||
folder = getFolderFromCacheList(collection.Folder);
|
||||
|
||||
folder = getFolderFromCacheList(collection.Folder),
|
||||
folderInfo = collection.FolderInfo;
|
||||
if (folder && !bCached) {
|
||||
// folder.revivePropertiesFromJson(result);
|
||||
folder.expires = Date.now();
|
||||
folder.uidNext = folderInfo.UidNext;
|
||||
folder.hash = collection.FolderHash;
|
||||
|
||||
setFolderHash(collection.Folder, collection.FolderHash);
|
||||
|
||||
if (null != collection.MessageCount) {
|
||||
folder.messageCountAll(collection.MessageCount);
|
||||
if (null != folderInfo.totalEmails) {
|
||||
folder.totalEmails(folderInfo.totalEmails);
|
||||
}
|
||||
|
||||
if (null != collection.MessageUnseenCount) {
|
||||
if (pInt(folder.messageCountUnread()) !== pInt(collection.MessageUnseenCount)) {
|
||||
if (null != folderInfo.unreadEmails) {
|
||||
if (pInt(folder.unreadEmails()) !== pInt(folderInfo.unreadEmails)) {
|
||||
unreadCountChange = true;
|
||||
MessageFlagsCache.clearFolder(folder.fullName);
|
||||
}
|
||||
|
||||
folder.messageCountUnread(collection.MessageUnseenCount);
|
||||
folder.unreadEmails(folderInfo.unreadEmails);
|
||||
}
|
||||
|
||||
MessagelistUserStore.initUidNextAndNewMessages(folder.fullName, collection.UidNext, collection.NewMessages);
|
||||
folder.flags(folderInfo.Flags);
|
||||
let flags = folderInfo.PermanentFlags;
|
||||
if (flags.includes('\\*')) {
|
||||
let i = 6;
|
||||
while (--i) {
|
||||
flags.includes('$label'+i) || flags.push('$label'+i);
|
||||
}
|
||||
}
|
||||
flags.sort((a, b) => {
|
||||
a = a.toUpperCase();
|
||||
b = b.toUpperCase();
|
||||
return (a < b) ? -1 : ((a > b) ? 1 : 0);
|
||||
});
|
||||
folder.permanentFlags(flags);
|
||||
|
||||
MessagelistUserStore.notifyNewMessages(folder.fullName, collection.NewMessages);
|
||||
}
|
||||
|
||||
MessagelistUserStore.count(collection.MessageResultCount);
|
||||
|
|
@ -231,8 +239,6 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrenFolderCache
|
|||
MessagelistUserStore(collection);
|
||||
MessagelistUserStore.isIncomplete(false);
|
||||
|
||||
clearNewMessageCache();
|
||||
|
||||
if (folder && (bCached || unreadCountChange || SettingsUserStore.useThreads())) {
|
||||
rl.app.folderInformation(folder.fullName, collection);
|
||||
}
|
||||
|
|
@ -262,27 +268,25 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrenFolderCache
|
|||
MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => {
|
||||
messages = messages || MessagelistUserStore.listChecked();
|
||||
|
||||
let folder = null,
|
||||
let folder,
|
||||
alreadyUnread = 0,
|
||||
rootUids = messages.map(oMessage => oMessage && oMessage.uid ? oMessage.uid : null)
|
||||
.validUnique(),
|
||||
length = rootUids.length;
|
||||
|
||||
if (sFolderFullName && length) {
|
||||
rootUids.forEach(sSubUid =>
|
||||
alreadyUnread += MessageFlagsCache.storeBySetAction(sFolderFullName, sSubUid, iSetAction)
|
||||
);
|
||||
switch (iSetAction) {
|
||||
case MessageSetAction.SetSeen:
|
||||
length = 0;
|
||||
// fallthrough is intentionally
|
||||
case MessageSetAction.UnsetSeen:
|
||||
rootUids.forEach(sSubUid =>
|
||||
alreadyUnread += MessageFlagsCache.storeBySetAction(sFolderFullName, sSubUid, iSetAction)
|
||||
);
|
||||
|
||||
folder = getFolderFromCacheList(sFolderFullName);
|
||||
if (folder) {
|
||||
folder.messageCountUnread(folder.messageCountUnread() - alreadyUnread + length);
|
||||
folder.unreadEmails(folder.unreadEmails() - alreadyUnread + length);
|
||||
}
|
||||
|
||||
Remote.request('MessageSetSeen', null, {
|
||||
Folder: sFolderFullName,
|
||||
Uids: rootUids.join(','),
|
||||
|
|
@ -292,9 +296,6 @@ MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => {
|
|||
|
||||
case MessageSetAction.SetFlag:
|
||||
case MessageSetAction.UnsetFlag:
|
||||
rootUids.forEach(sSubUid =>
|
||||
MessageFlagsCache.storeBySetAction(sFolderFullName, sSubUid, iSetAction)
|
||||
);
|
||||
Remote.request('MessageSetFlagged', null, {
|
||||
Folder: sFolderFullName,
|
||||
Uids: rootUids.join(','),
|
||||
|
|
@ -334,26 +335,31 @@ MessagelistUserStore.removeMessagesFromList = (
|
|||
|
||||
messages.forEach(item => item && item.isUnseen() && ++unseenCount);
|
||||
|
||||
if (fromFolder && !copy) {
|
||||
fromFolder.messageCountAll(
|
||||
0 <= fromFolder.messageCountAll() - uidForRemove.length ? fromFolder.messageCountAll() - uidForRemove.length : 0
|
||||
);
|
||||
|
||||
if (0 < unseenCount) {
|
||||
fromFolder.messageCountUnread(
|
||||
0 <= fromFolder.messageCountUnread() - unseenCount ? fromFolder.messageCountUnread() - unseenCount : 0
|
||||
if (fromFolder) {
|
||||
fromFolder.hash = '';
|
||||
if (!copy) {
|
||||
fromFolder.totalEmails(
|
||||
0 <= fromFolder.totalEmails() - uidForRemove.length ? fromFolder.totalEmails() - uidForRemove.length : 0
|
||||
);
|
||||
|
||||
if (0 < unseenCount) {
|
||||
fromFolder.unreadEmails(
|
||||
0 <= fromFolder.unreadEmails() - unseenCount ? fromFolder.unreadEmails() - unseenCount : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toFolder) {
|
||||
toFolder.hash = '';
|
||||
|
||||
if (trashFolder === toFolder.fullName || spamFolder === toFolder.fullName) {
|
||||
unseenCount = 0;
|
||||
}
|
||||
|
||||
toFolder.messageCountAll(toFolder.messageCountAll() + uidForRemove.length);
|
||||
toFolder.totalEmails(toFolder.totalEmails() + uidForRemove.length);
|
||||
if (0 < unseenCount) {
|
||||
toFolder.messageCountUnread(toFolder.messageCountUnread() + unseenCount);
|
||||
toFolder.unreadEmails(toFolder.unreadEmails() + unseenCount);
|
||||
}
|
||||
|
||||
toFolder.actionBlink(true);
|
||||
|
|
@ -378,14 +384,6 @@ MessagelistUserStore.removeMessagesFromList = (
|
|||
}
|
||||
}
|
||||
|
||||
if (fromFolderFullName) {
|
||||
setFolderHash(fromFolderFullName, '');
|
||||
}
|
||||
|
||||
if (toFolderFullName) {
|
||||
setFolderHash(toFolderFullName, '');
|
||||
}
|
||||
|
||||
if (MessagelistUserStore.threadUid()) {
|
||||
if (
|
||||
messageList.length &&
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { SMAudio } from 'Common/Audio';
|
||||
import * as Links from 'Common/Links';
|
||||
import { addObservablesTo } from 'External/ko';
|
||||
import { fireEvent } from 'Common/Globals';
|
||||
|
|
@ -37,36 +36,24 @@ if (WorkerNotifications && ServiceWorkerRegistration && ServiceWorkerRegistratio
|
|||
export const NotificationUserStore = new class {
|
||||
constructor() {
|
||||
addObservablesTo(this, {
|
||||
enableSoundNotification: false,
|
||||
|
||||
enableDesktopNotification: false,/*.extend({ notify: 'always' })*/
|
||||
|
||||
isDesktopNotificationAllowed: !NotificationsDenied()
|
||||
enabled: false,/*.extend({ notify: 'always' })*/
|
||||
allowed: !NotificationsDenied()
|
||||
});
|
||||
|
||||
this.enableDesktopNotification.subscribe(value => {
|
||||
this.enabled.subscribe(value => {
|
||||
DesktopNotifications = !!value;
|
||||
if (value && HTML5Notification && !NotificationsGranted()) {
|
||||
HTML5Notification.requestPermission(() =>
|
||||
this.isDesktopNotificationAllowed(!NotificationsDenied())
|
||||
this.allowed(!NotificationsDenied())
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Used with SoundNotification setting
|
||||
*/
|
||||
playSoundNotification(skipSetting) {
|
||||
if (skipSetting ? true : this.enableSoundNotification()) {
|
||||
SMAudio.playNotification();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used with DesktopNotifications setting
|
||||
*/
|
||||
displayDesktopNotification(title, text, messageData, imageSrc) {
|
||||
display(title, text, messageData, imageSrc) {
|
||||
if (DesktopNotifications && NotificationsGranted()) {
|
||||
const options = {
|
||||
body: text,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
@import "User/Identity.less";
|
||||
@import "User/AdvancedSearch.less";
|
||||
@import "User/Attachments.less";
|
||||
@import "User/MessageFlags.less";
|
||||
@import "User/MessageList.less";
|
||||
@import "User/MessageView.less";
|
||||
@import "User/Contacts.less";
|
||||
|
|
|
|||
|
|
@ -142,6 +142,13 @@ html.rl-side-preview-pane {
|
|||
}
|
||||
}
|
||||
|
||||
#top-system-dropdown-id::after,
|
||||
#move-dropdown-id.dropdown-toggle::after,
|
||||
#button-add-prop-dropdown-id::after {
|
||||
content: '▼';
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
.dropdown-menu * + .dividerbar {
|
||||
margin-top: 9px;
|
||||
padding-top: 9px;
|
||||
|
|
@ -166,9 +173,6 @@ html.rl-side-preview-pane {
|
|||
|
||||
.btn-group.open .btn.btn-transparent.dropdown-toggle {
|
||||
color: #BD362F;
|
||||
.caret {
|
||||
border-top-color: #BD362F;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-group > .btn.single {
|
||||
|
|
|
|||
26
dev/Styles/User/MessageFlags.less
Normal file
26
dev/Styles/User/MessageFlags.less
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/* RFC8457 */
|
||||
.msgflag-\$important .checkboxMessage { background-color: #F00; color: #fff; }
|
||||
.msgflag-\$important.focused { background-color: rgba(255, 0, 0, 0.30); }
|
||||
.msgflag-\$important:not(.focused) { color: #F00; }
|
||||
|
||||
/* KMail tags */
|
||||
.msgflag-\$todo .checkboxMessage { background-color: #00F; color: #fff; }
|
||||
.msgflag-\$todo.focused { background-color: rgba( 64, 64, 255, 0.30); }
|
||||
.msgflag-\$todo:not(.focused) { color: #33F; }
|
||||
|
||||
/* Thunderbird labels */
|
||||
.msgflag-\$label5 .checkboxMessage { background-color: #808; color: #fff; }
|
||||
.msgflag-\$label4 .checkboxMessage { background-color: #00F; color: #fff; }
|
||||
.msgflag-\$label3 .checkboxMessage { background-color: #080; color: #fff; }
|
||||
.msgflag-\$label2 .checkboxMessage { background-color: #FA0; color: #fff; }
|
||||
.msgflag-\$label1 .checkboxMessage { background-color: #F00; color: #fff; }
|
||||
.msgflag-\$label5.focused { background-color: rgba(255, 0, 255, 0.30); }
|
||||
.msgflag-\$label4.focused { background-color: rgba( 64, 64, 255, 0.30); }
|
||||
.msgflag-\$label3.focused { background-color: rgba( 0, 255, 0, 0.30); }
|
||||
.msgflag-\$label2.focused { background-color: rgba(255, 170, 0, 0.30); }
|
||||
.msgflag-\$label1.focused { background-color: rgba(255, 0, 0, 0.30); }
|
||||
.msgflag-\$label5:not(.focused) { color: #939; }
|
||||
.msgflag-\$label4:not(.focused) { color: #33F; }
|
||||
.msgflag-\$label3:not(.focused) { color: #090; }
|
||||
.msgflag-\$label2:not(.focused) { color: #F90; }
|
||||
.msgflag-\$label1:not(.focused) { color: #F00; }
|
||||
|
|
@ -188,7 +188,7 @@ html:not(rl-mobile) {
|
|||
flex-wrap: wrap;
|
||||
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
|
|
@ -211,16 +211,11 @@ html:not(rl-mobile) {
|
|||
border-left-color: #ccc;
|
||||
}
|
||||
|
||||
.importantMark {
|
||||
color:red;
|
||||
margin-right:5px
|
||||
}
|
||||
|
||||
&.flag-\\deleted {
|
||||
opacity: .7;
|
||||
.subjectParent {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.priorityHigh::before {
|
||||
content: '!'; /*❗*/
|
||||
color: red;
|
||||
font-weight: bolder;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&.deleted {
|
||||
|
|
@ -276,7 +271,7 @@ html:not(rl-mobile) {
|
|||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.threads-len span {
|
||||
.threads-len {
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 11px;
|
||||
|
|
@ -287,6 +282,9 @@ html:not(rl-mobile) {
|
|||
border-color: #666;
|
||||
}
|
||||
}
|
||||
.threads-len::after {
|
||||
content: ' ›';
|
||||
}
|
||||
|
||||
.replyFlag, .forwardFlag {
|
||||
margin-right: 0.25em;
|
||||
|
|
@ -329,26 +327,41 @@ html:not(rl-mobile) {
|
|||
border-bottom-color: rgba(57, 140, 242, 0.2);
|
||||
border-left-color: #398CF2;
|
||||
z-index: 101;
|
||||
|
||||
+ .messageListItem {
|
||||
border-bottom-color: rgba(57, 140, 242, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.flagParent {
|
||||
padding: 0 10px 0 5px;
|
||||
}
|
||||
&.flag-\\flagged .flagParent::after,
|
||||
.flagParent::after {
|
||||
content: '☆'; /*⚐*/
|
||||
}
|
||||
&.msgflag-\\flagged .flagParent::after,
|
||||
&.hasFlaggedSubMessage .flagParent::after {
|
||||
color: orange;
|
||||
content: '★'; /*⚑*/
|
||||
}
|
||||
&:not(.flag-\\flagged):not(.hasFlaggedSubMessage) .flagParent::after {
|
||||
content: '☆'; /*⚐*/
|
||||
}
|
||||
&:not(.flag-\\flagged):not(.hasFlaggedSubMessage) .flagParent:not(:hover) {
|
||||
&:not(.msgflag-\\flagged):not(.hasFlaggedSubMessage) .flagParent:not(:hover) {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.senderParent::before {
|
||||
font-family: snappymail;
|
||||
}
|
||||
&.msgflag-\\answered .senderParent::before {
|
||||
content: '← ';
|
||||
}
|
||||
&.msgflag-\$forwarded .senderParent::before {
|
||||
content: '→ ';
|
||||
}
|
||||
&.msgflag-\\answered.msgflag-\$forwarded .senderParent::before {
|
||||
content: '←→ ';
|
||||
}
|
||||
&.msgflag-\\deleted {
|
||||
opacity: .7;
|
||||
.subjectParent {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html.rl-ctrl-key-pressed .messageListItem {
|
||||
|
|
@ -442,35 +455,3 @@ html:not(.rl-mobile):not(.rl-side-preview-pane) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.senderParent::before {
|
||||
font-family: snappymail;
|
||||
}
|
||||
.flag-\\answered .senderParent::before {
|
||||
content: '← ';
|
||||
}
|
||||
.flag-\$forwarded .senderParent::before {
|
||||
content: '→ ';
|
||||
}
|
||||
.flag-\\answered.flag-\$forwarded .senderParent::before {
|
||||
content: '←→ ';
|
||||
}
|
||||
|
||||
/* Thunderbird labels */
|
||||
/*
|
||||
.flag-\$label5 .checkboxMessage { background-color: #808; }
|
||||
.flag-\$label4 .checkboxMessage { background-color: #00F; }
|
||||
.flag-\$label3 .checkboxMessage { background-color: #080; }
|
||||
.flag-\$label2 .checkboxMessage { background-color: #FA0; }
|
||||
.flag-\$label1 .checkboxMessage { background-color: #F00; }
|
||||
*/
|
||||
.messageListItem.flag-\$label5.focused { background-color: rgba(255, 0, 255, 0.30); }
|
||||
.messageListItem.flag-\$label4.focused { background-color: rgba( 64, 64, 255, 0.30); }
|
||||
.messageListItem.flag-\$label3.focused { background-color: rgba( 0, 255, 0, 0.30); }
|
||||
.messageListItem.flag-\$label2.focused { background-color: rgba(255, 170, 0, 0.30); }
|
||||
.messageListItem.flag-\$label1.focused { background-color: rgba(255, 0, 0, 0.30); }
|
||||
.messageListItem.flag-\$label5:not(.focused) { color: #939; }
|
||||
.messageListItem.flag-\$label4:not(.focused) { color: #33F; }
|
||||
.messageListItem.flag-\$label3:not(.focused) { color: #090; }
|
||||
.messageListItem.flag-\$label2:not(.focused) { color: #F90; }
|
||||
.messageListItem.flag-\$label1:not(.focused) { color: #F00; }
|
||||
|
|
|
|||
|
|
@ -90,6 +90,14 @@ html.rl-no-preview-pane {
|
|||
}
|
||||
}
|
||||
|
||||
.messageTags {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.messageTags span span {
|
||||
border: 1px solid #808080;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.messageItemHeader {
|
||||
|
||||
background-color: #f8f8f8;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
#top-system-dropdown-id {
|
||||
overflow: hidden;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-left: 8px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.rl-left-panel-disabled #more-list-dropdown-id + .dropdown-menu {
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ export class ComposePopupView extends AbstractViewPopup {
|
|||
Cc: this.cc(),
|
||||
Bcc: this.bcc(),
|
||||
ReplyTo: this.replyTo(),
|
||||
Subject: this.subject(),
|
||||
subject: this.subject(),
|
||||
DraftInfo: this.aDraftInfo,
|
||||
InReplyTo: this.sInReplyTo,
|
||||
References: this.sReferences,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { i18n, getNotification } from 'Common/Translator';
|
||||
import { setFolderHash } from 'Common/Cache';
|
||||
|
||||
import { MessageUserStore } from 'Stores/User/Message';
|
||||
import { MessagelistUserStore } from 'Stores/User/Messagelist';
|
||||
|
|
@ -43,10 +42,9 @@ export class FolderClearPopupView extends AbstractViewPopup {
|
|||
|
||||
this.clearingProcess(true);
|
||||
|
||||
folderToClear.messageCountAll(0);
|
||||
folderToClear.messageCountUnread(0);
|
||||
|
||||
setFolderHash(folderToClear.fullName, '');
|
||||
folderToClear.totalEmails(0);
|
||||
folderToClear.unreadEmails(0);
|
||||
folderToClear.hash = '';
|
||||
|
||||
Remote.request('FolderClear', iError => {
|
||||
this.clearingProcess(false);
|
||||
|
|
|
|||
|
|
@ -88,11 +88,14 @@ export class LoginUserView extends AbstractViewLogin {
|
|||
}
|
||||
|
||||
submitCommand(self, event) {
|
||||
const email = this.email().trim();
|
||||
this.email(email);
|
||||
|
||||
let form = event.target.form,
|
||||
data = new FormData(form),
|
||||
valid = form.reportValidity() && fireEvent('sm-user-login', data, 1);
|
||||
|
||||
this.emailError(!this.email());
|
||||
this.emailError(!email);
|
||||
this.passwordError(!this.password());
|
||||
this.formError(!valid);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import ko from 'ko';
|
|||
import { Scope } from 'Common/Enums';
|
||||
import { moveAction, addShortcut } from 'Common/Globals';
|
||||
import { mailBox, settings } from 'Common/Links';
|
||||
import { setFolderHash } from 'Common/Cache';
|
||||
//import { setFolderHash } from 'Common/Cache';
|
||||
import { addComputablesTo } from 'External/ko';
|
||||
|
||||
import { AppUserStore } from 'Stores/User/App';
|
||||
|
|
@ -100,11 +100,11 @@ export class MailFolderList extends AbstractViewLeft {
|
|||
if (!SettingsUserStore.usePreviewPane()) {
|
||||
MessageUserStore.message(null);
|
||||
}
|
||||
|
||||
/*
|
||||
if (folder.fullName === FolderUserStore.currentFolderFullName()) {
|
||||
setFolderHash(folder.fullName, '');
|
||||
}
|
||||
|
||||
*/
|
||||
hasher.setHash(
|
||||
mailBox(folder.fullNameHash, 1,
|
||||
(event.target.matches('.flag-icon') && !folder.isFlagged()) ? 'flagged' : ''
|
||||
|
|
|
|||
|
|
@ -24,9 +24,7 @@ import { i18n, initOnStartOrLangChange } from 'Common/Translator';
|
|||
|
||||
import {
|
||||
getFolderFromCacheList,
|
||||
MessageFlagsCache,
|
||||
hasRequestedMessage,
|
||||
addRequestedMessage
|
||||
MessageFlagsCache
|
||||
} from 'Common/Cache';
|
||||
|
||||
import { AppUserStore } from 'Stores/User/App';
|
||||
|
|
@ -61,7 +59,6 @@ export class MailMessageList extends AbstractViewRight {
|
|||
constructor() {
|
||||
super();
|
||||
|
||||
this.bPrefetch = false;
|
||||
this.emptySubjectValue = '';
|
||||
|
||||
this.iGoToUpOrDownTimeout = 0;
|
||||
|
|
@ -487,9 +484,9 @@ export class MailMessageList extends AbstractViewRight {
|
|||
});
|
||||
|
||||
if (iThreadUid) {
|
||||
folder.messageCountUnread(Math.max(0, folder.messageCountUnread() - cnt));
|
||||
folder.unreadEmails(Math.max(0, folder.unreadEmails() - cnt));
|
||||
} else {
|
||||
folder.messageCountUnread(0);
|
||||
folder.unreadEmails(0);
|
||||
}
|
||||
|
||||
MessageFlagsCache.clearFolder(sFolderFullName);
|
||||
|
|
@ -817,35 +814,6 @@ export class MailMessageList extends AbstractViewRight {
|
|||
addShortcut('arrowright', 'meta', Scope.MessageView, ()=>false);
|
||||
|
||||
addShortcut('f', 'meta', Scope.MessageList, ()=>this.advancedSearchClick());
|
||||
|
||||
if (!ThemeStore.isMobile() && SettingsCapa('Prefetch')) {
|
||||
ifvisible.idle(this.prefetchNextTick.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
prefetchNextTick() {
|
||||
if (!this.bPrefetch && !ifvisible.now() && !this.viewModelDom.hidden) {
|
||||
const message = MessagelistUserStore.find(
|
||||
item => item && !hasRequestedMessage(item.folder, item.uid)
|
||||
);
|
||||
if (message) {
|
||||
this.bPrefetch = true;
|
||||
|
||||
addRequestedMessage(message.folder, message.uid);
|
||||
|
||||
Remote.message(
|
||||
iError => {
|
||||
const next = !iError;
|
||||
setTimeout(() => {
|
||||
this.bPrefetch = false;
|
||||
next && this.prefetchNextTick();
|
||||
}, 1000);
|
||||
},
|
||||
message.folder,
|
||||
message.uid
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
advancedSearchClick() {
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@ export class MailMessageView extends AbstractViewRight {
|
|||
)
|
||||
},
|
||||
|
||||
tagsAllowed: () => FolderUserStore.currentFolder() ? FolderUserStore.currentFolder().tagsAllowed() : false,
|
||||
|
||||
messageVisibility: () => !MessageUserStore.loading() && !!currentMessage(),
|
||||
|
||||
canBeRepliedOrForwarded: () => !this.isDraftFolder() && this.messageVisibility(),
|
||||
|
|
@ -575,7 +577,7 @@ export class MailMessageView extends AbstractViewRight {
|
|||
MessageFolder: oMessage.folder,
|
||||
MessageUid: oMessage.uid,
|
||||
ReadReceipt: oMessage.readReceipt(),
|
||||
Subject: i18n('READ_RECEIPT/SUBJECT', { SUBJECT: oMessage.subject() }),
|
||||
subject: i18n('READ_RECEIPT/SUBJECT', { SUBJECT: oMessage.subject() }),
|
||||
Text: i18n('READ_RECEIPT/BODY', { 'READ-RECEIPT': AccountUserStore.email() })
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue