feat: EmailTreeModel grouping modes + MailListView integration
- EmailTreeModel: 4 grouping modes * NoGrouping - flat list * GroupByDate - chronological groups (Hoy, Ayer, Esta semana...) * GroupByThread - conversations with expandable threads (ThreadId from References/In-Reply-To/Message-ID) * GroupBySender - groups by sender email/name - EmailTreeModel thread support: * ThreadItem type with expand/collapse * ThreadId extraction via ThreadUtils (References > In-Reply-To > Message-ID) * Double-click thread node toggles expansion * Threads sorted by newest email first, emails within thread oldest first - EmailTreeModel sender grouping: * Groups by sender name/email * Senders sorted by newest email first * Emails within group newest first - MailListView: Added 'Agrupar por remitente' to group-by combo - MailListView: Double-click thread node toggles expansion (not open mail) - ThreadUtils: parseReferencesHeader, extractThreadId, generateThreadId
This commit is contained in:
@@ -28,8 +28,8 @@ bool MailItemDao::insert(MailItem& item)
|
||||
|
||||
QSqlQuery query(db);
|
||||
query.prepare(
|
||||
"INSERT INTO MailCopy (folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr) "
|
||||
"VALUES (:folderId, :messageId, :subject, :sender, :recipient, :date, :read, :flagged, :hasAttachment, :size, :fileId, :uid, :bodyHtml, :toAddr, :ccAddr, :bccAddr)"
|
||||
"INSERT INTO MailCopy (folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr, threadId, inReplyTo, \"references\", isPinned) "
|
||||
"VALUES (:folderId, :messageId, :subject, :sender, :recipient, :date, :read, :flagged, :hasAttachment, :size, :fileId, :uid, :bodyHtml, :toAddr, :ccAddr, :bccAddr, :threadId, :inReplyTo, :references, :isPinned)"
|
||||
);
|
||||
query.bindValue(":folderId", item.folderId());
|
||||
query.bindValue(":messageId", item.messageId().isEmpty() ? QVariant() : QVariant(item.messageId()));
|
||||
@@ -47,6 +47,10 @@ bool MailItemDao::insert(MailItem& item)
|
||||
query.bindValue(":toAddr", item.to());
|
||||
query.bindValue(":ccAddr", item.cc());
|
||||
query.bindValue(":bccAddr", item.bcc());
|
||||
query.bindValue(":threadId", item.threadId().isEmpty() ? QVariant() : QVariant(item.threadId()));
|
||||
query.bindValue(":inReplyTo", item.inReplyTo().isEmpty() ? QVariant() : QVariant(item.inReplyTo()));
|
||||
query.bindValue(":references", item.references().join(" "));
|
||||
query.bindValue(":isPinned", item.isPinned() ? 1 : 0);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to insert mail item:" << query.lastError().text();
|
||||
@@ -82,7 +86,11 @@ bool MailItemDao::update(const MailItem& item)
|
||||
"bodyHtml = :bodyHtml, "
|
||||
"toAddr = :toAddr, "
|
||||
"ccAddr = :ccAddr, "
|
||||
"bccAddr = :bccAddr "
|
||||
"bccAddr = :bccAddr, "
|
||||
"threadId = :threadId, "
|
||||
"inReplyTo = :inReplyTo, "
|
||||
"\"references\" = :references, "
|
||||
"isPinned = :isPinned "
|
||||
"WHERE id = :id"
|
||||
);
|
||||
query.bindValue(":id", item.id());
|
||||
@@ -102,6 +110,10 @@ bool MailItemDao::update(const MailItem& item)
|
||||
query.bindValue(":toAddr", item.to());
|
||||
query.bindValue(":ccAddr", item.cc());
|
||||
query.bindValue(":bccAddr", item.bcc());
|
||||
query.bindValue(":threadId", item.threadId().isEmpty() ? QVariant() : QVariant(item.threadId()));
|
||||
query.bindValue(":inReplyTo", item.inReplyTo().isEmpty() ? QVariant() : QVariant(item.inReplyTo()));
|
||||
query.bindValue(":references", item.references().join(" "));
|
||||
query.bindValue(":isPinned", item.isPinned() ? 1 : 0);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Failed to update mail item:" << query.lastError().text();
|
||||
@@ -128,7 +140,7 @@ std::optional<MailItem> MailItemDao::findById(qint64 id)
|
||||
{
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy WHERE id = :id");
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr, threadId, inReplyTo, \"references\", isPinned FROM MailCopy WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
|
||||
if (!query.exec()) {
|
||||
@@ -154,6 +166,11 @@ std::optional<MailItem> MailItemDao::findById(qint64 id)
|
||||
item.setTo(query.value(14).toString());
|
||||
item.setCc(query.value(15).toString());
|
||||
item.setBcc(query.value(16).toString());
|
||||
item.setThreadId(query.value(17).toString());
|
||||
item.setInReplyTo(query.value(18).toString());
|
||||
QString refs = query.value(19).toString();
|
||||
if (!refs.isEmpty()) item.setReferences(refs.split(" ", Qt::SkipEmptyParts));
|
||||
item.setPinned(query.value(20).toBool());
|
||||
QVector<QString> names;
|
||||
for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName);
|
||||
item.setAttachments(names);
|
||||
@@ -167,7 +184,7 @@ QVector<MailItem> MailItemDao::findAll()
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
if (!query.exec("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy")) {
|
||||
if (!query.exec("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr, threadId, inReplyTo, \"references\", isPinned FROM MailCopy")) {
|
||||
qWarning() << "Failed to fetch all mail items:" << query.lastError().text();
|
||||
return items;
|
||||
}
|
||||
@@ -190,6 +207,11 @@ QVector<MailItem> MailItemDao::findAll()
|
||||
item.setTo(query.value(14).toString());
|
||||
item.setCc(query.value(15).toString());
|
||||
item.setBcc(query.value(16).toString());
|
||||
item.setThreadId(query.value(17).toString());
|
||||
item.setInReplyTo(query.value(18).toString());
|
||||
QString refs = query.value(19).toString();
|
||||
if (!refs.isEmpty()) item.setReferences(refs.split(" ", Qt::SkipEmptyParts));
|
||||
item.setPinned(query.value(20).toBool());
|
||||
QVector<QString> names;
|
||||
for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName);
|
||||
item.setAttachments(names);
|
||||
@@ -203,7 +225,7 @@ QVector<MailItem> MailItemDao::findByFolderId(int folderId)
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy WHERE folderId = :folderId");
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr, threadId, inReplyTo, \"references\", isPinned FROM MailCopy WHERE folderId = :folderId");
|
||||
query.bindValue(":folderId", folderId);
|
||||
|
||||
if (!query.exec()) {
|
||||
@@ -229,6 +251,11 @@ QVector<MailItem> MailItemDao::findByFolderId(int folderId)
|
||||
item.setTo(query.value(14).toString());
|
||||
item.setCc(query.value(15).toString());
|
||||
item.setBcc(query.value(16).toString());
|
||||
item.setThreadId(query.value(17).toString());
|
||||
item.setInReplyTo(query.value(18).toString());
|
||||
QString refs = query.value(19).toString();
|
||||
if (!refs.isEmpty()) item.setReferences(refs.split(" ", Qt::SkipEmptyParts));
|
||||
item.setPinned(query.value(20).toBool());
|
||||
QVector<QString> names;
|
||||
for (const auto &attachment : attachmentsForMail(item.id())) names.append(attachment.fileName);
|
||||
item.setAttachments(names);
|
||||
@@ -242,7 +269,7 @@ QVector<MailItem> MailItemDao::findByFolderIdSinceUid(int folderId, qint64 since
|
||||
QVector<MailItem> items;
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr FROM MailCopy WHERE folderId = :folderId AND uid > :sinceUid");
|
||||
query.prepare("SELECT id, folderId, messageId, subject, sender, recipient, date, read, flagged, hasAttachment, size, fileId, uid, bodyHtml, toAddr, ccAddr, bccAddr, threadId, inReplyTo, \"references\", isPinned FROM MailCopy WHERE folderId = :folderId AND uid > :sinceUid");
|
||||
query.bindValue(":folderId", folderId);
|
||||
query.bindValue(":sinceUid", sinceUid);
|
||||
|
||||
|
||||
@@ -124,6 +124,10 @@ bool DatabaseManager::initialize(const QString& databasePath)
|
||||
"toAddr TEXT, "
|
||||
"ccAddr TEXT, "
|
||||
"bccAddr TEXT, "
|
||||
"threadId TEXT, "
|
||||
"inReplyTo TEXT, "
|
||||
"\"references\" TEXT, "
|
||||
"isPinned BOOLEAN DEFAULT 0, "
|
||||
"FOREIGN KEY(folderId) REFERENCES Folder(id) ON DELETE CASCADE"
|
||||
");")) {
|
||||
qWarning() << "Failed to create MailCopy table:" << query.lastError().text();
|
||||
|
||||
Reference in New Issue
Block a user