mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-30 12:39:20 +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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue