mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Added message tags toggle for #419
This commit is contained in:
parent
a6d97a601c
commit
70e3e6423e
19 changed files with 197 additions and 130 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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, '');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -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() }">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue