mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
feat: store text deleted messages in db
This commit is contained in:
parent
7caf421074
commit
625f9ef816
20 changed files with 14341 additions and 7849 deletions
|
@ -136,12 +136,12 @@ set(ayugram_files
|
||||||
ayu/ui/context_menu/context_menu.h
|
ayu/ui/context_menu/context_menu.h
|
||||||
ayu/ui/context_menu/menu_item_subtext.cpp
|
ayu/ui/context_menu/menu_item_subtext.cpp
|
||||||
ayu/ui/context_menu/menu_item_subtext.h
|
ayu/ui/context_menu/menu_item_subtext.h
|
||||||
ayu/ui/sections/edited/edited_log_inner.cpp
|
ayu/ui/message_history/history_inner.cpp
|
||||||
ayu/ui/sections/edited/edited_log_inner.h
|
ayu/ui/message_history/history_inner.h
|
||||||
ayu/ui/sections/edited/edited_log_item.cpp
|
ayu/ui/message_history/history_item.cpp
|
||||||
ayu/ui/sections/edited/edited_log_item.h
|
ayu/ui/message_history/history_item.h
|
||||||
ayu/ui/sections/edited/edited_log_section.cpp
|
ayu/ui/message_history/history_section.cpp
|
||||||
ayu/ui/sections/edited/edited_log_section.h
|
ayu/ui/message_history/history_section.h
|
||||||
ayu/ui/boxes/edit_deleted_mark.cpp
|
ayu/ui/boxes/edit_deleted_mark.cpp
|
||||||
ayu/ui/boxes/edit_deleted_mark.h
|
ayu/ui/boxes/edit_deleted_mark.h
|
||||||
ayu/ui/boxes/edit_edited_mark.cpp
|
ayu/ui/boxes/edit_edited_mark.cpp
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
using namespace sqlite_orm;
|
using namespace sqlite_orm;
|
||||||
auto storage = make_storage(
|
auto storage = make_storage(
|
||||||
"./tdata/ayudata.db",
|
"./tdata/ayudata.db",
|
||||||
make_table(
|
make_table<DeletedMessage>(
|
||||||
"DeletedMessage",
|
"DeletedMessage",
|
||||||
make_column("fakeId", &DeletedMessage::fakeId, primary_key().autoincrement()),
|
make_column("fakeId", &DeletedMessage::fakeId, primary_key().autoincrement()),
|
||||||
make_column("userId", &DeletedMessage::userId),
|
make_column("userId", &DeletedMessage::userId),
|
||||||
|
@ -52,7 +52,7 @@ auto storage = make_storage(
|
||||||
make_column("documentAttributesSerialized", &DeletedMessage::documentAttributesSerialized),
|
make_column("documentAttributesSerialized", &DeletedMessage::documentAttributesSerialized),
|
||||||
make_column("mimeType", &DeletedMessage::mimeType)
|
make_column("mimeType", &DeletedMessage::mimeType)
|
||||||
),
|
),
|
||||||
make_table(
|
make_table<EditedMessage>(
|
||||||
"EditedMessage",
|
"EditedMessage",
|
||||||
make_column("fakeId", &EditedMessage::fakeId, primary_key().autoincrement()),
|
make_column("fakeId", &EditedMessage::fakeId, primary_key().autoincrement()),
|
||||||
make_column("userId", &EditedMessage::userId),
|
make_column("userId", &EditedMessage::userId),
|
||||||
|
@ -88,7 +88,7 @@ auto storage = make_storage(
|
||||||
make_column("documentAttributesSerialized", &EditedMessage::documentAttributesSerialized),
|
make_column("documentAttributesSerialized", &EditedMessage::documentAttributesSerialized),
|
||||||
make_column("mimeType", &EditedMessage::mimeType)
|
make_column("mimeType", &EditedMessage::mimeType)
|
||||||
),
|
),
|
||||||
make_table(
|
make_table<DeletedDialog>(
|
||||||
"DeletedDialog",
|
"DeletedDialog",
|
||||||
make_column("fakeId", &DeletedDialog::fakeId, primary_key().autoincrement()),
|
make_column("fakeId", &DeletedDialog::fakeId, primary_key().autoincrement()),
|
||||||
make_column("userId", &DeletedDialog::userId),
|
make_column("userId", &DeletedDialog::userId),
|
||||||
|
@ -100,7 +100,7 @@ auto storage = make_storage(
|
||||||
make_column("flags", &DeletedDialog::flags),
|
make_column("flags", &DeletedDialog::flags),
|
||||||
make_column("entityCreateDate", &DeletedDialog::entityCreateDate)
|
make_column("entityCreateDate", &DeletedDialog::entityCreateDate)
|
||||||
),
|
),
|
||||||
make_table(
|
make_table<RegexFilter>(
|
||||||
"RegexFilter",
|
"RegexFilter",
|
||||||
make_column("id", &RegexFilter::id),
|
make_column("id", &RegexFilter::id),
|
||||||
make_column("text", &RegexFilter::text),
|
make_column("text", &RegexFilter::text),
|
||||||
|
@ -108,13 +108,13 @@ auto storage = make_storage(
|
||||||
make_column("caseInsensitive", &RegexFilter::caseInsensitive),
|
make_column("caseInsensitive", &RegexFilter::caseInsensitive),
|
||||||
make_column("dialogId", &RegexFilter::dialogId)
|
make_column("dialogId", &RegexFilter::dialogId)
|
||||||
),
|
),
|
||||||
make_table(
|
make_table<RegexFilterGlobalExclusion>(
|
||||||
"RegexFilterGlobalExclusion",
|
"RegexFilterGlobalExclusion",
|
||||||
make_column("fakeId", &RegexFilterGlobalExclusion::fakeId, primary_key().autoincrement()),
|
make_column("fakeId", &RegexFilterGlobalExclusion::fakeId, primary_key().autoincrement()),
|
||||||
make_column("dialogId", &RegexFilterGlobalExclusion::dialogId),
|
make_column("dialogId", &RegexFilterGlobalExclusion::dialogId),
|
||||||
make_column("filterId", &RegexFilterGlobalExclusion::filterId)
|
make_column("filterId", &RegexFilterGlobalExclusion::filterId)
|
||||||
),
|
),
|
||||||
make_table(
|
make_table<SpyMessageRead>(
|
||||||
"SpyMessageRead",
|
"SpyMessageRead",
|
||||||
make_column("fakeId", &SpyMessageRead::fakeId, primary_key().autoincrement()),
|
make_column("fakeId", &SpyMessageRead::fakeId, primary_key().autoincrement()),
|
||||||
make_column("userId", &SpyMessageRead::userId),
|
make_column("userId", &SpyMessageRead::userId),
|
||||||
|
@ -122,7 +122,7 @@ auto storage = make_storage(
|
||||||
make_column("messageId", &SpyMessageRead::messageId),
|
make_column("messageId", &SpyMessageRead::messageId),
|
||||||
make_column("entityCreateDate", &SpyMessageRead::entityCreateDate)
|
make_column("entityCreateDate", &SpyMessageRead::entityCreateDate)
|
||||||
),
|
),
|
||||||
make_table(
|
make_table<SpyMessageContentsRead>(
|
||||||
"SpyMessageContentsRead",
|
"SpyMessageContentsRead",
|
||||||
make_column("fakeId", &SpyMessageContentsRead::fakeId, primary_key().autoincrement()),
|
make_column("fakeId", &SpyMessageContentsRead::fakeId, primary_key().autoincrement()),
|
||||||
make_column("userId", &SpyMessageContentsRead::userId),
|
make_column("userId", &SpyMessageContentsRead::userId),
|
||||||
|
@ -135,7 +135,7 @@ auto storage = make_storage(
|
||||||
namespace AyuDatabase {
|
namespace AyuDatabase {
|
||||||
|
|
||||||
void moveCurrentDatabase() {
|
void moveCurrentDatabase() {
|
||||||
auto time = base::unixtime::now();
|
const auto time = base::unixtime::now();
|
||||||
|
|
||||||
if (QFile::exists("./tdata/ayudata.db")) {
|
if (QFile::exists("./tdata/ayudata.db")) {
|
||||||
QFile::rename("./tdata/ayudata.db", QString("./tdata/ayudata_%1.db").arg(time));
|
QFile::rename("./tdata/ayudata.db", QString("./tdata/ayudata_%1.db").arg(time));
|
||||||
|
@ -190,18 +190,22 @@ void addEditedMessage(const EditedMessage &message) {
|
||||||
storage.begin_transaction();
|
storage.begin_transaction();
|
||||||
storage.insert(message);
|
storage.insert(message);
|
||||||
storage.commit();
|
storage.commit();
|
||||||
} catch (std::exception& ex) {
|
} catch (std::exception &ex) {
|
||||||
LOG(("Failed to save edited message for some reason: %1").arg(ex.what()));
|
LOG(("Failed to save edited message for some reason: %1").arg(ex.what()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageId) {
|
std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageId, ID minId, ID maxId, int totalLimit) {
|
||||||
return storage.get_all<EditedMessage>(
|
return storage.get_all<EditedMessage>(
|
||||||
where(
|
where(
|
||||||
c(&EditedMessage::userId) == userId and
|
column<EditedMessage>(&EditedMessage::userId) == userId and
|
||||||
c(&EditedMessage::dialogId) == dialogId and
|
column<EditedMessage>(&EditedMessage::dialogId) == dialogId and
|
||||||
c(&EditedMessage::messageId) == messageId
|
column<EditedMessage>(&EditedMessage::messageId) == messageId and
|
||||||
)
|
(column<EditedMessage>(&EditedMessage::fakeId) > minId or minId == 0) and
|
||||||
|
(column<EditedMessage>(&EditedMessage::fakeId) < maxId or maxId == 0)
|
||||||
|
),
|
||||||
|
order_by(column<EditedMessage>(&EditedMessage::fakeId)).desc(),
|
||||||
|
limit(totalLimit)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,15 +213,50 @@ bool hasRevisions(ID userId, ID dialogId, ID messageId) {
|
||||||
try {
|
try {
|
||||||
return storage.count<EditedMessage>(
|
return storage.count<EditedMessage>(
|
||||||
where(
|
where(
|
||||||
c(&EditedMessage::userId) == userId and
|
column<EditedMessage>(&EditedMessage::userId) == userId and
|
||||||
c(&EditedMessage::dialogId) == dialogId and
|
column<EditedMessage>(&EditedMessage::dialogId) == dialogId and
|
||||||
c(&EditedMessage::messageId) == messageId
|
column<EditedMessage>(&EditedMessage::messageId) == messageId
|
||||||
)
|
)
|
||||||
) > 0;
|
) > 0;
|
||||||
} catch (std::exception& ex) {
|
} catch (std::exception &ex) {
|
||||||
LOG(("Failed to check if message has revisions: %1").arg(ex.what()));
|
LOG(("Failed to check if message has revisions: %1").arg(ex.what()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addDeletedMessage(const DeletedMessage &message) {
|
||||||
|
try {
|
||||||
|
storage.begin_transaction();
|
||||||
|
storage.insert(message);
|
||||||
|
storage.commit();
|
||||||
|
} catch (std::exception &ex) {
|
||||||
|
LOG(("Failed to save edited message for some reason: %1").arg(ex.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DeletedMessage> getDeletedMessages(ID dialogId, ID minId, ID maxId, int totalLimit) {
|
||||||
|
return storage.get_all<DeletedMessage>(
|
||||||
|
where(
|
||||||
|
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId and
|
||||||
|
(column<DeletedMessage>(&DeletedMessage::messageId) > minId or minId == 0) and
|
||||||
|
(column<DeletedMessage>(&DeletedMessage::messageId) < maxId or maxId == 0)
|
||||||
|
),
|
||||||
|
order_by(column<DeletedMessage>(&DeletedMessage::messageId)).desc(),
|
||||||
|
limit(totalLimit)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasDeletedMessages(ID dialogId) {
|
||||||
|
try {
|
||||||
|
return storage.count<DeletedMessage>(
|
||||||
|
where(
|
||||||
|
column<DeletedMessage>(&DeletedMessage::dialogId) == dialogId
|
||||||
|
)
|
||||||
|
) > 0;
|
||||||
|
} catch (std::exception &ex) {
|
||||||
|
LOG(("Failed to check if dialog has deleted message: %1").arg(ex.what()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,11 @@ namespace AyuDatabase {
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
||||||
void addEditedMessage(const EditedMessage &message);
|
void addEditedMessage(const EditedMessage &message);
|
||||||
std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageId);
|
std::vector<EditedMessage> getEditedMessages(ID userId, ID dialogId, ID messageId, ID minId, ID maxId, int totalLimit);
|
||||||
bool hasRevisions(ID userId, ID dialogId, ID messageId);
|
bool hasRevisions(ID userId, ID dialogId, ID messageId);
|
||||||
|
|
||||||
|
void addDeletedMessage(const DeletedMessage &message);
|
||||||
|
std::vector<DeletedMessage> getDeletedMessages(ID dialogId, ID minId, ID maxId, int totalLimit);
|
||||||
|
bool hasDeletedMessages(ID dialogId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
|
|
||||||
#define ID long long
|
#define ID long long
|
||||||
|
|
||||||
template<typename TableName>
|
|
||||||
class AyuMessageBase
|
class AyuMessageBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -49,9 +48,13 @@ public:
|
||||||
std::string mimeType;
|
std::string mimeType;
|
||||||
};
|
};
|
||||||
|
|
||||||
using DeletedMessage = AyuMessageBase<struct DeletedMessageTag>;
|
class DeletedMessage : public AyuMessageBase
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
using EditedMessage = AyuMessageBase<struct EditedMessageTag>;
|
class EditedMessage : public AyuMessageBase
|
||||||
|
{
|
||||||
|
};
|
||||||
|
|
||||||
class DeletedDialog
|
class DeletedDialog
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,7 +24,17 @@
|
||||||
|
|
||||||
namespace AyuMessages {
|
namespace AyuMessages {
|
||||||
|
|
||||||
void map(HistoryMessageEdition &edition, not_null<HistoryItem*> item, EditedMessage &message) {
|
template<typename DerivedMessage>
|
||||||
|
std::vector<AyuMessageBase> convertToBase(const std::vector<DerivedMessage> &messages) {
|
||||||
|
std::vector<AyuMessageBase> based;
|
||||||
|
based.reserve(messages.size());
|
||||||
|
for (const auto &msg : messages) {
|
||||||
|
based.push_back(static_cast<AyuMessageBase>(msg));
|
||||||
|
}
|
||||||
|
return based;
|
||||||
|
}
|
||||||
|
|
||||||
|
void map(not_null<HistoryItem*> item, AyuMessageBase &message) {
|
||||||
message.userId = item->history()->owner().session().userId().bare;
|
message.userId = item->history()->owner().session().userId().bare;
|
||||||
message.dialogId = getDialogIdFromPeer(item->history()->peer);
|
message.dialogId = getDialogIdFromPeer(item->history()->peer);
|
||||||
message.groupedId = item->groupId().value;
|
message.groupedId = item->groupId().value;
|
||||||
|
@ -62,17 +72,21 @@ void map(HistoryMessageEdition &edition, not_null<HistoryItem*> item, EditedMess
|
||||||
|
|
||||||
void addEditedMessage(HistoryMessageEdition &edition, not_null<HistoryItem*> item) {
|
void addEditedMessage(HistoryMessageEdition &edition, not_null<HistoryItem*> item) {
|
||||||
EditedMessage message;
|
EditedMessage message;
|
||||||
map(edition, item, message);
|
map(item, message);
|
||||||
|
|
||||||
|
if (message.text.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AyuDatabase::addEditedMessage(message);
|
AyuDatabase::addEditedMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<EditedMessage> getEditedMessages(not_null<HistoryItem*> item) {
|
std::vector<AyuMessageBase> getEditedMessages(not_null<HistoryItem*> item, ID minId, ID maxId, int totalLimit) {
|
||||||
auto userId = item->history()->owner().session().userId().bare;
|
auto userId = item->history()->owner().session().userId().bare;
|
||||||
auto dialogId = getDialogIdFromPeer(item->history()->peer);
|
auto dialogId = getDialogIdFromPeer(item->history()->peer);
|
||||||
auto msgId = item->id.bare;
|
auto msgId = item->id.bare;
|
||||||
|
|
||||||
return AyuDatabase::getEditedMessages(userId, dialogId, msgId);
|
return convertToBase(AyuDatabase::getEditedMessages(userId, dialogId, msgId, minId, maxId, totalLimit));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasRevisions(not_null<HistoryItem*> item) {
|
bool hasRevisions(not_null<HistoryItem*> item) {
|
||||||
|
@ -83,4 +97,23 @@ bool hasRevisions(not_null<HistoryItem*> item) {
|
||||||
return AyuDatabase::hasRevisions(userId, dialogId, msgId);
|
return AyuDatabase::hasRevisions(userId, dialogId, msgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addDeletedMessage(not_null<HistoryItem*> item) {
|
||||||
|
DeletedMessage message;
|
||||||
|
map(item, message);
|
||||||
|
|
||||||
|
if (message.text.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AyuDatabase::addDeletedMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<AyuMessageBase> getDeletedMessages(not_null<PeerData*> peer, ID minId, ID maxId, int totalLimit) {
|
||||||
|
return convertToBase(AyuDatabase::getDeletedMessages(getDialogIdFromPeer(peer), minId, maxId, totalLimit));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasDeletedMessages(not_null<PeerData*> peer) {
|
||||||
|
return AyuDatabase::hasDeletedMessages(getDialogIdFromPeer(peer));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,11 @@
|
||||||
namespace AyuMessages {
|
namespace AyuMessages {
|
||||||
|
|
||||||
void addEditedMessage(HistoryMessageEdition &edition, not_null<HistoryItem*> item);
|
void addEditedMessage(HistoryMessageEdition &edition, not_null<HistoryItem*> item);
|
||||||
std::vector<EditedMessage> getEditedMessages(not_null<HistoryItem*> item);
|
std::vector<AyuMessageBase> getEditedMessages(not_null<HistoryItem*> item, ID minId, ID maxId, int totalLimit);
|
||||||
bool hasRevisions(not_null<HistoryItem*> item);
|
bool hasRevisions(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
|
void addDeletedMessage(not_null<HistoryItem*> item);
|
||||||
|
std::vector<AyuMessageBase> getDeletedMessages(not_null<PeerData*> peer, ID minId, ID maxId, int totalLimit);
|
||||||
|
bool hasDeletedMessages(not_null<PeerData*> peer);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -146,9 +146,9 @@ extern "C" {
|
||||||
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
** [sqlite3_libversion_number()], [sqlite3_sourceid()],
|
||||||
** [sqlite_version()] and [sqlite_source_id()].
|
** [sqlite_version()] and [sqlite_source_id()].
|
||||||
*/
|
*/
|
||||||
#define SQLITE_VERSION "3.45.3"
|
#define SQLITE_VERSION "3.46.1"
|
||||||
#define SQLITE_VERSION_NUMBER 3045003
|
#define SQLITE_VERSION_NUMBER 3046001
|
||||||
#define SQLITE_SOURCE_ID "2024-04-15 13:34:05 8653b758870e6ef0c98d46b3ace27849054af85da891eb121e9aaa537f1e8355"
|
#define SQLITE_SOURCE_ID "2024-08-13 09:16:08 c9c2ab54ba1f5f46360f1b4f35d849cd3f080e6fc2b6c60e91b16c63f69a1e33"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Run-Time Library Version Numbers
|
** CAPI3REF: Run-Time Library Version Numbers
|
||||||
|
@ -764,11 +764,11 @@ struct sqlite3_file {
|
||||||
** </ul>
|
** </ul>
|
||||||
** xLock() upgrades the database file lock. In other words, xLock() moves the
|
** xLock() upgrades the database file lock. In other words, xLock() moves the
|
||||||
** database file lock in the direction NONE toward EXCLUSIVE. The argument to
|
** database file lock in the direction NONE toward EXCLUSIVE. The argument to
|
||||||
** xLock() is always on of SHARED, RESERVED, PENDING, or EXCLUSIVE, never
|
** xLock() is always one of SHARED, RESERVED, PENDING, or EXCLUSIVE, never
|
||||||
** SQLITE_LOCK_NONE. If the database file lock is already at or above the
|
** SQLITE_LOCK_NONE. If the database file lock is already at or above the
|
||||||
** requested lock, then the call to xLock() is a no-op.
|
** requested lock, then the call to xLock() is a no-op.
|
||||||
** xUnlock() downgrades the database file lock to either SHARED or NONE.
|
** xUnlock() downgrades the database file lock to either SHARED or NONE.
|
||||||
* If the lock is already at or below the requested lock state, then the call
|
** If the lock is already at or below the requested lock state, then the call
|
||||||
** to xUnlock() is a no-op.
|
** to xUnlock() is a no-op.
|
||||||
** The xCheckReservedLock() method checks whether any database connection,
|
** The xCheckReservedLock() method checks whether any database connection,
|
||||||
** either in this process or in some other process, is holding a RESERVED,
|
** either in this process or in some other process, is holding a RESERVED,
|
||||||
|
@ -3305,8 +3305,8 @@ SQLITE_API int sqlite3_set_authorizer(
|
||||||
#define SQLITE_RECURSIVE 33 /* NULL NULL */
|
#define SQLITE_RECURSIVE 33 /* NULL NULL */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Tracing And Profiling Functions
|
** CAPI3REF: Deprecated Tracing And Profiling Functions
|
||||||
** METHOD: sqlite3
|
** DEPRECATED
|
||||||
**
|
**
|
||||||
** These routines are deprecated. Use the [sqlite3_trace_v2()] interface
|
** These routines are deprecated. Use the [sqlite3_trace_v2()] interface
|
||||||
** instead of the routines described here.
|
** instead of the routines described here.
|
||||||
|
@ -6887,6 +6887,12 @@ SQLITE_API int sqlite3_autovacuum_pages(
|
||||||
** The exceptions defined in this paragraph might change in a future
|
** The exceptions defined in this paragraph might change in a future
|
||||||
** release of SQLite.
|
** release of SQLite.
|
||||||
**
|
**
|
||||||
|
** Whether the update hook is invoked before or after the
|
||||||
|
** corresponding change is currently unspecified and may differ
|
||||||
|
** depending on the type of change. Do not rely on the order of the
|
||||||
|
** hook call with regards to the final result of the operation which
|
||||||
|
** triggers the hook.
|
||||||
|
**
|
||||||
** The update hook implementation must not do anything that will modify
|
** The update hook implementation must not do anything that will modify
|
||||||
** the database connection that invoked the update hook. Any actions
|
** the database connection that invoked the update hook. Any actions
|
||||||
** to modify the database connection must be deferred until after the
|
** to modify the database connection must be deferred until after the
|
||||||
|
@ -8357,7 +8363,7 @@ SQLITE_API int sqlite3_test_control(int op, ...);
|
||||||
** The sqlite3_keyword_count() interface returns the number of distinct
|
** The sqlite3_keyword_count() interface returns the number of distinct
|
||||||
** keywords understood by SQLite.
|
** keywords understood by SQLite.
|
||||||
**
|
**
|
||||||
** The sqlite3_keyword_name(N,Z,L) interface finds the N-th keyword and
|
** The sqlite3_keyword_name(N,Z,L) interface finds the 0-based N-th keyword and
|
||||||
** makes *Z point to that keyword expressed as UTF8 and writes the number
|
** makes *Z point to that keyword expressed as UTF8 and writes the number
|
||||||
** of bytes in the keyword into *L. The string that *Z points to is not
|
** of bytes in the keyword into *L. The string that *Z points to is not
|
||||||
** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns
|
** zero-terminated. The sqlite3_keyword_name(N,Z,L) routine returns
|
||||||
|
@ -9936,24 +9942,45 @@ SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int);
|
||||||
** <li value="2"><p>
|
** <li value="2"><p>
|
||||||
** ^(If the sqlite3_vtab_distinct() interface returns 2, that means
|
** ^(If the sqlite3_vtab_distinct() interface returns 2, that means
|
||||||
** that the query planner does not need the rows returned in any particular
|
** that the query planner does not need the rows returned in any particular
|
||||||
** order, as long as rows with the same values in all "aOrderBy" columns
|
** order, as long as rows with the same values in all columns identified
|
||||||
** are adjacent.)^ ^(Furthermore, only a single row for each particular
|
** by "aOrderBy" are adjacent.)^ ^(Furthermore, when two or more rows
|
||||||
** combination of values in the columns identified by the "aOrderBy" field
|
** contain the same values for all columns identified by "colUsed", all but
|
||||||
** needs to be returned.)^ ^It is always ok for two or more rows with the same
|
** one such row may optionally be omitted from the result.)^
|
||||||
** values in all "aOrderBy" columns to be returned, as long as all such rows
|
** The virtual table is not required to omit rows that are duplicates
|
||||||
** are adjacent. ^The virtual table may, if it chooses, omit extra rows
|
** over the "colUsed" columns, but if the virtual table can do that without
|
||||||
** that have the same value for all columns identified by "aOrderBy".
|
** too much extra effort, it could potentially help the query to run faster.
|
||||||
** ^However omitting the extra rows is optional.
|
|
||||||
** This mode is used for a DISTINCT query.
|
** This mode is used for a DISTINCT query.
|
||||||
** <li value="3"><p>
|
** <li value="3"><p>
|
||||||
** ^(If the sqlite3_vtab_distinct() interface returns 3, that means
|
** ^(If the sqlite3_vtab_distinct() interface returns 3, that means the
|
||||||
** that the query planner needs only distinct rows but it does need the
|
** virtual table must return rows in the order defined by "aOrderBy" as
|
||||||
** rows to be sorted.)^ ^The virtual table implementation is free to omit
|
** if the sqlite3_vtab_distinct() interface had returned 0. However if
|
||||||
** rows that are identical in all aOrderBy columns, if it wants to, but
|
** two or more rows in the result have the same values for all columns
|
||||||
** it is not required to omit any rows. This mode is used for queries
|
** identified by "colUsed", then all but one such row may optionally be
|
||||||
|
** omitted.)^ Like when the return value is 2, the virtual table
|
||||||
|
** is not required to omit rows that are duplicates over the "colUsed"
|
||||||
|
** columns, but if the virtual table can do that without
|
||||||
|
** too much extra effort, it could potentially help the query to run faster.
|
||||||
|
** This mode is used for queries
|
||||||
** that have both DISTINCT and ORDER BY clauses.
|
** that have both DISTINCT and ORDER BY clauses.
|
||||||
** </ol>
|
** </ol>
|
||||||
**
|
**
|
||||||
|
** <p>The following table summarizes the conditions under which the
|
||||||
|
** virtual table is allowed to set the "orderByConsumed" flag based on
|
||||||
|
** the value returned by sqlite3_vtab_distinct(). This table is a
|
||||||
|
** restatement of the previous four paragraphs:
|
||||||
|
**
|
||||||
|
** <table border=1 cellspacing=0 cellpadding=10 width="90%">
|
||||||
|
** <tr>
|
||||||
|
** <td valign="top">sqlite3_vtab_distinct() return value
|
||||||
|
** <td valign="top">Rows are returned in aOrderBy order
|
||||||
|
** <td valign="top">Rows with the same value in all aOrderBy columns are adjacent
|
||||||
|
** <td valign="top">Duplicates over all colUsed columns may be omitted
|
||||||
|
** <tr><td>0<td>yes<td>yes<td>no
|
||||||
|
** <tr><td>1<td>no<td>yes<td>no
|
||||||
|
** <tr><td>2<td>no<td>yes<td>yes
|
||||||
|
** <tr><td>3<td>yes<td>yes<td>yes
|
||||||
|
** </table>
|
||||||
|
**
|
||||||
** ^For the purposes of comparing virtual table output values to see if the
|
** ^For the purposes of comparing virtual table output values to see if the
|
||||||
** values are same value for sorting purposes, two NULL values are considered
|
** values are same value for sorting purposes, two NULL values are considered
|
||||||
** to be the same. In other words, the comparison operator is "IS"
|
** to be the same. In other words, the comparison operator is "IS"
|
||||||
|
@ -11998,6 +12025,30 @@ SQLITE_API int sqlite3changegroup_schema(sqlite3_changegroup*, sqlite3*, const c
|
||||||
*/
|
*/
|
||||||
SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
|
SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);
|
||||||
|
|
||||||
|
/*
|
||||||
|
** CAPI3REF: Add A Single Change To A Changegroup
|
||||||
|
** METHOD: sqlite3_changegroup
|
||||||
|
**
|
||||||
|
** This function adds the single change currently indicated by the iterator
|
||||||
|
** passed as the second argument to the changegroup object. The rules for
|
||||||
|
** adding the change are just as described for [sqlite3changegroup_add()].
|
||||||
|
**
|
||||||
|
** If the change is successfully added to the changegroup, SQLITE_OK is
|
||||||
|
** returned. Otherwise, an SQLite error code is returned.
|
||||||
|
**
|
||||||
|
** The iterator must point to a valid entry when this function is called.
|
||||||
|
** If it does not, SQLITE_ERROR is returned and no change is added to the
|
||||||
|
** changegroup. Additionally, the iterator must not have been opened with
|
||||||
|
** the SQLITE_CHANGESETAPPLY_INVERT flag. In this case SQLITE_ERROR is also
|
||||||
|
** returned.
|
||||||
|
*/
|
||||||
|
SQLITE_API int sqlite3changegroup_add_change(
|
||||||
|
sqlite3_changegroup*,
|
||||||
|
sqlite3_changeset_iter*
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** CAPI3REF: Obtain A Composite Changeset From A Changegroup
|
** CAPI3REF: Obtain A Composite Changeset From A Changegroup
|
||||||
** METHOD: sqlite3_changegroup
|
** METHOD: sqlite3_changegroup
|
||||||
|
@ -12802,8 +12853,8 @@ struct Fts5PhraseIter {
|
||||||
** EXTENSION API FUNCTIONS
|
** EXTENSION API FUNCTIONS
|
||||||
**
|
**
|
||||||
** xUserData(pFts):
|
** xUserData(pFts):
|
||||||
** Return a copy of the context pointer the extension function was
|
** Return a copy of the pUserData pointer passed to the xCreateFunction()
|
||||||
** registered with.
|
** API when the extension function was registered.
|
||||||
**
|
**
|
||||||
** xColumnTotalSize(pFts, iCol, pnToken):
|
** xColumnTotalSize(pFts, iCol, pnToken):
|
||||||
** If parameter iCol is less than zero, set output variable *pnToken
|
** If parameter iCol is less than zero, set output variable *pnToken
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -6,25 +6,24 @@
|
||||||
// Copyright @Radolyn, 2024
|
// Copyright @Radolyn, 2024
|
||||||
#include "ayu/ui/context_menu/context_menu.h"
|
#include "ayu/ui/context_menu/context_menu.h"
|
||||||
|
|
||||||
#include <styles/style_menu_icons.h>
|
|
||||||
|
|
||||||
#include "lang_auto.h"
|
#include "lang_auto.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "ayu/ayu_settings.h"
|
#include "ayu/ayu_settings.h"
|
||||||
#include "ayu/ayu_state.h"
|
#include "ayu/ayu_state.h"
|
||||||
#include "../../data/messages_storage.h"
|
#include "ayu/data/messages_storage.h"
|
||||||
#include "ayu/ui/context_menu/menu_item_subtext.h"
|
#include "ayu/ui/context_menu/menu_item_subtext.h"
|
||||||
#include "ayu/utils/qt_key_modifiers_extended.h"
|
#include "ayu/utils/qt_key_modifiers_extended.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
|
|
||||||
#include "core/mime_type.h"
|
#include "core/mime_type.h"
|
||||||
#include "styles/style_ayu_icons.h"
|
#include "styles/style_ayu_icons.h"
|
||||||
|
#include "styles/style_menu_icons.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
#include "ui/widgets/menu/menu_add_action_callback_factory.h"
|
||||||
#include "window/window_peer_menu.h"
|
#include "window/window_peer_menu.h"
|
||||||
|
|
||||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
#include "ayu/ui/message_history/history_section.h"
|
||||||
#include "ayu/utils/telegram_helpers.h"
|
#include "ayu/utils/telegram_helpers.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "history/view/history_view_context_menu.h"
|
#include "history/view/history_view_context_menu.h"
|
||||||
|
@ -37,13 +36,36 @@ bool needToShowItem(int state) {
|
||||||
return state == 1 || (state == 2 && base::IsExtendedContextMenuModifierPressed());
|
return state == 1 || (state == 2 && base::IsExtendedContextMenuModifierPressed());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddDeletedMessagesActions(PeerData *peerData,
|
||||||
|
not_null<Window::SessionController*> sessionController,
|
||||||
|
const Dialogs::EntryState &entryState,
|
||||||
|
const Window::PeerMenuCallback &addCallback) {
|
||||||
|
if (!peerData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto has = AyuMessages::hasDeletedMessages(peerData);
|
||||||
|
if (!has) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addCallback(tr::ayu_ViewDeletedMenuText(tr::now),
|
||||||
|
[=]
|
||||||
|
{
|
||||||
|
sessionController->session().tryResolveWindow()
|
||||||
|
->showSection(std::make_shared<MessageHistory::SectionMemento>(peerData));
|
||||||
|
},
|
||||||
|
&st::menuIconArchive);
|
||||||
|
}
|
||||||
|
|
||||||
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||||
if (AyuMessages::hasRevisions(item)) {
|
if (AyuMessages::hasRevisions(item)) {
|
||||||
menu->addAction(tr::ayu_EditsHistoryMenuText(tr::now),
|
menu->addAction(tr::ayu_EditsHistoryMenuText(tr::now),
|
||||||
[=]
|
[=]
|
||||||
{
|
{
|
||||||
item->history()->session().tryResolveWindow()
|
item->history()->session().tryResolveWindow()
|
||||||
->showSection(std::make_shared<EditedLog::SectionMemento>(item->history()->peer, item));
|
->showSection(
|
||||||
|
std::make_shared<MessageHistory::SectionMemento>(item->history()->peer, item));
|
||||||
},
|
},
|
||||||
&st::ayuEditsHistoryIcon);
|
&st::ayuEditsHistoryIcon);
|
||||||
}
|
}
|
||||||
|
@ -120,7 +142,7 @@ void AddMessageDetailsAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item) {
|
||||||
if (!containsSingleCustomEmojiPack && emojiPacks.size() > 1) {
|
if (!containsSingleCustomEmojiPack && emojiPacks.size() > 1) {
|
||||||
const auto author = emojiPacks.front().id >> 32;
|
const auto author = emojiPacks.front().id >> 32;
|
||||||
auto sameAuthor = true;
|
auto sameAuthor = true;
|
||||||
for (const auto &pack : emojiPacks) {
|
for (const auto &pack : emojiPacks) {
|
||||||
if (pack.id >> 32 != author) {
|
if (pack.id >> 32 != author) {
|
||||||
sameAuthor = false;
|
sameAuthor = false;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -8,11 +8,18 @@
|
||||||
|
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "ui/widgets/popup_menu.h"
|
#include "ui/widgets/popup_menu.h"
|
||||||
|
#include "window/window_peer_menu.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
|
|
||||||
namespace AyuUi {
|
namespace AyuUi {
|
||||||
|
|
||||||
bool needToShowItem(int state);
|
bool needToShowItem(int state);
|
||||||
|
|
||||||
|
void AddDeletedMessagesActions(PeerData *peerData,
|
||||||
|
not_null<Window::SessionController*> sessionController,
|
||||||
|
const Dialogs::EntryState &entryState,
|
||||||
|
const Window::PeerMenuCallback &addCallback);
|
||||||
|
|
||||||
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
void AddHistoryAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||||
void AddHideMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
void AddHideMessageAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||||
void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
void AddUserMessagesAction(not_null<Ui::PopupMenu*> menu, HistoryItem *item);
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
// but be respectful and credit the original author.
|
// but be respectful and credit the original author.
|
||||||
//
|
//
|
||||||
// Copyright @Radolyn, 2024
|
// Copyright @Radolyn, 2024
|
||||||
#include "ayu/ui/sections/edited/edited_log_inner.h"
|
#include "ayu/ui/message_history/history_inner.h"
|
||||||
|
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "api/api_attached_stickers.h"
|
#include "api/api_attached_stickers.h"
|
||||||
#include "ayu/data/messages_storage.h"
|
#include "ayu/data/messages_storage.h"
|
||||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
#include "ayu/ui/message_history/history_section.h"
|
||||||
#include "base/call_delayed.h"
|
#include "base/call_delayed.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
|
@ -59,17 +59,14 @@
|
||||||
|
|
||||||
#include "ui/ui_utility.h"
|
#include "ui/ui_utility.h"
|
||||||
|
|
||||||
namespace EditedLog {
|
namespace MessageHistory {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// If we require to support more admins we'll have to rewrite this anyway.
|
|
||||||
constexpr auto kMaxChannelAdmins = 200;
|
|
||||||
|
|
||||||
constexpr auto kScrollDateHideTimeout = 1000;
|
constexpr auto kScrollDateHideTimeout = 1000;
|
||||||
|
|
||||||
constexpr auto kEventsFirstPage = 20;
|
constexpr auto kMessagesFirstPage = 20;
|
||||||
|
|
||||||
constexpr auto kEventsPerPage = 50;
|
constexpr auto kMessagesPerPage = 30;
|
||||||
|
|
||||||
constexpr auto kClearUserpicsAfter = 50;
|
constexpr auto kClearUserpicsAfter = 50;
|
||||||
|
|
||||||
|
@ -249,7 +246,7 @@ InnerWidget::InnerWidget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<HistoryItem*> item)
|
HistoryItem *item)
|
||||||
: RpWidget(parent),
|
: RpWidget(parent),
|
||||||
_controller(controller),
|
_controller(controller),
|
||||||
_peer(peer),
|
_peer(peer),
|
||||||
|
@ -380,9 +377,7 @@ void InnerWidget::visibleTopBottomUpdated(
|
||||||
}
|
}
|
||||||
|
|
||||||
updateVisibleTopItem();
|
updateVisibleTopItem();
|
||||||
if (_items.size() == 0) {
|
checkPreloadMore();
|
||||||
addEvents(Direction::Up);
|
|
||||||
}
|
|
||||||
if (scrolledUp) {
|
if (scrolledUp) {
|
||||||
_scrollDateCheck.call();
|
_scrollDateCheck.call();
|
||||||
} else {
|
} else {
|
||||||
|
@ -465,17 +460,17 @@ void InnerWidget::repaintScrollDateCallback() {
|
||||||
update(0, updateTop, width(), updateHeight);
|
update(0, updateTop, width(), updateHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void InnerWidget::checkPreloadMore() {
|
||||||
|
if (_visibleTop + PreloadHeightsCount * (_visibleBottom - _visibleTop) > height()) {
|
||||||
|
preloadMore(Direction::Down);
|
||||||
|
}
|
||||||
|
if (_visibleTop < PreloadHeightsCount * (_visibleBottom - _visibleTop)) {
|
||||||
|
preloadMore(Direction::Up);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void InnerWidget::updateEmptyText() {
|
void InnerWidget::updateEmptyText() {
|
||||||
auto hasSearch = false;
|
_emptyText.setMarkedText(st::defaultTextStyle, Ui::Text::Semibold(tr::lng_search_messages_none(tr::now)));
|
||||||
auto hasFilter = false;
|
|
||||||
auto text = Ui::Text::Semibold((hasSearch || hasFilter)
|
|
||||||
? tr::lng_admin_log_no_results_title(tr::now)
|
|
||||||
: tr::lng_admin_log_no_events_title(tr::now));
|
|
||||||
auto description = _peer->isMegagroup()
|
|
||||||
? tr::lng_admin_log_no_events_text(tr::now)
|
|
||||||
: tr::lng_admin_log_no_events_text_channel(tr::now);
|
|
||||||
text.text.append(u"\n\n"_q + description);
|
|
||||||
_emptyText.setMarkedText(st::defaultTextStyle, text);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString InnerWidget::tooltipText() const {
|
QString InnerWidget::tooltipText() const {
|
||||||
|
@ -613,7 +608,7 @@ void InnerWidget::elementStartInteraction(not_null<const Element*> view) {
|
||||||
|
|
||||||
void InnerWidget::elementStartPremium(
|
void InnerWidget::elementStartPremium(
|
||||||
not_null<const HistoryView::Element*> view,
|
not_null<const HistoryView::Element*> view,
|
||||||
HistoryView::Element *replacing) {
|
HistoryView::Element *replacing) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::elementCancelPremium(not_null<const Element*> view) {
|
void InnerWidget::elementCancelPremium(not_null<const Element*> view) {
|
||||||
|
@ -633,17 +628,15 @@ bool InnerWidget::elementHideTopicButton(not_null<const Element*> view) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
void InnerWidget::saveState(not_null<SectionMemento*> memento) {
|
||||||
if (!_filterChanged) {
|
for (auto &item : _items) {
|
||||||
for (auto &item : _items) {
|
item.clearView();
|
||||||
item.clearView();
|
|
||||||
}
|
|
||||||
memento->setItems(
|
|
||||||
base::take(_items),
|
|
||||||
base::take(_eventIds),
|
|
||||||
_upLoaded,
|
|
||||||
_downLoaded);
|
|
||||||
base::take(_itemsByData);
|
|
||||||
}
|
}
|
||||||
|
memento->setItems(
|
||||||
|
base::take(_items),
|
||||||
|
base::take(_messageIds),
|
||||||
|
_upLoaded,
|
||||||
|
_downLoaded);
|
||||||
|
base::take(_itemsByData);
|
||||||
_upLoaded = _downLoaded = true; // Don't load or handle anything anymore.
|
_upLoaded = _downLoaded = true; // Don't load or handle anything anymore.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -653,23 +646,62 @@ void InnerWidget::restoreState(not_null<SectionMemento*> memento) {
|
||||||
item.refreshView(this);
|
item.refreshView(this);
|
||||||
_itemsByData.emplace(item->data(), item.get());
|
_itemsByData.emplace(item->data(), item.get());
|
||||||
}
|
}
|
||||||
_eventIds = memento->takeEventIds();
|
_messageIds = memento->takeMessageIds();
|
||||||
_upLoaded = memento->upLoaded();
|
_upLoaded = memento->upLoaded();
|
||||||
_downLoaded = memento->downLoaded();
|
_downLoaded = memento->downLoaded();
|
||||||
_filterChanged = false;
|
|
||||||
updateSize();
|
updateSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::addEvents(Direction direction) {
|
void InnerWidget::preloadMore(Direction direction) {
|
||||||
auto messages = AyuMessages::getEditedMessages(_item);
|
auto &loadedFlag = (direction == Direction::Up) ? _upLoaded : _downLoaded;
|
||||||
if (messages.empty()) {
|
if (loadedFlag) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto size = messages.size();
|
auto maxId = (direction == Direction::Up) ? _minId : 0;
|
||||||
auto &container = _items;
|
auto minId = (direction == Direction::Up) ? 0 : _maxId;
|
||||||
|
auto perPage = _items.empty() ? kMessagesFirstPage : kMessagesPerPage;
|
||||||
|
|
||||||
|
std::vector<AyuMessageBase> messages;
|
||||||
|
if (_item) {
|
||||||
|
// viewing edited history
|
||||||
|
messages = AyuMessages::getEditedMessages(_item, minId, maxId, perPage);
|
||||||
|
} else {
|
||||||
|
// viewing deleted messages
|
||||||
|
messages = AyuMessages::getDeletedMessages(_peer, minId, maxId, perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
crl::on_main([=]
|
||||||
|
{
|
||||||
|
addMessages(direction, messages);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void InnerWidget::addMessages(Direction direction, const std::vector<AyuMessageBase> &messages) {
|
||||||
|
auto up = (direction == Direction::Up);
|
||||||
|
if (messages.empty()) {
|
||||||
|
(up ? _upLoaded : _downLoaded) = true;
|
||||||
|
update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When loading items up we just add them to the back of the _items vector.
|
||||||
|
// When loading items down we add them to a new vector and copy _items after them.
|
||||||
|
auto newItemsForDownDirection = std::vector<OwnedItem>();
|
||||||
|
auto oldItemsCount = _items.size();
|
||||||
|
auto &addToItems = (direction == Direction::Up)
|
||||||
|
? _items
|
||||||
|
: newItemsForDownDirection;
|
||||||
|
addToItems.reserve(oldItemsCount + messages.size() * 2);
|
||||||
|
|
||||||
for (const auto &message : messages) {
|
for (const auto &message : messages) {
|
||||||
|
const auto id = _item
|
||||||
|
? message.fakeId // viewing edited history
|
||||||
|
: message.messageId; // viewing deleted messages
|
||||||
|
if (_messageIds.find(id) != _messageIds.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto count = 0;
|
||||||
const auto addOne = [&](
|
const auto addOne = [&](
|
||||||
OwnedItem item,
|
OwnedItem item,
|
||||||
TimeId sentDate,
|
TimeId sentDate,
|
||||||
|
@ -678,19 +710,50 @@ void InnerWidget::addEvents(Direction direction) {
|
||||||
if (sentDate) {
|
if (sentDate) {
|
||||||
_itemDates.emplace(item->data(), sentDate);
|
_itemDates.emplace(item->data(), sentDate);
|
||||||
}
|
}
|
||||||
|
_messageIds.emplace(id);
|
||||||
_itemsByData.emplace(item->data(), item.get());
|
_itemsByData.emplace(item->data(), item.get());
|
||||||
container.push_back(std::move(item));
|
addToItems.push_back(std::move(item));
|
||||||
|
++count;
|
||||||
};
|
};
|
||||||
GenerateItems(
|
GenerateItems(
|
||||||
this,
|
this,
|
||||||
_history,
|
_history,
|
||||||
message,
|
message,
|
||||||
addOne);
|
addOne);
|
||||||
|
if (count > 1) {
|
||||||
|
// Reverse the inner order of the added messages, because we load messages
|
||||||
|
// from bottom to top but inside one message they go from top to bottom.
|
||||||
|
auto full = addToItems.size();
|
||||||
|
auto from = full - count;
|
||||||
|
for (auto i = 0, toReverse = count / 2; i != toReverse; ++i) {
|
||||||
|
std::swap(addToItems[from + i], addToItems[full - i - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto newItemsCount = _items.size() + ((direction == Direction::Up) ? 0 : newItemsForDownDirection.size());
|
||||||
|
if (newItemsCount != oldItemsCount) {
|
||||||
|
if (direction == Direction::Down) {
|
||||||
|
for (auto &item : _items) {
|
||||||
|
newItemsForDownDirection.push_back(std::move(item));
|
||||||
|
}
|
||||||
|
_items = std::move(newItemsForDownDirection);
|
||||||
|
}
|
||||||
|
updateMinMaxIds();
|
||||||
|
itemsAdded(direction, newItemsCount - oldItemsCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsAdded(direction, size);
|
|
||||||
update();
|
update();
|
||||||
repaint();
|
}
|
||||||
|
|
||||||
|
void InnerWidget::updateMinMaxIds() {
|
||||||
|
if (_messageIds.empty()) {
|
||||||
|
_maxId = _minId = 0;
|
||||||
|
} else {
|
||||||
|
_maxId = *_messageIds.rbegin();
|
||||||
|
_minId = *_messageIds.begin();
|
||||||
|
if (_minId == 1) {
|
||||||
|
_upLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InnerWidget::itemsAdded(Direction direction, int addedCount) {
|
void InnerWidget::itemsAdded(Direction direction, int addedCount) {
|
||||||
|
@ -720,6 +783,7 @@ void InnerWidget::updateSize() {
|
||||||
TWidget::resizeToWidth(width());
|
TWidget::resizeToWidth(width());
|
||||||
restoreScrollPosition();
|
restoreScrollPosition();
|
||||||
updateVisibleTopItem();
|
updateVisibleTopItem();
|
||||||
|
checkPreloadMore();
|
||||||
}
|
}
|
||||||
|
|
||||||
int InnerWidget::resizeGetHeight(int newWidth) {
|
int InnerWidget::resizeGetHeight(int newWidth) {
|
||||||
|
@ -1636,4 +1700,4 @@ QPoint InnerWidget::mapPointToItem(QPoint point, const Element *view) const {
|
||||||
|
|
||||||
InnerWidget::~InnerWidget() = default;
|
InnerWidget::~InnerWidget() = default;
|
||||||
|
|
||||||
} // namespace EditedLog
|
} // namespace MessageHistory
|
|
@ -6,8 +6,8 @@
|
||||||
// Copyright @Radolyn, 2024
|
// Copyright @Radolyn, 2024
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "ayu/ui/sections/edited/edited_log_item.h"
|
#include "ayu/ui/message_history/history_item.h"
|
||||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
#include "ayu/ui/message_history/history_section.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "menu/menu_antispam_validator.h"
|
#include "menu/menu_antispam_validator.h"
|
||||||
|
@ -40,7 +40,7 @@ namespace Window {
|
||||||
class SessionController;
|
class SessionController;
|
||||||
} // namespace Window
|
} // namespace Window
|
||||||
|
|
||||||
namespace EditedLog {
|
namespace MessageHistory {
|
||||||
|
|
||||||
class SectionMemento;
|
class SectionMemento;
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ public:
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<HistoryItem*> item);
|
HistoryItem *item);
|
||||||
|
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<int> scrollToSignal() const;
|
[[nodiscard]] rpl::producer<int> scrollToSignal() const;
|
||||||
|
|
||||||
[[nodiscard]] not_null<PeerData*> channel() const {
|
[[nodiscard]] not_null<PeerData*> peer() const {
|
||||||
return _peer;
|
return _peer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,15 +207,18 @@ private:
|
||||||
TextForMimeData getSelectedText() const;
|
TextForMimeData getSelectedText() const;
|
||||||
|
|
||||||
void updateVisibleTopItem();
|
void updateVisibleTopItem();
|
||||||
|
void preloadMore(Direction direction);
|
||||||
void itemsAdded(Direction direction, int addedCount);
|
void itemsAdded(Direction direction, int addedCount);
|
||||||
void updateSize();
|
void updateSize();
|
||||||
|
void updateMinMaxIds();
|
||||||
void updateEmptyText();
|
void updateEmptyText();
|
||||||
void paintEmpty(Painter &p, not_null<const Ui::ChatStyle*> st);
|
void paintEmpty(Painter &p, not_null<const Ui::ChatStyle*> st);
|
||||||
void addEvents(Direction direction);
|
void addMessages(Direction direction, const std::vector<AyuMessageBase> &messages);
|
||||||
Element *viewForItem(const HistoryItem *item);
|
Element *viewForItem(const HistoryItem *item);
|
||||||
|
|
||||||
void toggleScrollDateShown();
|
void toggleScrollDateShown();
|
||||||
void repaintScrollDateCallback();
|
void repaintScrollDateCallback();
|
||||||
|
void checkPreloadMore();
|
||||||
bool displayScrollDate() const;
|
bool displayScrollDate() const;
|
||||||
void scrollDateHide();
|
void scrollDateHide();
|
||||||
void scrollDateCheck();
|
void scrollDateCheck();
|
||||||
|
@ -247,7 +250,7 @@ private:
|
||||||
|
|
||||||
const not_null<Window::SessionController*> _controller;
|
const not_null<Window::SessionController*> _controller;
|
||||||
const not_null<PeerData*> _peer;
|
const not_null<PeerData*> _peer;
|
||||||
const not_null<HistoryItem*> _item;
|
HistoryItem *_item;
|
||||||
const not_null<History*> _history;
|
const not_null<History*> _history;
|
||||||
MTP::Sender _api;
|
MTP::Sender _api;
|
||||||
|
|
||||||
|
@ -255,7 +258,7 @@ private:
|
||||||
std::shared_ptr<Ui::ChatTheme> _theme;
|
std::shared_ptr<Ui::ChatTheme> _theme;
|
||||||
|
|
||||||
std::vector<OwnedItem> _items;
|
std::vector<OwnedItem> _items;
|
||||||
std::set<uint64> _eventIds;
|
std::set<uint64> _messageIds;
|
||||||
std::map<not_null<const HistoryItem*>, not_null<Element*>> _itemsByData;
|
std::map<not_null<const HistoryItem*>, not_null<Element*>> _itemsByData;
|
||||||
base::flat_map<not_null<const HistoryItem*>, TimeId> _itemDates;
|
base::flat_map<not_null<const HistoryItem*>, TimeId> _itemDates;
|
||||||
base::flat_set<FullMsgId> _animatedStickersPlayed;
|
base::flat_set<FullMsgId> _animatedStickersPlayed;
|
||||||
|
@ -279,10 +282,13 @@ private:
|
||||||
Element *_scrollDateLastItem = nullptr;
|
Element *_scrollDateLastItem = nullptr;
|
||||||
int _scrollDateLastItemTop = 0;
|
int _scrollDateLastItemTop = 0;
|
||||||
|
|
||||||
|
// Up - max, Down - min.
|
||||||
|
uint64 _maxId = 0;
|
||||||
|
uint64 _minId = 0;
|
||||||
|
|
||||||
// Don't load anything until the memento was read.
|
// Don't load anything until the memento was read.
|
||||||
bool _upLoaded = true;
|
bool _upLoaded = true;
|
||||||
bool _downLoaded = true;
|
bool _downLoaded = true;
|
||||||
bool _filterChanged = false;
|
|
||||||
Ui::Text::String _emptyText;
|
Ui::Text::String _emptyText;
|
||||||
|
|
||||||
MouseAction _mouseAction = MouseAction::None;
|
MouseAction _mouseAction = MouseAction::None;
|
||||||
|
@ -308,4 +314,4 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace EditedLog
|
} // namespace MessageHistory
|
116
Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp
Normal file
116
Telegram/SourceFiles/ayu/ui/message_history/history_item.cpp
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
// This is the source code of AyuGram for Desktop.
|
||||||
|
//
|
||||||
|
// We do not and cannot prevent the use of our code,
|
||||||
|
// but be respectful and credit the original author.
|
||||||
|
//
|
||||||
|
// Copyright @Radolyn, 2024
|
||||||
|
#include "ayu/ui/message_history/history_item.h"
|
||||||
|
#include "ayu/data/entities.h"
|
||||||
|
|
||||||
|
#include "api/api_chat_participants.h"
|
||||||
|
#include "api/api_text_entities.h"
|
||||||
|
#include "ayu/ui/message_history/history_inner.h"
|
||||||
|
#include "base/unixtime.h"
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "core/click_handler_types.h"
|
||||||
|
#include "data/data_channel.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
|
#include "data/data_forum_topic.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_user.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "history/history_item_helpers.h"
|
||||||
|
#include "history/view/history_view_element.h"
|
||||||
|
#include "lang/lang_keys.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "ui/basic_click_handlers.h"
|
||||||
|
#include "ui/text/text_utilities.h"
|
||||||
|
|
||||||
|
namespace MessageHistory {
|
||||||
|
|
||||||
|
OwnedItem::OwnedItem(std::nullptr_t) {
|
||||||
|
}
|
||||||
|
|
||||||
|
OwnedItem::OwnedItem(
|
||||||
|
not_null<HistoryView::ElementDelegate*> delegate,
|
||||||
|
not_null<HistoryItem*> data)
|
||||||
|
: _data(data), _view(_data->createView(delegate)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
OwnedItem::OwnedItem(OwnedItem &&other)
|
||||||
|
: _data(base::take(other._data)), _view(base::take(other._view)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
OwnedItem &OwnedItem::operator=(OwnedItem &&other) {
|
||||||
|
_data = base::take(other._data);
|
||||||
|
_view = base::take(other._view);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
OwnedItem::~OwnedItem() {
|
||||||
|
clearView();
|
||||||
|
if (_data) {
|
||||||
|
_data->destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OwnedItem::refreshView(
|
||||||
|
not_null<HistoryView::ElementDelegate*> delegate) {
|
||||||
|
_view = _data->createView(delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OwnedItem::clearView() {
|
||||||
|
_view = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GenerateItems(
|
||||||
|
not_null<HistoryView::ElementDelegate*> delegate,
|
||||||
|
not_null<History*> history,
|
||||||
|
AyuMessageBase message,
|
||||||
|
Fn<void(OwnedItem item, TimeId sentDate, MsgId)> callback) {
|
||||||
|
PeerData *from = history->owner().userLoaded(message.fromId);
|
||||||
|
if (!from) {
|
||||||
|
from = history->owner().channelLoaded(message.fromId);
|
||||||
|
}
|
||||||
|
if (!from) {
|
||||||
|
from = reinterpret_cast<PeerData*>(history->owner().chatLoaded(message.fromId));
|
||||||
|
}
|
||||||
|
if (!from) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto date = message.entityCreateDate;
|
||||||
|
const auto addPart = [&](
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
TimeId sentDate = 0,
|
||||||
|
MsgId realId = MsgId())
|
||||||
|
{
|
||||||
|
return callback(OwnedItem(delegate, item), sentDate, realId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto fromName = from->name();
|
||||||
|
const auto fromLink = from->createOpenLink();
|
||||||
|
const auto fromLinkText = Ui::Text::Link(fromName, QString());
|
||||||
|
|
||||||
|
const auto makeSimpleTextMessage = [&](TextWithEntities &&text)
|
||||||
|
{
|
||||||
|
return history->makeMessage({
|
||||||
|
.id = history->nextNonHistoryEntryId(),
|
||||||
|
.flags = MessageFlag::HasFromId | MessageFlag::AdminLogEntry,
|
||||||
|
.from = from->id,
|
||||||
|
.date = date,
|
||||||
|
},
|
||||||
|
std::move(text),
|
||||||
|
MTP_messageMediaEmpty());
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto addSimpleTextMessage = [&](TextWithEntities &&text)
|
||||||
|
{
|
||||||
|
addPart(makeSimpleTextMessage(std::move(text)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto text = QString::fromStdString(message.text);
|
||||||
|
addSimpleTextMessage(Ui::Text::WithEntities(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MessageHistory
|
|
@ -15,14 +15,14 @@ class ElementDelegate;
|
||||||
class Element;
|
class Element;
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
||||||
namespace EditedLog {
|
namespace MessageHistory {
|
||||||
|
|
||||||
class OwnedItem;
|
class OwnedItem;
|
||||||
|
|
||||||
void GenerateItems(
|
void GenerateItems(
|
||||||
not_null<HistoryView::ElementDelegate*> delegate,
|
not_null<HistoryView::ElementDelegate*> delegate,
|
||||||
not_null<History*> history,
|
not_null<History*> history,
|
||||||
EditedMessage message,
|
AyuMessageBase message,
|
||||||
Fn<void(OwnedItem item, TimeId sentDate, MsgId)> callback);
|
Fn<void(OwnedItem item, TimeId sentDate, MsgId)> callback);
|
||||||
|
|
||||||
// Smart pointer wrapper for HistoryItem* that destroys the owned item.
|
// Smart pointer wrapper for HistoryItem* that destroys the owned item.
|
||||||
|
@ -60,4 +60,4 @@ private:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace EditedLog
|
} // namespace MessageHistory
|
|
@ -4,10 +4,10 @@
|
||||||
// but be respectful and credit the original author.
|
// but be respectful and credit the original author.
|
||||||
//
|
//
|
||||||
// Copyright @Radolyn, 2024
|
// Copyright @Radolyn, 2024
|
||||||
#include "ayu/ui/sections/edited/edited_log_section.h"
|
#include "ayu/ui/message_history/history_section.h"
|
||||||
|
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "ayu/ui/sections/edited/edited_log_inner.h"
|
#include "ayu/ui/message_history/history_inner.h"
|
||||||
#include "base/timer.h"
|
#include "base/timer.h"
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "window/themes/window_theme.h"
|
#include "window/themes/window_theme.h"
|
||||||
|
|
||||||
namespace EditedLog {
|
namespace MessageHistory {
|
||||||
|
|
||||||
class FixedBar final : public TWidget
|
class FixedBar final : public TWidget
|
||||||
{
|
{
|
||||||
|
@ -142,7 +142,7 @@ Widget::Widget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<HistoryItem*> item)
|
HistoryItem *item)
|
||||||
: Window::SectionWidget(parent, controller, rpl::single<PeerData*>(peer)),
|
: Window::SectionWidget(parent, controller, rpl::single<PeerData*>(peer)),
|
||||||
_scroll(this, st::historyScroll, false),
|
_scroll(this, st::historyScroll, false),
|
||||||
_fixedBar(this, controller, peer),
|
_fixedBar(this, controller, peer),
|
||||||
|
@ -190,7 +190,7 @@ void Widget::updateAdaptiveLayout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
not_null<PeerData*> Widget::channel() const {
|
not_null<PeerData*> Widget::channel() const {
|
||||||
return _inner->channel();
|
return _inner->peer();
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialogs::RowDescriptor Widget::activeChat() const {
|
Dialogs::RowDescriptor Widget::activeChat() const {
|
||||||
|
@ -321,4 +321,4 @@ QRect Widget::floatPlayerAvailableRect() {
|
||||||
return mapToGlobal(_scroll->geometry());
|
return mapToGlobal(_scroll->geometry());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace EditedLog
|
} // namespace MessageHistory
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
#include "window/section_widget.h"
|
#include "window/section_widget.h"
|
||||||
#include "window/section_memento.h"
|
#include "window/section_memento.h"
|
||||||
#include "ayu/ui/sections/edited/edited_log_item.h"
|
#include "ayu/ui/message_history/history_item.h"
|
||||||
#include "mtproto/sender.h"
|
#include "mtproto/sender.h"
|
||||||
// don't reformat includes above
|
// don't reformat includes above
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ namespace Profile {
|
||||||
class BackButton;
|
class BackButton;
|
||||||
} // namespace Profile
|
} // namespace Profile
|
||||||
|
|
||||||
namespace EditedLog {
|
namespace MessageHistory {
|
||||||
|
|
||||||
class FixedBar;
|
class FixedBar;
|
||||||
class InnerWidget;
|
class InnerWidget;
|
||||||
|
@ -35,7 +35,7 @@ public:
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
not_null<HistoryItem*> item);
|
HistoryItem *item);
|
||||||
|
|
||||||
not_null<PeerData*> channel() const;
|
not_null<PeerData*> channel() const;
|
||||||
Dialogs::RowDescriptor activeChat() const override;
|
Dialogs::RowDescriptor activeChat() const override;
|
||||||
|
@ -77,7 +77,7 @@ private:
|
||||||
QPointer<InnerWidget> _inner;
|
QPointer<InnerWidget> _inner;
|
||||||
object_ptr<FixedBar> _fixedBar;
|
object_ptr<FixedBar> _fixedBar;
|
||||||
object_ptr<Ui::PlainShadow> _fixedBarShadow;
|
object_ptr<Ui::PlainShadow> _fixedBarShadow;
|
||||||
not_null<HistoryItem*> _item;
|
HistoryItem *_item;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -91,6 +91,11 @@ public:
|
||||||
_item(item) {
|
_item(item) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SectionMemento(not_null<PeerData*> peer)
|
||||||
|
: _peer(peer),
|
||||||
|
_item(nullptr) {
|
||||||
|
}
|
||||||
|
|
||||||
object_ptr<Window::SectionWidget> createWidget(
|
object_ptr<Window::SectionWidget> createWidget(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> controller,
|
||||||
|
@ -124,7 +129,7 @@ public:
|
||||||
return std::move(_items);
|
return std::move(_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<uint64> takeEventIds() {
|
std::set<uint64> takeMessageIds() {
|
||||||
return std::move(_eventIds);
|
return std::move(_eventIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,14 +143,12 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
not_null<PeerData*> _peer;
|
not_null<PeerData*> _peer;
|
||||||
not_null<HistoryItem*> _item;
|
HistoryItem *_item;
|
||||||
int _scrollTop = 0;
|
int _scrollTop = 0;
|
||||||
std::vector<not_null<UserData*>> _admins;
|
|
||||||
std::vector<not_null<UserData*>> _adminsCanEdit;
|
|
||||||
std::vector<OwnedItem> _items;
|
std::vector<OwnedItem> _items;
|
||||||
std::set<uint64> _eventIds;
|
std::set<uint64> _eventIds;
|
||||||
bool _upLoaded = false;
|
bool _upLoaded = false;
|
||||||
bool _downLoaded = true;
|
bool _downLoaded = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace EditedLog
|
} // namespace MessageHistory
|
|
@ -1,659 +0,0 @@
|
||||||
// This is the source code of AyuGram for Desktop.
|
|
||||||
//
|
|
||||||
// We do not and cannot prevent the use of our code,
|
|
||||||
// but be respectful and credit the original author.
|
|
||||||
//
|
|
||||||
// Copyright @Radolyn, 2024
|
|
||||||
#include "ayu/ui/sections/edited/edited_log_item.h"
|
|
||||||
#include "ayu/data/entities.h"
|
|
||||||
|
|
||||||
#include "api/api_chat_participants.h"
|
|
||||||
#include "api/api_text_entities.h"
|
|
||||||
#include "ayu/ui/sections/edited/edited_log_inner.h"
|
|
||||||
#include "base/unixtime.h"
|
|
||||||
#include "core/application.h"
|
|
||||||
#include "core/click_handler_types.h"
|
|
||||||
#include "data/data_channel.h"
|
|
||||||
#include "data/data_file_origin.h"
|
|
||||||
#include "data/data_forum_topic.h"
|
|
||||||
#include "data/data_session.h"
|
|
||||||
#include "data/data_user.h"
|
|
||||||
#include "history/history.h"
|
|
||||||
#include "history/history_item.h"
|
|
||||||
#include "history/history_item_helpers.h"
|
|
||||||
#include "history/view/history_view_element.h"
|
|
||||||
#include "lang/lang_keys.h"
|
|
||||||
#include "main/main_session.h"
|
|
||||||
#include "ui/basic_click_handlers.h"
|
|
||||||
#include "ui/text/text_utilities.h"
|
|
||||||
|
|
||||||
namespace EditedLog {
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
const auto CollectChanges = [](
|
|
||||||
auto &phraseMap,
|
|
||||||
auto plusFlags,
|
|
||||||
auto minusFlags)
|
|
||||||
{
|
|
||||||
auto withPrefix = [&phraseMap](auto flags, QChar prefix)
|
|
||||||
{
|
|
||||||
auto result = QString();
|
|
||||||
for (auto &phrase : phraseMap) {
|
|
||||||
if (flags & phrase.first) {
|
|
||||||
result.append('\n' + (prefix + phrase.second(tr::now)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
const auto kMinus = QChar(0x2212);
|
|
||||||
return withPrefix(plusFlags & ~minusFlags, '+')
|
|
||||||
+ withPrefix(minusFlags & ~plusFlags, kMinus);
|
|
||||||
};
|
|
||||||
|
|
||||||
TextWithEntities GenerateAdminChangeText(
|
|
||||||
not_null<ChannelData*> channel,
|
|
||||||
const TextWithEntities &user,
|
|
||||||
ChatAdminRightsInfo newRights,
|
|
||||||
ChatAdminRightsInfo prevRights) {
|
|
||||||
using Flag = ChatAdminRight;
|
|
||||||
using Flags = ChatAdminRights;
|
|
||||||
|
|
||||||
auto result = tr::lng_admin_log_promoted(
|
|
||||||
tr::now,
|
|
||||||
lt_user,
|
|
||||||
user,
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
|
|
||||||
const auto useInviteLinkPhrase = channel->isMegagroup()
|
|
||||||
&& channel->anyoneCanAddMembers();
|
|
||||||
const auto invitePhrase = useInviteLinkPhrase
|
|
||||||
? tr::lng_admin_log_admin_invite_link
|
|
||||||
: tr::lng_admin_log_admin_invite_users;
|
|
||||||
const auto callPhrase = channel->isBroadcast()
|
|
||||||
? tr::lng_admin_log_admin_manage_calls_channel
|
|
||||||
: tr::lng_admin_log_admin_manage_calls;
|
|
||||||
static auto phraseMap = std::map<Flags, tr::phrase<>>{
|
|
||||||
{Flag::ChangeInfo, tr::lng_admin_log_admin_change_info},
|
|
||||||
{Flag::PostMessages, tr::lng_admin_log_admin_post_messages},
|
|
||||||
{Flag::EditMessages, tr::lng_admin_log_admin_edit_messages},
|
|
||||||
{Flag::DeleteMessages, tr::lng_admin_log_admin_delete_messages},
|
|
||||||
{Flag::BanUsers, tr::lng_admin_log_admin_ban_users},
|
|
||||||
{Flag::InviteByLinkOrAdd, invitePhrase},
|
|
||||||
{Flag::ManageTopics, tr::lng_admin_log_admin_manage_topics},
|
|
||||||
{Flag::PinMessages, tr::lng_admin_log_admin_pin_messages},
|
|
||||||
{Flag::ManageCall, tr::lng_admin_log_admin_manage_calls},
|
|
||||||
{Flag::AddAdmins, tr::lng_admin_log_admin_add_admins},
|
|
||||||
{Flag::Anonymous, tr::lng_admin_log_admin_remain_anonymous},
|
|
||||||
};
|
|
||||||
phraseMap[Flag::InviteByLinkOrAdd] = invitePhrase;
|
|
||||||
phraseMap[Flag::ManageCall] = callPhrase;
|
|
||||||
|
|
||||||
if (!channel->isMegagroup()) {
|
|
||||||
// Don't display "Ban users" changes in channels.
|
|
||||||
newRights.flags &= ~Flag::BanUsers;
|
|
||||||
prevRights.flags &= ~Flag::BanUsers;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto changes = CollectChanges(
|
|
||||||
phraseMap,
|
|
||||||
newRights.flags,
|
|
||||||
prevRights.flags);
|
|
||||||
if (!changes.isEmpty()) {
|
|
||||||
result.text.append('\n' + changes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
QString GeneratePermissionsChangeText(
|
|
||||||
ChatRestrictionsInfo newRights,
|
|
||||||
ChatRestrictionsInfo prevRights) {
|
|
||||||
using Flag = ChatRestriction;
|
|
||||||
using Flags = ChatRestrictions;
|
|
||||||
|
|
||||||
static auto phraseMap = std::map<Flags, tr::phrase<>>{
|
|
||||||
{Flag::ViewMessages, tr::lng_admin_log_banned_view_messages},
|
|
||||||
{Flag::SendOther, tr::lng_admin_log_banned_send_messages},
|
|
||||||
{Flag::SendPhotos, tr::lng_admin_log_banned_send_photos},
|
|
||||||
{Flag::SendVideos, tr::lng_admin_log_banned_send_videos},
|
|
||||||
{Flag::SendMusic, tr::lng_admin_log_banned_send_music},
|
|
||||||
{Flag::SendFiles, tr::lng_admin_log_banned_send_files},
|
|
||||||
{
|
|
||||||
Flag::SendVoiceMessages,
|
|
||||||
tr::lng_admin_log_banned_send_voice_messages
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flag::SendVideoMessages,
|
|
||||||
tr::lng_admin_log_banned_send_video_messages
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flag::SendStickers
|
|
||||||
| Flag::SendGifs
|
|
||||||
| Flag::SendInline
|
|
||||||
| Flag::SendGames,
|
|
||||||
tr::lng_admin_log_banned_send_stickers
|
|
||||||
},
|
|
||||||
{Flag::EmbedLinks, tr::lng_admin_log_banned_embed_links},
|
|
||||||
{Flag::SendPolls, tr::lng_admin_log_banned_send_polls},
|
|
||||||
{Flag::ChangeInfo, tr::lng_admin_log_admin_change_info},
|
|
||||||
{Flag::AddParticipants, tr::lng_admin_log_admin_invite_users},
|
|
||||||
{Flag::CreateTopics, tr::lng_admin_log_admin_create_topics},
|
|
||||||
{Flag::PinMessages, tr::lng_admin_log_admin_pin_messages},
|
|
||||||
};
|
|
||||||
return CollectChanges(phraseMap, prevRights.flags, newRights.flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextWithEntities GeneratePermissionsChangeText(
|
|
||||||
PeerId participantId,
|
|
||||||
const TextWithEntities &user,
|
|
||||||
ChatRestrictionsInfo newRights,
|
|
||||||
ChatRestrictionsInfo prevRights) {
|
|
||||||
using Flag = ChatRestriction;
|
|
||||||
|
|
||||||
const auto newFlags = newRights.flags;
|
|
||||||
const auto newUntil = newRights.until;
|
|
||||||
const auto prevFlags = prevRights.flags;
|
|
||||||
const auto indefinitely = ChannelData::IsRestrictedForever(newUntil);
|
|
||||||
if (newFlags & Flag::ViewMessages) {
|
|
||||||
return tr::lng_admin_log_banned(
|
|
||||||
tr::now,
|
|
||||||
lt_user,
|
|
||||||
user,
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
} else if (newFlags == 0
|
|
||||||
&& (prevFlags & Flag::ViewMessages)
|
|
||||||
&& !peerIsUser(participantId)) {
|
|
||||||
return tr::lng_admin_log_unbanned(
|
|
||||||
tr::now,
|
|
||||||
lt_user,
|
|
||||||
user,
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
}
|
|
||||||
const auto untilText = indefinitely
|
|
||||||
? tr::lng_admin_log_restricted_forever(tr::now)
|
|
||||||
: tr::lng_admin_log_restricted_until(
|
|
||||||
tr::now,
|
|
||||||
lt_date,
|
|
||||||
langDateTime(base::unixtime::parse(newUntil)));
|
|
||||||
auto result = tr::lng_admin_log_restricted(
|
|
||||||
tr::now,
|
|
||||||
lt_user,
|
|
||||||
user,
|
|
||||||
lt_until,
|
|
||||||
TextWithEntities{untilText},
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
const auto changes = GeneratePermissionsChangeText(newRights, prevRights);
|
|
||||||
if (!changes.isEmpty()) {
|
|
||||||
result.text.append('\n' + changes);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString PublicJoinLink() {
|
|
||||||
return u"(public_join_link)"_q;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ExtractInviteLink(const MTPExportedChatInvite &data) {
|
|
||||||
return data.match([&](const MTPDchatInviteExported &data)
|
|
||||||
{
|
|
||||||
return qs(data.vlink());
|
|
||||||
},
|
|
||||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
|
||||||
{
|
|
||||||
return PublicJoinLink();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ExtractInviteLinkLabel(const MTPExportedChatInvite &data) {
|
|
||||||
return data.match([&](const MTPDchatInviteExported &data)
|
|
||||||
{
|
|
||||||
return qs(data.vtitle().value_or_empty());
|
|
||||||
},
|
|
||||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
|
||||||
{
|
|
||||||
return PublicJoinLink();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
QString InternalInviteLinkUrl(const MTPExportedChatInvite &data) {
|
|
||||||
const auto base64 = ExtractInviteLink(data).toUtf8().toBase64();
|
|
||||||
return "internal:show_invite_link/?link=" + QString::fromLatin1(base64);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString GenerateInviteLinkText(const MTPExportedChatInvite &data) {
|
|
||||||
const auto label = ExtractInviteLinkLabel(data);
|
|
||||||
return label.isEmpty()
|
|
||||||
? ExtractInviteLink(data).replace(
|
|
||||||
u"https://"_q,
|
|
||||||
QString()
|
|
||||||
).replace(
|
|
||||||
u"t.me/joinchat/"_q,
|
|
||||||
QString()
|
|
||||||
)
|
|
||||||
: label;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextWithEntities GenerateInviteLinkLink(const MTPExportedChatInvite &data) {
|
|
||||||
const auto text = GenerateInviteLinkText(data);
|
|
||||||
return text.endsWith(Ui::kQEllipsis)
|
|
||||||
? TextWithEntities{.text = text}
|
|
||||||
: Ui::Text::Link(text, InternalInviteLinkUrl(data));
|
|
||||||
}
|
|
||||||
|
|
||||||
TextWithEntities GenerateInviteLinkChangeText(
|
|
||||||
const MTPExportedChatInvite &newLink,
|
|
||||||
const MTPExportedChatInvite &prevLink) {
|
|
||||||
auto link = TextWithEntities{GenerateInviteLinkText(newLink)};
|
|
||||||
if (!link.text.endsWith(Ui::kQEllipsis)) {
|
|
||||||
link.entities.push_back({
|
|
||||||
EntityType::CustomUrl,
|
|
||||||
0,
|
|
||||||
int(link.text.size()),
|
|
||||||
InternalInviteLinkUrl(newLink)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
auto result = tr::lng_admin_log_edited_invite_link(
|
|
||||||
tr::now,
|
|
||||||
lt_link,
|
|
||||||
link,
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
result.text.append('\n');
|
|
||||||
|
|
||||||
const auto label = [](const MTPExportedChatInvite &link)
|
|
||||||
{
|
|
||||||
return link.match([](const MTPDchatInviteExported &data)
|
|
||||||
{
|
|
||||||
return qs(data.vtitle().value_or_empty());
|
|
||||||
},
|
|
||||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
|
||||||
{
|
|
||||||
return PublicJoinLink();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const auto expireDate = [](const MTPExportedChatInvite &link)
|
|
||||||
{
|
|
||||||
return link.match([](const MTPDchatInviteExported &data)
|
|
||||||
{
|
|
||||||
return data.vexpire_date().value_or_empty();
|
|
||||||
},
|
|
||||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
|
||||||
{
|
|
||||||
return TimeId();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const auto usageLimit = [](const MTPExportedChatInvite &link)
|
|
||||||
{
|
|
||||||
return link.match([](const MTPDchatInviteExported &data)
|
|
||||||
{
|
|
||||||
return data.vusage_limit().value_or_empty();
|
|
||||||
},
|
|
||||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const auto requestApproval = [](const MTPExportedChatInvite &link)
|
|
||||||
{
|
|
||||||
return link.match([](const MTPDchatInviteExported &data)
|
|
||||||
{
|
|
||||||
return data.is_request_needed();
|
|
||||||
},
|
|
||||||
[&](const MTPDchatInvitePublicJoinRequests &data)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const auto wrapDate = [](TimeId date)
|
|
||||||
{
|
|
||||||
return date
|
|
||||||
? langDateTime(base::unixtime::parse(date))
|
|
||||||
: tr::lng_group_invite_expire_never(tr::now);
|
|
||||||
};
|
|
||||||
const auto wrapUsage = [](int count)
|
|
||||||
{
|
|
||||||
return count
|
|
||||||
? QString::number(count)
|
|
||||||
: tr::lng_group_invite_usage_any(tr::now);
|
|
||||||
};
|
|
||||||
const auto wasLabel = label(prevLink);
|
|
||||||
const auto nowLabel = label(newLink);
|
|
||||||
const auto wasExpireDate = expireDate(prevLink);
|
|
||||||
const auto nowExpireDate = expireDate(newLink);
|
|
||||||
const auto wasUsageLimit = usageLimit(prevLink);
|
|
||||||
const auto nowUsageLimit = usageLimit(newLink);
|
|
||||||
const auto wasRequestApproval = requestApproval(prevLink);
|
|
||||||
const auto nowRequestApproval = requestApproval(newLink);
|
|
||||||
if (wasLabel != nowLabel) {
|
|
||||||
result.text.append('\n').append(
|
|
||||||
tr::lng_admin_log_invite_link_label(
|
|
||||||
tr::now,
|
|
||||||
lt_previous,
|
|
||||||
wasLabel,
|
|
||||||
lt_limit,
|
|
||||||
nowLabel));
|
|
||||||
}
|
|
||||||
if (wasExpireDate != nowExpireDate) {
|
|
||||||
result.text.append('\n').append(
|
|
||||||
tr::lng_admin_log_invite_link_expire_date(
|
|
||||||
tr::now,
|
|
||||||
lt_previous,
|
|
||||||
wrapDate(wasExpireDate),
|
|
||||||
lt_limit,
|
|
||||||
wrapDate(nowExpireDate)));
|
|
||||||
}
|
|
||||||
if (wasUsageLimit != nowUsageLimit) {
|
|
||||||
result.text.append('\n').append(
|
|
||||||
tr::lng_admin_log_invite_link_usage_limit(
|
|
||||||
tr::now,
|
|
||||||
lt_previous,
|
|
||||||
wrapUsage(wasUsageLimit),
|
|
||||||
lt_limit,
|
|
||||||
wrapUsage(nowUsageLimit)));
|
|
||||||
}
|
|
||||||
if (wasRequestApproval != nowRequestApproval) {
|
|
||||||
result.text.append('\n').append(
|
|
||||||
nowRequestApproval
|
|
||||||
? tr::lng_admin_log_invite_link_request_needed(tr::now)
|
|
||||||
: tr::lng_admin_log_invite_link_request_not_needed(tr::now));
|
|
||||||
}
|
|
||||||
|
|
||||||
result.entities.push_front(
|
|
||||||
EntityInText(EntityType::Italic, 0, result.text.size()));
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto GenerateParticipantString(
|
|
||||||
not_null<Main::Session*> session,
|
|
||||||
PeerId participantId) {
|
|
||||||
// User name in "User name (@username)" format with entities.
|
|
||||||
const auto peer = session->data().peer(participantId);
|
|
||||||
auto name = TextWithEntities{peer->name()};
|
|
||||||
if (const auto user = peer->asUser()) {
|
|
||||||
const auto data = TextUtilities::MentionNameDataFromFields({
|
|
||||||
.selfId = session->userId().bare,
|
|
||||||
.userId = peerToUser(user->id).bare,
|
|
||||||
.accessHash = user->accessHash(),
|
|
||||||
});
|
|
||||||
name.entities.push_back({
|
|
||||||
EntityType::MentionName,
|
|
||||||
0,
|
|
||||||
int(name.text.size()),
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const auto username = peer->username();
|
|
||||||
if (username.isEmpty()) {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
auto mention = TextWithEntities{'@' + username};
|
|
||||||
mention.entities.push_back({
|
|
||||||
EntityType::Mention,
|
|
||||||
0,
|
|
||||||
int(mention.text.size())
|
|
||||||
});
|
|
||||||
return tr::lng_admin_log_user_with_username(
|
|
||||||
tr::now,
|
|
||||||
lt_name,
|
|
||||||
name,
|
|
||||||
lt_mention,
|
|
||||||
mention,
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto GenerateParticipantChangeText(
|
|
||||||
not_null<ChannelData*> channel,
|
|
||||||
const Api::ChatParticipant &participant,
|
|
||||||
std::optional<Api::ChatParticipant> oldParticipant = std::nullopt) {
|
|
||||||
using Type = Api::ChatParticipant::Type;
|
|
||||||
const auto oldRights = oldParticipant
|
|
||||||
? oldParticipant->rights()
|
|
||||||
: ChatAdminRightsInfo();
|
|
||||||
const auto oldRestrictions = oldParticipant
|
|
||||||
? oldParticipant->restrictions()
|
|
||||||
: ChatRestrictionsInfo();
|
|
||||||
|
|
||||||
const auto generateOther = [&](PeerId participantId)
|
|
||||||
{
|
|
||||||
auto user = GenerateParticipantString(
|
|
||||||
&channel->session(),
|
|
||||||
participantId);
|
|
||||||
if (oldParticipant && oldParticipant->type() == Type::Admin) {
|
|
||||||
return GenerateAdminChangeText(
|
|
||||||
channel,
|
|
||||||
user,
|
|
||||||
ChatAdminRightsInfo(),
|
|
||||||
oldRights);
|
|
||||||
} else if (oldParticipant && oldParticipant->type() == Type::Banned) {
|
|
||||||
return GeneratePermissionsChangeText(
|
|
||||||
participantId,
|
|
||||||
user,
|
|
||||||
ChatRestrictionsInfo(),
|
|
||||||
oldRestrictions);
|
|
||||||
} else if (oldParticipant
|
|
||||||
&& oldParticipant->type() == Type::Restricted
|
|
||||||
&& (participant.type() == Type::Member
|
|
||||||
|| participant.type() == Type::Left)) {
|
|
||||||
return GeneratePermissionsChangeText(
|
|
||||||
participantId,
|
|
||||||
user,
|
|
||||||
ChatRestrictionsInfo(),
|
|
||||||
oldRestrictions);
|
|
||||||
}
|
|
||||||
return tr::lng_admin_log_invited(
|
|
||||||
tr::now,
|
|
||||||
lt_user,
|
|
||||||
user,
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto result = [&]
|
|
||||||
{
|
|
||||||
const auto &peerId = participant.id();
|
|
||||||
switch (participant.type()) {
|
|
||||||
case Api::ChatParticipant::Type::Creator: {
|
|
||||||
// No valid string here :(
|
|
||||||
const auto user = GenerateParticipantString(
|
|
||||||
&channel->session(),
|
|
||||||
peerId);
|
|
||||||
if (peerId == channel->session().userPeerId()) {
|
|
||||||
return GenerateAdminChangeText(
|
|
||||||
channel,
|
|
||||||
user,
|
|
||||||
participant.rights(),
|
|
||||||
oldRights);
|
|
||||||
}
|
|
||||||
return tr::lng_admin_log_transferred(
|
|
||||||
tr::now,
|
|
||||||
lt_user,
|
|
||||||
user,
|
|
||||||
Ui::Text::WithEntities);
|
|
||||||
}
|
|
||||||
case Api::ChatParticipant::Type::Admin: {
|
|
||||||
const auto user = GenerateParticipantString(
|
|
||||||
&channel->session(),
|
|
||||||
peerId);
|
|
||||||
return GenerateAdminChangeText(
|
|
||||||
channel,
|
|
||||||
user,
|
|
||||||
participant.rights(),
|
|
||||||
oldRights);
|
|
||||||
}
|
|
||||||
case Api::ChatParticipant::Type::Restricted:
|
|
||||||
case Api::ChatParticipant::Type::Banned: {
|
|
||||||
const auto user = GenerateParticipantString(
|
|
||||||
&channel->session(),
|
|
||||||
peerId);
|
|
||||||
return GeneratePermissionsChangeText(
|
|
||||||
peerId,
|
|
||||||
user,
|
|
||||||
participant.restrictions(),
|
|
||||||
oldRestrictions);
|
|
||||||
}
|
|
||||||
case Api::ChatParticipant::Type::Left:
|
|
||||||
case Api::ChatParticipant::Type::Member: return generateOther(peerId);
|
|
||||||
};
|
|
||||||
Unexpected("Participant type in GenerateParticipantChangeText.");
|
|
||||||
}();
|
|
||||||
|
|
||||||
result.entities.push_front(
|
|
||||||
EntityInText(EntityType::Italic, 0, result.text.size()));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TextWithEntities GenerateParticipantChangeText(
|
|
||||||
not_null<ChannelData*> channel,
|
|
||||||
const MTPChannelParticipant &participant,
|
|
||||||
std::optional<MTPChannelParticipant> oldParticipant = std::nullopt) {
|
|
||||||
return GenerateParticipantChangeText(
|
|
||||||
channel,
|
|
||||||
Api::ChatParticipant(participant, channel),
|
|
||||||
oldParticipant
|
|
||||||
? std::make_optional(Api::ChatParticipant(
|
|
||||||
*oldParticipant,
|
|
||||||
channel))
|
|
||||||
: std::nullopt);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextWithEntities GenerateDefaultBannedRightsChangeText(
|
|
||||||
not_null<ChannelData*> channel,
|
|
||||||
ChatRestrictionsInfo rights,
|
|
||||||
ChatRestrictionsInfo oldRights) {
|
|
||||||
auto result = TextWithEntities{
|
|
||||||
tr::lng_admin_log_changed_default_permissions(tr::now)
|
|
||||||
};
|
|
||||||
const auto changes = GeneratePermissionsChangeText(rights, oldRights);
|
|
||||||
if (!changes.isEmpty()) {
|
|
||||||
result.text.append('\n' + changes);
|
|
||||||
}
|
|
||||||
result.entities.push_front(
|
|
||||||
EntityInText(EntityType::Italic, 0, result.text.size()));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsTopicClosed(const MTPForumTopic &topic) {
|
|
||||||
return topic.match([](const MTPDforumTopic &data)
|
|
||||||
{
|
|
||||||
return data.is_closed();
|
|
||||||
},
|
|
||||||
[](const MTPDforumTopicDeleted &)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsTopicHidden(const MTPForumTopic &topic) {
|
|
||||||
return topic.match([](const MTPDforumTopic &data)
|
|
||||||
{
|
|
||||||
return data.is_hidden();
|
|
||||||
},
|
|
||||||
[](const MTPDforumTopicDeleted &)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] TextWithEntities GenerateTopicLink(
|
|
||||||
not_null<ChannelData*> channel,
|
|
||||||
const MTPForumTopic &topic) {
|
|
||||||
return topic.match([&](const MTPDforumTopic &data)
|
|
||||||
{
|
|
||||||
return Ui::Text::Link(
|
|
||||||
Data::ForumTopicIconWithTitle(
|
|
||||||
data.vid().v,
|
|
||||||
data.vicon_emoji_id().value_or_empty(),
|
|
||||||
qs(data.vtitle())),
|
|
||||||
u"internal:url:https://t.me/c/%1/%2"_q.arg(
|
|
||||||
peerToChannel(channel->id).bare).arg(
|
|
||||||
data.vid().v));
|
|
||||||
},
|
|
||||||
[](const MTPDforumTopicDeleted &)
|
|
||||||
{
|
|
||||||
return TextWithEntities{u"Deleted"_q};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
OwnedItem::OwnedItem(std::nullptr_t) {
|
|
||||||
}
|
|
||||||
|
|
||||||
OwnedItem::OwnedItem(
|
|
||||||
not_null<HistoryView::ElementDelegate*> delegate,
|
|
||||||
not_null<HistoryItem*> data)
|
|
||||||
: _data(data), _view(_data->createView(delegate)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
OwnedItem::OwnedItem(OwnedItem &&other)
|
|
||||||
: _data(base::take(other._data)), _view(base::take(other._view)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
OwnedItem &OwnedItem::operator=(OwnedItem &&other) {
|
|
||||||
_data = base::take(other._data);
|
|
||||||
_view = base::take(other._view);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
OwnedItem::~OwnedItem() {
|
|
||||||
clearView();
|
|
||||||
if (_data) {
|
|
||||||
_data->destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void OwnedItem::refreshView(
|
|
||||||
not_null<HistoryView::ElementDelegate*> delegate) {
|
|
||||||
_view = _data->createView(delegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OwnedItem::clearView() {
|
|
||||||
_view = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GenerateItems(
|
|
||||||
not_null<HistoryView::ElementDelegate*> delegate,
|
|
||||||
not_null<History*> history,
|
|
||||||
EditedMessage message,
|
|
||||||
Fn<void(OwnedItem item, TimeId sentDate, MsgId)> callback) {
|
|
||||||
PeerData *from = history->owner().userLoaded(message.fromId);
|
|
||||||
if (!from) {
|
|
||||||
from = history->owner().channelLoaded(message.fromId);
|
|
||||||
}
|
|
||||||
if (!from) {
|
|
||||||
from = reinterpret_cast<PeerData*>(history->owner().chatLoaded(message.fromId));
|
|
||||||
}
|
|
||||||
if (!from) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto date = message.entityCreateDate;
|
|
||||||
const auto addPart = [&](
|
|
||||||
not_null<HistoryItem*> item,
|
|
||||||
TimeId sentDate = 0,
|
|
||||||
MsgId realId = MsgId())
|
|
||||||
{
|
|
||||||
return callback(OwnedItem(delegate, item), sentDate, realId);
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto fromName = from->name();
|
|
||||||
const auto fromLink = from->createOpenLink();
|
|
||||||
const auto fromLinkText = Ui::Text::Link(fromName, QString());
|
|
||||||
|
|
||||||
const auto makeSimpleTextMessage = [&](TextWithEntities &&text)
|
|
||||||
{
|
|
||||||
return history->makeMessage({
|
|
||||||
.id = history->nextNonHistoryEntryId(),
|
|
||||||
.flags = MessageFlag::HasFromId | MessageFlag::AdminLogEntry,
|
|
||||||
.from = from->id,
|
|
||||||
.date = date,
|
|
||||||
}, std::move(text), MTP_messageMediaEmpty());
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto addSimpleTextMessage = [&](TextWithEntities &&text)
|
|
||||||
{
|
|
||||||
addPart(makeSimpleTextMessage(std::move(text)));
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto text = QString::fromStdString(message.text);
|
|
||||||
addSimpleTextMessage(Ui::Text::WithEntities(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace EditedLog
|
|
|
@ -2549,23 +2549,25 @@ void Session::processMessagesDeleted(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto settings = &AyuSettings::getInstance();
|
||||||
|
|
||||||
auto historiesToCheck = base::flat_set<not_null<History*>>();
|
auto historiesToCheck = base::flat_set<not_null<History*>>();
|
||||||
for (const auto &messageId : data) {
|
for (const auto &messageId : data) {
|
||||||
const auto i = list ? list->find(messageId.v) : Messages::iterator();
|
const auto i = list ? list->find(messageId.v) : Messages::iterator();
|
||||||
if (list && i != list->end()) {
|
if (list && i != list->end()) {
|
||||||
const auto history = i->second->history();
|
const auto history = i->second->history();
|
||||||
|
|
||||||
const auto settings = &AyuSettings::getInstance();
|
|
||||||
if (!settings->saveDeletedMessages) {
|
if (!settings->saveDeletedMessages) {
|
||||||
i->second->destroy();
|
i->second->destroy();
|
||||||
} else {
|
} else {
|
||||||
i->second->setAyuHint(settings->deletedMark);
|
i->second->setAyuHint(settings->deletedMark);
|
||||||
|
AyuMessages::addDeletedMessage(i->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!history->chatListMessageKnown()) {
|
if (!history->chatListMessageKnown() && !settings->saveDeletedMessages) {
|
||||||
historiesToCheck.emplace(history);
|
historiesToCheck.emplace(history);
|
||||||
}
|
}
|
||||||
} else if (affected) {
|
} else if (affected && !settings->saveDeletedMessages) {
|
||||||
affected->unknownMessageDeleted(messageId.v);
|
affected->unknownMessageDeleted(messageId.v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2575,19 +2577,21 @@ void Session::processMessagesDeleted(
|
||||||
}
|
}
|
||||||
|
|
||||||
void Session::processNonChannelMessagesDeleted(const QVector<MTPint> &data) {
|
void Session::processNonChannelMessagesDeleted(const QVector<MTPint> &data) {
|
||||||
|
const auto settings = &AyuSettings::getInstance();
|
||||||
|
|
||||||
auto historiesToCheck = base::flat_set<not_null<History*>>();
|
auto historiesToCheck = base::flat_set<not_null<History*>>();
|
||||||
for (const auto &messageId : data) {
|
for (const auto &messageId : data) {
|
||||||
if (const auto item = nonChannelMessage(messageId.v)) {
|
if (const auto item = nonChannelMessage(messageId.v)) {
|
||||||
const auto history = item->history();
|
const auto history = item->history();
|
||||||
|
|
||||||
const auto settings = &AyuSettings::getInstance();
|
|
||||||
if (!settings->saveDeletedMessages) {
|
if (!settings->saveDeletedMessages) {
|
||||||
item->destroy();
|
item->destroy();
|
||||||
} else {
|
} else {
|
||||||
item->setAyuHint(settings->deletedMark);
|
item->setAyuHint(settings->deletedMark);
|
||||||
|
AyuMessages::addDeletedMessage(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!history->chatListMessageKnown()) {
|
if (!history->chatListMessageKnown() && !settings->saveDeletedMessages) {
|
||||||
historiesToCheck.emplace(history);
|
historiesToCheck.emplace(history);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
// AyuGram includes
|
// AyuGram includes
|
||||||
#include "styles/style_ayu_icons.h"
|
#include "styles/style_ayu_icons.h"
|
||||||
|
#include "ayu/ui/context_menu/context_menu.h"
|
||||||
|
|
||||||
|
|
||||||
namespace Window {
|
namespace Window {
|
||||||
|
@ -1505,6 +1506,7 @@ void Filler::fillHistoryActions() {
|
||||||
addExportChat();
|
addExportChat();
|
||||||
addTranslate();
|
addTranslate();
|
||||||
addReport();
|
addReport();
|
||||||
|
AyuUi::AddDeletedMessagesActions(_peer, _controller, _request, _addAction);
|
||||||
addClearHistory();
|
addClearHistory();
|
||||||
addDeleteChat();
|
addDeleteChat();
|
||||||
addLeaveChat();
|
addLeaveChat();
|
||||||
|
|
Loading…
Add table
Reference in a new issue