Added message tags toggle for #419

This commit is contained in:
the-djmaze 2022-06-03 13:47:04 +02:00
parent a6d97a601c
commit 70e3e6423e
19 changed files with 197 additions and 130 deletions

View file

@ -229,11 +229,11 @@ export class AppUser extends AbstractApp {
setFolderHash(result.Folder, result.Hash); setFolderHash(result.Folder, result.Hash);
folderFromCache.messageCountAll(result.totalEmails); folderFromCache.totalEmails(result.totalEmails);
let unreadCountChange = (folderFromCache.messageCountUnread() !== result.unreadEmails); let unreadCountChange = (folderFromCache.unreadEmails() !== result.unreadEmails);
folderFromCache.messageCountUnread(result.unreadEmails); folderFromCache.unreadEmails(result.unreadEmails);
if (unreadCountChange) { if (unreadCountChange) {
MessageFlagsCache.clearFolder(folderFromCache.fullName); MessageFlagsCache.clearFolder(folderFromCache.fullName);

View file

@ -38,13 +38,13 @@ fetchFolderInformation = (fCallback, folder, list = []) => {
if (!fetch) { if (!fetch) {
list.forEach(messageListItem => { list.forEach(messageListItem => {
if (!MessageFlagsCache.getFor(messageListItem.folder, messageListItem.uid)) { if (!MessageFlagsCache.getFor(folder, messageListItem.uid)) {
uids.push(messageListItem.uid); uids.push(messageListItem.uid);
} }
if (messageListItem.threads.length) { if (messageListItem.threads.length) {
messageListItem.threads.forEach(uid => { messageListItem.threads.forEach(uid => {
if (!MessageFlagsCache.getFor(messageListItem.folder, uid)) { if (!MessageFlagsCache.getFor(folder, uid)) {
uids.push(uid); uids.push(uid);
} }
}); });
@ -148,11 +148,11 @@ folderInformationMultiply = (boot = false) => {
setFolderHash(item.Folder, item.Hash); setFolderHash(item.Folder, item.Hash);
folder.messageCountAll(item.totalEmails); folder.totalEmails(item.totalEmails);
let unreadCountChange = folder.messageCountUnread() !== item.unreadEmails; let unreadCountChange = folder.unreadEmails() !== item.unreadEmails;
folder.messageCountUnread(item.unreadEmails); folder.unreadEmails(item.unreadEmails);
if (unreadCountChange) { if (unreadCountChange) {
MessageFlagsCache.clearFolder(folder.fullName); MessageFlagsCache.clearFolder(folder.fullName);

View file

@ -163,11 +163,11 @@ export class FolderCollectionModel extends AbstractCollectionModel
} }
if (null != oFolder.totalEmails) { if (null != oFolder.totalEmails) {
oCacheFolder.messageCountAll(oFolder.totalEmails); oCacheFolder.totalEmails(oFolder.totalEmails);
} }
if (null != oFolder.unreadEmails) { if (null != oFolder.unreadEmails) {
oCacheFolder.messageCountUnread(oFolder.unreadEmails); oCacheFolder.unreadEmails(oFolder.unreadEmails);
} }
return oCacheFolder; return oCacheFolder;
@ -258,8 +258,8 @@ export class FolderModel extends AbstractModel {
nameForEdit: '', nameForEdit: '',
errorMsg: '', errorMsg: '',
privateMessageCountAll: 0, totalEmailsValue: 0,
privateMessageCountUnread: 0, unreadEmailsValue: 0,
kolabType: null, kolabType: null,
@ -272,6 +272,33 @@ export class FolderModel extends AbstractModel {
this.subFolders = ko.observableArray(new FolderCollectionModel); this.subFolders = ko.observableArray(new FolderCollectionModel);
this.actionBlink = ko.observable(false).extend({ falseTimeout: 1000 }); 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' });
this.flags = ko.observableArray();
this.permanentFlags = ko.observableArray();
} }
/** /**
@ -302,30 +329,6 @@ export class FolderModel extends AbstractModel {
type && 'mail' != type && folder.kolabType(type); 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({ folder.addComputables({
isInbox: () => FolderType.Inbox === folder.type(), isInbox: () => FolderType.Inbox === folder.type(),
@ -371,8 +374,8 @@ export class FolderModel extends AbstractModel {
hidden: () => !folder.selectable() && (folder.isSystemFolder() | !folder.hasVisibleSubfolders()), hidden: () => !folder.selectable() && (folder.isSystemFolder() | !folder.hasVisibleSubfolders()),
printableUnreadCount: () => { printableUnreadCount: () => {
const count = folder.messageCountAll(), const count = folder.totalEmails(),
unread = folder.messageCountUnread(), unread = folder.unreadEmails(),
type = folder.type(); type = folder.type();
if (count) { if (count) {
@ -412,7 +415,7 @@ export class FolderModel extends AbstractModel {
return ''; return '';
}, },
hasUnreadMessages: () => 0 < folder.messageCountUnread() && folder.printableUnreadCount(), hasUnreadMessages: () => 0 < folder.unreadEmails() && folder.printableUnreadCount(),
hasSubscribedUnreadMessagesSubfolders: () => hasSubscribedUnreadMessagesSubfolders: () =>
!!folder.subFolders().find( !!folder.subFolders().find(
@ -427,7 +430,7 @@ export class FolderModel extends AbstractModel {
edited: value => value && folder.nameForEdit(folder.name()), edited: value => value && folder.nameForEdit(folder.name()),
messageCountUnread: unread => { unreadEmails: unread => {
if (FolderType.Inbox === folder.type()) { if (FolderType.Inbox === folder.type()) {
fireEvent('mailbox.inbox-unread-count', unread); fireEvent('mailbox.inbox-unread-count', unread);
} }

View file

@ -18,6 +18,8 @@ import { AbstractModel } from 'Knoin/AbstractModel';
import PreviewHTML from 'Html/PreviewMessage.html'; import PreviewHTML from 'Html/PreviewMessage.html';
import Remote from 'Remote/User/Fetch';
const const
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
url = /(^|[\s\n]|\/?>)(https:\/\/[-A-Z0-9+\u0026\u2019#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026#/%=~()_|])/gi, url = /(^|[\s\n]|\/?>)(https:\/\/[-A-Z0-9+\u0026\u2019#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026#/%=~()_|])/gi,
@ -32,6 +34,25 @@ const
return result; return result;
}, },
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);
}
}
}, {
Folder: message.folder,
Uids: message.uid,
Keyword: keyword,
SetAction: isSet ? 0 : 1
})
},
replyHelper = (emails, unic, localEmails) => { replyHelper = (emails, unic, localEmails) => {
emails.forEach(email => { emails.forEach(email => {
if (undefined === unic[email.email]) { if (undefined === unic[email.email]) {
@ -91,6 +112,14 @@ export class MessageModel extends AbstractModel {
this.unsubsribeLinks = ko.observableArray(); this.unsubsribeLinks = ko.observableArray();
this.flags = ko.observableArray(); this.flags = ko.observableArray();
const ignoredTags = [
'$sent',
'$signed',
'$error',
'$queued',
'$forwarded'
];
this.addComputables({ this.addComputables({
attachmentIconClass: () => FileInfo.getAttachmentsIconClass(this.attachments()), attachmentIconClass: () => FileInfo.getAttachmentsIconClass(this.attachments()),
threadsLen: () => this.threads().length, threadsLen: () => this.threads().length,
@ -107,7 +136,25 @@ export class MessageModel extends AbstractModel {
('\\' == value[0] || '$forwarded' == value) ('\\' == value[0] || '$forwarded' == value)
? '' ? ''
: '<span class="focused msgflag-'+value+'">' + i18n('MESSAGE_TAGS/'+value,0,value) + '</span>' : '<span class="focused msgflag-'+value+'">' + i18n('MESSAGE_TAGS/'+value,0,value) + '</span>'
).join(' ') ).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
}
}); });
} }

View file

@ -16,15 +16,16 @@ export class MessageCollectionModel extends AbstractCollectionModel
this.Filtered this.Filtered
this.Folder this.Folder
this.FolderHash this.FolderHash
this.Limit this.FolderInfo
this.totalEmails this.totalEmails
this.unreadEmails this.unreadEmails
this.MessageResultCount this.MessageResultCount
this.UidNext
this.ThreadUid
this.NewMessages this.NewMessages
this.Offset this.Offset
this.Limit
this.Search this.Search
this.ThreadUid
this.UidNext
} }
*/ */

View file

@ -125,7 +125,7 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
&& folderToRemove.canBeDeleted() && folderToRemove.canBeDeleted()
&& folderToRemove.askDelete() && folderToRemove.askDelete()
) { ) {
if (0 < folderToRemove.privateMessageCountAll()) { if (0 < folderToRemove.totalEmails()) {
// FolderUserStore.folderListError(getNotification(Notification.CantDeleteNonEmptyFolder)); // FolderUserStore.folderListError(getNotification(Notification.CantDeleteNonEmptyFolder));
folderToRemove.errorMsg(getNotification(Notification.CantDeleteNonEmptyFolder)); folderToRemove.errorMsg(getNotification(Notification.CantDeleteNonEmptyFolder));
} else { } else {

View file

@ -186,27 +186,29 @@ MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrenFolderCache
let unreadCountChange = false; let unreadCountChange = false;
const const
folder = getFolderFromCacheList(collection.Folder); folder = getFolderFromCacheList(collection.Folder),
folderInfo = collection.FolderInfo;
if (folder && !bCached) { if (folder && !bCached) {
folder.expires = Date.now(); folder.expires = Date.now();
setFolderHash(collection.Folder, collection.FolderHash); setFolderHash(collection.Folder, collection.FolderHash);
if (null != collection.totalEmails) { if (null != folderInfo.totalEmails) {
folder.messageCountAll(collection.totalEmails); folder.totalEmails(folderInfo.totalEmails);
} }
if (null != collection.unreadEmails) { if (null != folderInfo.unreadEmails) {
if (pInt(folder.messageCountUnread()) !== pInt(collection.unreadEmails)) { if (pInt(folder.unreadEmails()) !== pInt(folderInfo.unreadEmails)) {
unreadCountChange = true; unreadCountChange = true;
MessageFlagsCache.clearFolder(folder.fullName); MessageFlagsCache.clearFolder(folder.fullName);
} }
folder.unreadEmails(folderInfo.unreadEmails);
folder.messageCountUnread(collection.unreadEmails);
} }
MessagelistUserStore.initUidNextAndNewMessages(folder.fullName, collection.UidNext, collection.NewMessages); folder.flags(folderInfo.Flags);
folder.permanentFlags(folderInfo.PermanentFlags);
MessagelistUserStore.initUidNextAndNewMessages(folder.fullName, folderInfo.UidNext, collection.NewMessages);
} }
MessagelistUserStore.count(collection.MessageResultCount); MessagelistUserStore.count(collection.MessageResultCount);
@ -280,7 +282,7 @@ MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => {
folder = getFolderFromCacheList(sFolderFullName); folder = getFolderFromCacheList(sFolderFullName);
if (folder) { if (folder) {
folder.messageCountUnread(folder.messageCountUnread() - alreadyUnread + length); folder.unreadEmails(folder.unreadEmails() - alreadyUnread + length);
} }
Remote.request('MessageSetSeen', null, { Remote.request('MessageSetSeen', null, {
@ -335,13 +337,13 @@ MessagelistUserStore.removeMessagesFromList = (
messages.forEach(item => item && item.isUnseen() && ++unseenCount); messages.forEach(item => item && item.isUnseen() && ++unseenCount);
if (fromFolder && !copy) { if (fromFolder && !copy) {
fromFolder.messageCountAll( fromFolder.totalEmails(
0 <= fromFolder.messageCountAll() - uidForRemove.length ? fromFolder.messageCountAll() - uidForRemove.length : 0 0 <= fromFolder.totalEmails() - uidForRemove.length ? fromFolder.totalEmails() - uidForRemove.length : 0
); );
if (0 < unseenCount) { if (0 < unseenCount) {
fromFolder.messageCountUnread( fromFolder.unreadEmails(
0 <= fromFolder.messageCountUnread() - unseenCount ? fromFolder.messageCountUnread() - unseenCount : 0 0 <= fromFolder.unreadEmails() - unseenCount ? fromFolder.unreadEmails() - unseenCount : 0
); );
} }
} }
@ -351,9 +353,9 @@ MessagelistUserStore.removeMessagesFromList = (
unseenCount = 0; unseenCount = 0;
} }
toFolder.messageCountAll(toFolder.messageCountAll() + uidForRemove.length); toFolder.totalEmails(toFolder.totalEmails() + uidForRemove.length);
if (0 < unseenCount) { if (0 < unseenCount) {
toFolder.messageCountUnread(toFolder.messageCountUnread() + unseenCount); toFolder.unreadEmails(toFolder.unreadEmails() + unseenCount);
} }
toFolder.actionBlink(true); toFolder.actionBlink(true);

View file

@ -93,7 +93,7 @@ html.rl-no-preview-pane {
.messageTags { .messageTags {
margin-top: 6px; margin-top: 6px;
} }
.messageTags * * { .messageTags span span {
border: 1px solid #808080; border: 1px solid #808080;
padding: 2px; padding: 2px;
} }

View file

@ -43,8 +43,8 @@ export class FolderClearPopupView extends AbstractViewPopup {
this.clearingProcess(true); this.clearingProcess(true);
folderToClear.messageCountAll(0); folderToClear.totalEmails(0);
folderToClear.messageCountUnread(0); folderToClear.unreadEmails(0);
setFolderHash(folderToClear.fullName, ''); setFolderHash(folderToClear.fullName, '');

View file

@ -487,9 +487,9 @@ export class MailMessageList extends AbstractViewRight {
}); });
if (iThreadUid) { if (iThreadUid) {
folder.messageCountUnread(Math.max(0, folder.messageCountUnread() - cnt)); folder.unreadEmails(Math.max(0, folder.unreadEmails() - cnt));
} else { } else {
folder.messageCountUnread(0); folder.unreadEmails(0);
} }
MessageFlagsCache.clearFolder(sFolderFullName); MessageFlagsCache.clearFolder(sFolderFullName);

View file

@ -103,13 +103,16 @@ trait Folders
* @throws \MailSo\Base\Exceptions\InvalidArgumentException * @throws \MailSo\Base\Exceptions\InvalidArgumentException
* @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Net\Exceptions\Exception
* @throws \MailSo\Imap\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception
*
* https://datatracker.ietf.org/doc/html/rfc9051#section-6.3.11
*/ */
public function FolderStatus(string $sFolderName) : FolderInformation public function FolderStatus(string $sFolderName) : FolderInformation
{ {
$aStatusItems = array( $aStatusItems = array(
FolderResponseStatus::MESSAGES, FolderResponseStatus::MESSAGES,
FolderResponseStatus::UNSEEN, FolderResponseStatus::UNSEEN,
FolderResponseStatus::UIDNEXT FolderResponseStatus::UIDNEXT,
FolderResponseStatus::UIDVALIDITY
); );
if ($this->IsSupported('CONDSTORE')) { if ($this->IsSupported('CONDSTORE')) {
$aStatusItems[] = FolderResponseStatus::HIGHESTMODSEQ; $aStatusItems[] = FolderResponseStatus::HIGHESTMODSEQ;
@ -121,10 +124,14 @@ trait Folders
$aStatusItems[] = FolderResponseStatus::MAILBOXID; $aStatusItems[] = FolderResponseStatus::MAILBOXID;
/* /*
} else if ($this->IsSupported('X-DOVECOT')) { } else if ($this->IsSupported('X-DOVECOT')) {
$aFetchItems[] = 'X-GUID'; $aStatusItems[] = 'X-GUID';
*/ */
} }
/* // STATUS SIZE can take a significant amount of time, therefore not active
if ($this->IsSupported('IMAP4rev2')) {
$aStatusItems[] = FolderResponseStatus::SIZE;
}
*/
$oFolderInfo = $this->oCurrentFolderInfo; $oFolderInfo = $this->oCurrentFolderInfo;
$bReselect = false; $bReselect = false;
$bWritable = false; $bWritable = false;
@ -276,6 +283,9 @@ trait Folders
* @throws \MailSo\Base\Exceptions\InvalidArgumentException * @throws \MailSo\Base\Exceptions\InvalidArgumentException
* @throws \MailSo\Net\Exceptions\Exception * @throws \MailSo\Net\Exceptions\Exception
* @throws \MailSo\Imap\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception
*
* REQUIRED IMAP4rev2 untagged responses: FLAGS, EXISTS, LIST
* REQUIRED IMAP4rev2 OK untagged responses: PERMANENTFLAGS, UIDNEXT, UIDVALIDITY
*/ */
protected function selectOrExamineFolder(string $sFolderName, bool $bIsWritable, bool $bReSelectSameFolders) : FolderInformation protected function selectOrExamineFolder(string $sFolderName, bool $bIsWritable, bool $bReSelectSameFolders) : FolderInformation
{ {
@ -345,9 +355,6 @@ trait Folders
// $oResult->IsWritable = true; // $oResult->IsWritable = true;
} else if ('NOMODSEQ' === $key) { } else if ('NOMODSEQ' === $key) {
// https://datatracker.ietf.org/doc/html/rfc4551#section-3.1.2 // https://datatracker.ietf.org/doc/html/rfc4551#section-3.1.2
} else if ('MAILBOXID' === $key) {
// https://www.rfc-editor.org/rfc/rfc8474#section-4.2
$oResult->MAILBOXID = $oResponse->OptionalResponse[1];
} }
} }

View file

@ -30,4 +30,6 @@ abstract class FolderResponseStatus
const APPENDLIMIT = 'APPENDLIMIT'; const APPENDLIMIT = 'APPENDLIMIT';
// RFC 8474 // RFC 8474
const MAILBOXID = 'MAILBOXID'; const MAILBOXID = 'MAILBOXID';
// RFC 9051 IMAP4rev2
const SIZE = 'SIZE';
} }

View file

@ -30,7 +30,7 @@ abstract class MessageFlag
// https://datatracker.ietf.org/doc/html/rfc3503 // https://datatracker.ietf.org/doc/html/rfc3503
MDNSENT = '$MDNSent', MDNSENT = '$MDNSent',
// https://datatracker.ietf.org/doc/html/rfc8457 // https://datatracker.ietf.org/doc/html/rfc8457
DRAFT = '$Important', IMPORTANT = '$Important',
// https://datatracker.ietf.org/doc/html/rfc5788 // https://datatracker.ietf.org/doc/html/rfc5788
FORWARDED = '$Forwarded', FORWARDED = '$Forwarded',
// https://datatracker.ietf.org/doc/html/rfc9051#section-2.3.2 // https://datatracker.ietf.org/doc/html/rfc9051#section-2.3.2

View file

@ -62,19 +62,27 @@ class FolderInformation implements \JsonSerializable
public function jsonSerialize() public function jsonSerialize()
{ {
return array( $result = array(
'id' => $this->MAILBOXID, 'id' => $this->MAILBOXID,
'Name' => $this->FolderName, 'Name' => $this->FolderName,
'Flags' => $this->Flags, 'Flags' => $this->Flags,
'PermanentFlags' => $this->PermanentFlags, 'PermanentFlags' => $this->PermanentFlags,
/*
'totalEmails' => $this->MESSAGES,
'unreadEmails' => $this->UNSEEN,
'UidNext' => $this->UIDNEXT, 'UidNext' => $this->UIDNEXT,
'UidValidity' => $this->UIDVALIDITY, 'UidValidity' => $this->UIDVALIDITY
'Highestmodseq' => $this->HIGHESTMODSEQ,
'Appendlimit' => $this->APPENDLIMIT,
*/
); );
if (isset($this->MESSAGES)) {
$result['totalEmails'] = $this->MESSAGES;
$result['unreadEmails'] = $this->UNSEEN;
}
if (isset($this->HIGHESTMODSEQ)) {
$result['Highestmodseq'] = $this->HIGHESTMODSEQ;
}
if (isset($this->HIGHESTMODSEQ)) {
$result['Appendlimit'] = $this->APPENDLIMIT;
}
if (isset($this->SIZE)) {
$result['Size'] = $this->SIZE;
}
return $result;
} }
} }

View file

@ -83,7 +83,14 @@ trait Status
* This response also occurs as a result of a CREATE, SELECT or EXAMINE command. * This response also occurs as a result of a CREATE, SELECT or EXAMINE command.
* @var string * @var string
*/ */
$MAILBOXID; $MAILBOXID,
/**
* RFC 9051
* The total size of the mailbox in octets.
* @var int
*/
$SIZE;
public function getStatusItems() : array public function getStatusItems() : array
{ {
@ -150,7 +157,9 @@ trait Status
} }
// SELECT or EXAMINE command // SELECT or EXAMINE command
else if (\is_numeric($oResponse->ResponseList[1]) && \is_string($oResponse->ResponseList[2])) { else if (\is_numeric($oResponse->ResponseList[1]) && \is_string($oResponse->ResponseList[2])) {
$bResult = $this->setStatus($oResponse->ResponseList[2], $oResponse->ResponseList[1]); if ('UNSEEN' !== $oResponse->ResponseList[2]) {
$bResult |= $this->setStatus($oResponse->ResponseList[2], $oResponse->ResponseList[1]);
}
} }
} }

View file

@ -131,8 +131,7 @@ class MailClient
public function MessageSetFlag(string $sFolderName, SequenceSet $oRange, string $sMessageFlag, bool $bSetAction = true, bool $bSkipUnsupportedFlag = false) : void public function MessageSetFlag(string $sFolderName, SequenceSet $oRange, string $sMessageFlag, bool $bSetAction = true, bool $bSkipUnsupportedFlag = false) : void
{ {
if (\count($oRange)) { if (\count($oRange)) {
$oFolderInfo = $this->oImapClient->FolderSelect($sFolderName); if ($this->oImapClient->FolderSelect($sFolderName)->IsFlagSupported($sMessageFlag)) {
if ($oFolderInfo->IsFlagSupported($sMessageFlag)) {
$sStoreAction = $bSetAction ? StoreAction::ADD_FLAGS_SILENT : StoreAction::REMOVE_FLAGS_SILENT; $sStoreAction = $bSetAction ? StoreAction::ADD_FLAGS_SILENT : StoreAction::REMOVE_FLAGS_SILENT;
$this->oImapClient->MessageStoreFlag($oRange, array($sMessageFlag), $sStoreAction); $this->oImapClient->MessageStoreFlag($oRange, array($sMessageFlag), $sStoreAction);
} else if (!$bSkipUnsupportedFlag) { } else if (!$bSkipUnsupportedFlag) {
@ -522,11 +521,11 @@ class MailClient
public function FolderInformation(string $sFolderName, int $iPrevUidNext = 0, SequenceSet $oRange = null) : array public function FolderInformation(string $sFolderName, int $iPrevUidNext = 0, SequenceSet $oRange = null) : array
{ {
list($iCount, $iUnseenCount, $iUidNext, $iHighestModSeq, $iAppendLimit, $sMailboxId) = $this->initFolderValues($sFolderName); list($iCount, $iUnseenCount, $iUidNext, $iHighestModSeq, $iAppendLimit, $sMailboxId) = $this->initFolderValues($sFolderName);
// $oInfo = $this->oImapClient->FolderSelect($sFolderName);
$aFlags = array(); $aFlags = array();
if ($oRange && \count($oRange)) { if ($oRange && \count($oRange)) {
$oInfo = $this->oImapClient->FolderExamine($sFolderName); $oInfo = $this->oImapClient->FolderExamine($sFolderName);
// $oInfo->PermanentFlags
$aFetchResponse = $this->oImapClient->Fetch(array( $aFetchResponse = $this->oImapClient->Fetch(array(
FetchType::UID, FetchType::UID,
@ -545,14 +544,16 @@ class MailClient
return array( return array(
'Folder' => $sFolderName, 'Folder' => $sFolderName,
'Hash' => $this->GenerateFolderHash($sFolderName, $iCount, $iUidNext, $iHighestModSeq),
'totalEmails' => $iCount, 'totalEmails' => $iCount,
'unreadEmails' => $iUnseenCount, 'unreadEmails' => $iUnseenCount,
'UidNext' => $iUidNext, 'UidNext' => $iUidNext,
'MessagesFlags' => $aFlags,
'HighestModSeq' => $iHighestModSeq, 'HighestModSeq' => $iHighestModSeq,
'AppendLimit' => $iAppendLimit, 'AppendLimit' => $iAppendLimit,
'MailboxId' => $sMailboxId, 'MailboxId' => $sMailboxId,
// 'Flags' => $oInfo->Flags,
// 'PermanentFlags' => $oInfo->PermanentFlags,
'Hash' => $this->GenerateFolderHash($sFolderName, $iCount, $iUidNext, $iHighestModSeq),
'MessagesFlags' => $aFlags,
'NewMessages' => $this->getFolderNextMessageInformation($sFolderName, $iPrevUidNext, $iUidNext) 'NewMessages' => $this->getFolderNextMessageInformation($sFolderName, $iPrevUidNext, $iUidNext)
); );
} }
@ -823,21 +824,20 @@ class MailClient
$sSearch = \trim($oParams->sSearch); $sSearch = \trim($oParams->sSearch);
list($iMessageRealCount, $iMessageUnseenCount, $iUidNext, $iHighestModSeq) = $this->initFolderValues($oParams->sFolderName);
// Don't use FolderExamine, else PERMANENTFLAGS is empty
$oInfo = $this->oImapClient->FolderSelect($oParams->sFolderName);
$oMessageCollection = new MessageCollection; $oMessageCollection = new MessageCollection;
$oMessageCollection->FolderName = $oParams->sFolderName; $oMessageCollection->FolderName = $oParams->sFolderName;
$oMessageCollection->FolderInfo = $oInfo;
$oMessageCollection->Offset = $oParams->iOffset; $oMessageCollection->Offset = $oParams->iOffset;
$oMessageCollection->Limit = $oParams->iLimit; $oMessageCollection->Limit = $oParams->iLimit;
$oMessageCollection->Search = $sSearch; $oMessageCollection->Search = $sSearch;
$oMessageCollection->ThreadUid = $oParams->iThreadUid; $oMessageCollection->ThreadUid = $oParams->iThreadUid;
$oMessageCollection->Filtered = '' !== \MailSo\Config::$MessageListPermanentFilter; $oMessageCollection->Filtered = '' !== \MailSo\Config::$MessageListPermanentFilter;
$oMessageCollection->totalEmails = $iMessageRealCount;
$oMessageCollection->unreadEmails = $iMessageUnseenCount; list($iMessageRealCount, $iMessageUnseenCount, $iUidNext, $iHighestModSeq) = $this->initFolderValues($oParams->sFolderName);
// Don't use FolderExamine, else PERMANENTFLAGS is empty in Dovecot
$oInfo = $this->oImapClient->FolderSelect($oParams->sFolderName);
$oInfo->UNSEEN = $iMessageUnseenCount;
$oInfo->HIGHESTMODSEQ = $iHighestModSeq;
$oMessageCollection->FolderInfo = $oInfo;
$aUids = array(); $aUids = array();
$aAllThreads = []; $aAllThreads = [];
@ -858,14 +858,14 @@ class MailClient
} }
$oMessageCollection->FolderHash = $this->GenerateFolderHash( $oMessageCollection->FolderHash = $this->GenerateFolderHash(
$oParams->sFolderName, $iMessageRealCount, $iUidNext, $iHighestModSeq); $oParams->sFolderName, $iMessageRealCount, $iUidNext, $oInfo->HIGHESTMODSEQ ?: 0
);
$oMessageCollection->UidNext = $iUidNext;
if (!$oParams->iThreadUid) if (!$oParams->iThreadUid)
{ {
$oMessageCollection->NewMessages = $this->getFolderNextMessageInformation( $oMessageCollection->NewMessages = $this->getFolderNextMessageInformation(
$oParams->sFolderName, $oParams->iPrevUidNext, $iUidNext); $oParams->sFolderName, $oParams->iPrevUidNext, $iUidNext
);
} }
$bSearch = false; $bSearch = false;
@ -1256,17 +1256,13 @@ class MailClient
*/ */
public function FolderClear(string $sFolderFullName) : self public function FolderClear(string $sFolderFullName) : self
{ {
$oFolderInformation = $this->oImapClient->FolderSelect($sFolderFullName); if (0 < $this->oImapClient->FolderSelect($sFolderFullName)->MESSAGES) {
if (0 < $oFolderInformation->MESSAGES)
{
$this->oImapClient->MessageStoreFlag(new SequenceSet('1:*', false), $this->oImapClient->MessageStoreFlag(new SequenceSet('1:*', false),
array(MessageFlag::DELETED), array(MessageFlag::DELETED),
StoreAction::ADD_FLAGS_SILENT StoreAction::ADD_FLAGS_SILENT
); );
$this->oImapClient->FolderExpunge(); $this->oImapClient->FolderExpunge();
} }
return $this; return $this;
} }
@ -1275,13 +1271,10 @@ class MailClient
*/ */
public function FolderSubscribe(string $sFolderFullName, bool $bSubscribe) : self public function FolderSubscribe(string $sFolderFullName, bool $bSubscribe) : self
{ {
if (!\strlen($sFolderFullName)) if (!\strlen($sFolderFullName)) {
{
throw new \MailSo\Base\Exceptions\InvalidArgumentException; throw new \MailSo\Base\Exceptions\InvalidArgumentException;
} }
$this->oImapClient->{$bSubscribe ? 'FolderSubscribe' : 'FolderUnsubscribe'}($sFolderFullName); $this->oImapClient->{$bSubscribe ? 'FolderSubscribe' : 'FolderUnsubscribe'}($sFolderFullName);
return $this; return $this;
} }

View file

@ -22,16 +22,6 @@ class MessageCollection extends \MailSo\Base\Collection
*/ */
public $FolderHash = ''; public $FolderHash = '';
/**
* @var int
*/
public $totalEmails = 0;
/**
* @var int
*/
public $unreadEmails = 0;
/** /**
* @var int * @var int
*/ */
@ -57,11 +47,6 @@ class MessageCollection extends \MailSo\Base\Collection
*/ */
public $Search = ''; public $Search = '';
/**
* @var int
*/
public $UidNext = 0;
/** /**
* @var int * @var int
*/ */
@ -95,13 +80,10 @@ class MessageCollection extends \MailSo\Base\Collection
public function jsonSerialize() public function jsonSerialize()
{ {
return array_merge(parent::jsonSerialize(), array( return array_merge(parent::jsonSerialize(), array(
'totalEmails' => $this->totalEmails,
'unreadEmails' => $this->unreadEmails,
'MessageResultCount' => $this->MessageResultCount, 'MessageResultCount' => $this->MessageResultCount,
'Folder' => $this->FolderName, 'Folder' => $this->FolderName,
'FolderHash' => $this->FolderHash, 'FolderHash' => $this->FolderHash,
'FolderInfo' => $this->FolderInfo, 'FolderInfo' => $this->FolderInfo,
'UidNext' => $this->UidNext,
'ThreadUid' => $this->ThreadUid, 'ThreadUid' => $this->ThreadUid,
'NewMessages' => $this->NewMessages, 'NewMessages' => $this->NewMessages,
'Filtered' => $this->Filtered, 'Filtered' => $this->Filtered,

View file

@ -411,6 +411,11 @@ trait Messages
return $this->messageSetFlag(MessageFlag::FLAGGED, __FUNCTION__, true); return $this->messageSetFlag(MessageFlag::FLAGGED, __FUNCTION__, true);
} }
public function DoMessageSetKeyword() : array
{
return $this->messageSetFlag($this->GetActionParam('Keyword', ''), __FUNCTION__, true);
}
/** /**
* @throws \MailSo\Base\Exceptions\Exception * @throws \MailSo\Base\Exceptions\Exception
*/ */

View file

@ -66,7 +66,7 @@
<a href="#" tabindex="-1" data-bind="command: editAsNewCommand" data-icon="🖉" data-i18n="MESSAGE/BUTTON_EDIT_AS_NEW"></a> <a href="#" tabindex="-1" data-bind="command: editAsNewCommand" data-icon="🖉" data-i18n="MESSAGE/BUTTON_EDIT_AS_NEW"></a>
</li> </li>
</div> </div>
<div data-bind="attr: { css: isDraftFolder() ? 'dividerbar' : ''}"> <div data-bind="attr: { class: isDraftFolder() ? 'dividerbar' : ''}">
<li role="presentation" data-bind="visible: !isDraftFolder() && !isArchiveFolder() && !isArchiveDisabled()"> <li role="presentation" data-bind="visible: !isDraftFolder() && !isArchiveFolder() && !isArchiveDisabled()">
<a href="#" tabindex="-1" data-bind="command: archiveCommand" data-icon="🗄" data-i18n="GLOBAL/TO_ARCHIVE"></a> <a href="#" tabindex="-1" data-bind="command: archiveCommand" data-icon="🗄" data-i18n="GLOBAL/TO_ARCHIVE"></a>
</li> </li>
@ -198,6 +198,14 @@
<div class="messageTags"> <div class="messageTags">
<span data-i18n="MESSAGE/TAGS"></span>: <span data-i18n="MESSAGE/TAGS"></span>:
<span data-bind="html: message().tagsToHTML()"></span> <span data-bind="html: message().tagsToHTML()"></span>
<div class="btn-group" data-bind="registerBootstrapDropdown: true" style="display: inline-block">
<a class="btn btn-thin btn-transparent dropdown-toggle fontastic" id="tags-dropdown-id" href="#" tabindex="-1"></a>
<ul class="dropdown-menu right-edge" role="menu" aria-labelledby="tags-dropdown-id" data-bind="foreach: message().tagOptions()">
<li role="presentation">
<a href="#" tabindex="-1" data-icon="☐" data-bind="click: toggle, text: label, title: value, attr: { class: css, 'data-icon': checked ? '☑' : '☐' }"></a>
</li>
</ul>
</div>
</div> </div>
</div> </div>
<div id="messageItem" data-bind="css: message().lineAsCss()"> <div id="messageItem" data-bind="css: message().lineAsCss()">
@ -218,7 +226,7 @@
<div class="readReceipt" data-bind="visible: !isDraftOrSentFolder() && '' !== message().readReceipt() && !message().isReadReceipt(), click: readReceipt" <div class="readReceipt" data-bind="visible: !isDraftOrSentFolder() && '' !== message().readReceipt() && !message().isReadReceipt(), click: readReceipt"
data-icon="✉" data-i18n="MESSAGE/BUTTON_NOTIFY_READ_RECEIPT"></div> data-icon="✉" data-i18n="MESSAGE/BUTTON_NOTIFY_READ_RECEIPT"></div>
<div class="attachmentsPlace" data-bind="visible: message().hasAttachments(), css: {'selection-mode' : showAttachmentControls}"> <div class="attachmentsPlace" data-bind="visible: message().hasAttachments(), css: {'selection-mode' : showAttachmentControls}">
<ul class="attachmentList" data-bind="foreach: message() ? message().attachments() : []"> <ul class="attachmentList" data-bind="foreach: message().attachments()">
<li class="attachmentItem" draggable="true" <li class="attachmentItem" draggable="true"
data-bind="visible: !isLinked(), event: { 'dragstart': eventDragStart }, attr: { 'title': fileName }, css: {'checked': checked}"> data-bind="visible: !isLinked(), event: { 'dragstart': eventDragStart }, attr: { 'title': fileName }, css: {'checked': checked}">
<div class="attachmentIconParent" data-bind="css: { 'hasPreview': hasPreview(), 'hasPreplay': hasPreplay(), 'isImage': isImage() }"> <div class="attachmentIconParent" data-bind="css: { 'hasPreview': hasPreview(), 'hasPreplay': hasPreplay(), 'isImage': isImage() }">