diff --git a/Telegram/Resources/export_html/css/style.css b/Telegram/Resources/export_html/css/style.css
index 63c444d2a..31136ab9c 100644
--- a/Telegram/Resources/export_html/css/style.css
+++ b/Telegram/Resources/export_html/css/style.css
@@ -2,6 +2,34 @@ body {
margin: 0;
font: 12px/18px 'Open Sans',"Lucida Grande","Lucida Sans Unicode",Arial,Helvetica,Verdana,sans-serif;
}
+strong {
+ font-weight: 700;
+}
+code, kbd, pre, samp {
+ font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
+}
+code {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #c7254e;
+ background-color: #f9f2f4;
+ border-radius: 4px;
+}
+pre {
+ display: block;
+ margin: 0;
+ line-height: 1.42857143;
+ word-break: break-all;
+ word-wrap: break-word;
+ color: #333;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ overflow: auto;
+ padding: 3px;
+ border: 1px solid #eee;
+ max-height: none;
+ font-size: inherit;
+}
.clearfix:after {
content: " ";
visibility: hidden;
@@ -38,13 +66,13 @@ body {
border-radius: 0 !important;
}
.page_header a.content {
- background-image: url(../images/back.png);
background-repeat: no-repeat;
background-position: 24px 21px;
background-size: 24px 24px;
}
.bold {
color: #212121;
+ font-weight: 700;
}
.details {
color: #70777b;
@@ -52,7 +80,6 @@ body {
.page_header .content .text {
padding: 24px 24px 22px 24px;
font-size: 22px;
- font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -90,28 +117,47 @@ body {
text-transform: uppercase;
user-select: none;
}
-.userpic1 {
+.color_red,
+.userpic1,
+.media_call .thumb,
+.media_file .thumb,
+.media_live_location .thumb {
background-color: #ff5555;
}
-.userpic2 {
+.color_green,
+.userpic2,
+.media_call.success .thumb {
background-color: #64bf47;
}
-.userpic3 {
+.color_yellow,
+.userpic3,
+.media_venue .thumb {
background-color: #ffab00;
}
-.userpic4 {
+.color_blue,
+.userpic4,
+.media_audio_file .thumb,
+.media_voice_message .thumb {
background-color: #4f9cd9;
}
-.userpic5 {
+.color_purple,
+.userpic5,
+.media_game .thumb {
background-color: #9884e8;
}
-.userpic6 {
+.color_pink,
+.userpic6,
+.media_invoice .thumb {
background-color: #e671a5;
}
-.userpic7 {
+.color_sea,
+.userpic7,
+.media_location .thumb {
background-color: #47bcd1;
}
-.userpic8 {
+.color_orange,
+.userpic8,
+.media_contact .thumb {
background-color: #ff8c44;
}
.personal_info {
@@ -162,54 +208,6 @@ a.block_link:hover {
.section .label {
padding: 15px 0 0 82px;
font-size: 15px;
- font-weight: 700;
-}
-.section.calls {
- background-image: url(../images/calls.png);
-}
-.section.chats {
- background-image: url(../images/chats.png);
-}
-.section.contacts {
- background-image: url(../images/contacts.png);
-}
-.section.frequent {
- background-image: url(../images/frequent.png);
-}
-.section.photos {
- background-image: url(../images/photos.png);
-}
-.section.sessions {
- background-image: url(../images/sessions.png);
-}
-.section.web {
- background-image: url(../images/web.png);
-}
-@media only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) {
-.section.calls {
- background-image: url(../images/calls@2x.png);
-}
-.section.chats {
- background-image: url(../images/chats@2x.png);
-}
-.section.contacts {
- background-image: url(../images/contacts@2x.png);
-}
-.section.frequent {
- background-image: url(../images/frequent@2x.png);
-}
-.section.photos {
- background-image: url(../images/photos@2x.png);
-}
-.section.sessions {
- background-image: url(../images/sessions@2x.png);
-}
-.section.web {
- background-image: url(../images/web@2x.png);
-}
-.page_header a.content {
- background-image: url(../images/back@2x.png);
-}
}
.list_page .page_about {
padding: 16px 24px 0;
@@ -229,7 +227,6 @@ a.block_link:hover {
}
.list_page .entry .name {
padding: 4px 0 2px;
- font-weight: 700;
font-size: 14px;
}
.list_page .entry .subname {
@@ -248,7 +245,7 @@ a.block_link:hover {
.service {
padding: 10px 24px;
}
-.service .content {
+.service .body {
text-align: center;
}
.service .userpic_wrap {
@@ -260,3 +257,172 @@ a.block_link:hover {
.service .userpic .initials {
font-size: 24px;
}
+.message .userpic .initials {
+ font-size: 16px;
+}
+.default {
+ padding: 10px 0 10px;
+}
+.default.joined {
+ padding-top: 0;
+}
+.default .from_name {
+ color: #3892db;
+ font-weight: 700;
+ padding-bottom: 5px;
+}
+.default .from_name .details {
+ font-weight: normal;
+}
+.default .body {
+ margin-left: 60px;
+}
+.default .text {
+ word-wrap: break-word;
+ line-height: 150%;
+}
+.default .reply_to,
+.default .media_wrap {
+ padding-bottom: 5px;
+}
+.default .media {
+ margin: 0 -10px;
+ padding: 5px 10px;
+}
+.default .media .thumb {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ background-repeat: no-repeat;
+ background-position: 12px 12px;
+ background-size: 24px 24px;
+}
+.default .media .title {
+ padding-top: 4px;
+ font-size: 14px;
+}
+.default .media .description {
+ color: #000000;
+ padding-top: 4px;
+ font-size: 13px;
+}
+.default .media .status {
+ padding-top: 4px;
+ font-size: 13px;
+}
+
+.section.calls {
+ background-image: url(../images/section_calls.png);
+}
+.section.chats {
+ background-image: url(../images/section_chats.png);
+}
+.section.contacts {
+ background-image: url(../images/section_contacts.png);
+}
+.section.frequent {
+ background-image: url(../images/section_frequent.png);
+}
+.section.photos {
+ background-image: url(../images/section_photos.png);
+}
+.section.sessions {
+ background-image: url(../images/section_sessions.png);
+}
+.section.web {
+ background-image: url(../images/section_web.png);
+}
+.section.leftchats {
+ background-image: url(../images/section_leftchats.png);
+}
+.section.other {
+ background-image: url(../images/section_other.png)
+}
+.page_header a.content {
+ background-image: url(../images/back.png);
+}
+.media_call .thumb {
+ background-image: url(../images/media_call.png)
+}
+.media_contact .thumb {
+ background-image: url(../images/media_contact.png)
+}
+.media_file .thumb {
+ background-image: url(../images/media_file.png)
+}
+.media_game .thumb {
+ background-image: url(../images/media_game.png)
+}
+.media_live_location .thumb,
+.media_location .thumb,
+.media_venue .thumb {
+ background-image: url(../images/media_location.png)
+}
+.media_audio_file .thumb {
+ background-image: url(../images/media_music.png)
+}
+.media_invoice .thumb {
+ background-image: url(../images/media_shop.png)
+}
+.media_voice_message .thumb {
+ background-image: url(../images/media_voice.png)
+}
+
+@media only screen and (min--moz-device-pixel-ratio: 2), only screen and (-o-min-device-pixel-ratio: 2/1), only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2) {
+.section.calls {
+ background-image: url(../images/section_calls@2x.png);
+}
+.section.chats {
+ background-image: url(../images/section_chats@2x.png);
+}
+.section.contacts {
+ background-image: url(../images/section_contacts@2x.png);
+}
+.section.frequent {
+ background-image: url(../images/section_frequent@2x.png);
+}
+.section.photos {
+ background-image: url(../images/section_photos@2x.png);
+}
+.section.sessions {
+ background-image: url(../images/section_sessions@2x.png);
+}
+.section.web {
+ background-image: url(../images/section_web@2x.png);
+}
+.section.leftchats {
+ background-image: url(../images/section_leftchats@2x.png);
+}
+.section.other {
+ background-image: url(../images/section_other@2x.png);
+}
+.page_header a.content {
+ background-image: url(../images/back@2x.png);
+}
+.media_call .thumb {
+ background-image: url(../images/media_call@2x.png)
+}
+.media_contact .thumb {
+ background-image: url(../images/media_contact@2x.png)
+}
+.media_file .thumb {
+ background-image: url(../images/media_file@2x.png)
+}
+.media_game .thumb {
+ background-image: url(../images/media_game@2x.png)
+}
+.media_live_location .thumb,
+.media_location .thumb,
+.media_venue .thumb {
+ background-image: url(../images/media_location@2x.png)
+}
+.media_audio_file .thumb {
+ background-image: url(../images/media_music@2x.png)
+}
+.media_invoice .thumb {
+ background-image: url(../images/media_shop@2x.png)
+}
+.media_voice_message .thumb {
+ background-image: url(../images/media_voice@2x.png)
+}
+}
diff --git a/Telegram/Resources/export_html/images/media_call.png b/Telegram/Resources/export_html/images/media_call.png
new file mode 100644
index 000000000..9614b9562
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_call.png differ
diff --git a/Telegram/Resources/export_html/images/media_call@2x.png b/Telegram/Resources/export_html/images/media_call@2x.png
new file mode 100644
index 000000000..92cdbe618
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_call@2x.png differ
diff --git a/Telegram/Resources/export_html/images/media_contact.png b/Telegram/Resources/export_html/images/media_contact.png
new file mode 100644
index 000000000..be79172d5
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_contact.png differ
diff --git a/Telegram/Resources/export_html/images/media_contact@2x.png b/Telegram/Resources/export_html/images/media_contact@2x.png
new file mode 100644
index 000000000..e4be8b037
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_contact@2x.png differ
diff --git a/Telegram/Resources/export_html/images/media_file.png b/Telegram/Resources/export_html/images/media_file.png
new file mode 100644
index 000000000..3a5bde66c
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_file.png differ
diff --git a/Telegram/Resources/export_html/images/media_file@2x.png b/Telegram/Resources/export_html/images/media_file@2x.png
new file mode 100644
index 000000000..ad2c6cc2c
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_file@2x.png differ
diff --git a/Telegram/Resources/export_html/images/media_game.png b/Telegram/Resources/export_html/images/media_game.png
new file mode 100644
index 000000000..cad8ab854
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_game.png differ
diff --git a/Telegram/Resources/export_html/images/media_game@2x.png b/Telegram/Resources/export_html/images/media_game@2x.png
new file mode 100644
index 000000000..09c2eb395
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_game@2x.png differ
diff --git a/Telegram/Resources/export_html/images/media_location.png b/Telegram/Resources/export_html/images/media_location.png
new file mode 100644
index 000000000..ab8080b7e
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_location.png differ
diff --git a/Telegram/Resources/export_html/images/media_location@2x.png b/Telegram/Resources/export_html/images/media_location@2x.png
new file mode 100644
index 000000000..11d88d529
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_location@2x.png differ
diff --git a/Telegram/Resources/export_html/images/media_music.png b/Telegram/Resources/export_html/images/media_music.png
new file mode 100644
index 000000000..057a694b4
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_music.png differ
diff --git a/Telegram/Resources/export_html/images/media_music@2x.png b/Telegram/Resources/export_html/images/media_music@2x.png
new file mode 100644
index 000000000..20c805644
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_music@2x.png differ
diff --git a/Telegram/Resources/export_html/images/media_shop.png b/Telegram/Resources/export_html/images/media_shop.png
new file mode 100644
index 000000000..4a92ce83e
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_shop.png differ
diff --git a/Telegram/Resources/export_html/images/media_shop@2x.png b/Telegram/Resources/export_html/images/media_shop@2x.png
new file mode 100644
index 000000000..9cfe5512b
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_shop@2x.png differ
diff --git a/Telegram/Resources/export_html/images/media_voice.png b/Telegram/Resources/export_html/images/media_voice.png
new file mode 100644
index 000000000..af33c2ed8
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_voice.png differ
diff --git a/Telegram/Resources/export_html/images/media_voice@2x.png b/Telegram/Resources/export_html/images/media_voice@2x.png
new file mode 100644
index 000000000..5356a377c
Binary files /dev/null and b/Telegram/Resources/export_html/images/media_voice@2x.png differ
diff --git a/Telegram/Resources/export_html/images/calls.png b/Telegram/Resources/export_html/images/section_calls.png
similarity index 100%
rename from Telegram/Resources/export_html/images/calls.png
rename to Telegram/Resources/export_html/images/section_calls.png
diff --git a/Telegram/Resources/export_html/images/calls@2x.png b/Telegram/Resources/export_html/images/section_calls@2x.png
similarity index 100%
rename from Telegram/Resources/export_html/images/calls@2x.png
rename to Telegram/Resources/export_html/images/section_calls@2x.png
diff --git a/Telegram/Resources/export_html/images/chats.png b/Telegram/Resources/export_html/images/section_chats.png
similarity index 100%
rename from Telegram/Resources/export_html/images/chats.png
rename to Telegram/Resources/export_html/images/section_chats.png
diff --git a/Telegram/Resources/export_html/images/chats@2x.png b/Telegram/Resources/export_html/images/section_chats@2x.png
similarity index 100%
rename from Telegram/Resources/export_html/images/chats@2x.png
rename to Telegram/Resources/export_html/images/section_chats@2x.png
diff --git a/Telegram/Resources/export_html/images/contacts.png b/Telegram/Resources/export_html/images/section_contacts.png
similarity index 100%
rename from Telegram/Resources/export_html/images/contacts.png
rename to Telegram/Resources/export_html/images/section_contacts.png
diff --git a/Telegram/Resources/export_html/images/contacts@2x.png b/Telegram/Resources/export_html/images/section_contacts@2x.png
similarity index 100%
rename from Telegram/Resources/export_html/images/contacts@2x.png
rename to Telegram/Resources/export_html/images/section_contacts@2x.png
diff --git a/Telegram/Resources/export_html/images/frequent.png b/Telegram/Resources/export_html/images/section_frequent.png
similarity index 100%
rename from Telegram/Resources/export_html/images/frequent.png
rename to Telegram/Resources/export_html/images/section_frequent.png
diff --git a/Telegram/Resources/export_html/images/frequent@2x.png b/Telegram/Resources/export_html/images/section_frequent@2x.png
similarity index 100%
rename from Telegram/Resources/export_html/images/frequent@2x.png
rename to Telegram/Resources/export_html/images/section_frequent@2x.png
diff --git a/Telegram/Resources/export_html/images/section_leftchats.png b/Telegram/Resources/export_html/images/section_leftchats.png
new file mode 100644
index 000000000..350d22cd4
Binary files /dev/null and b/Telegram/Resources/export_html/images/section_leftchats.png differ
diff --git a/Telegram/Resources/export_html/images/section_leftchats@2x.png b/Telegram/Resources/export_html/images/section_leftchats@2x.png
new file mode 100644
index 000000000..b33520d10
Binary files /dev/null and b/Telegram/Resources/export_html/images/section_leftchats@2x.png differ
diff --git a/Telegram/Resources/export_html/images/section_other.png b/Telegram/Resources/export_html/images/section_other.png
new file mode 100644
index 000000000..a60ed7a40
Binary files /dev/null and b/Telegram/Resources/export_html/images/section_other.png differ
diff --git a/Telegram/Resources/export_html/images/section_other@2x.png b/Telegram/Resources/export_html/images/section_other@2x.png
new file mode 100644
index 000000000..497fb3338
Binary files /dev/null and b/Telegram/Resources/export_html/images/section_other@2x.png differ
diff --git a/Telegram/Resources/export_html/images/photos.png b/Telegram/Resources/export_html/images/section_photos.png
similarity index 100%
rename from Telegram/Resources/export_html/images/photos.png
rename to Telegram/Resources/export_html/images/section_photos.png
diff --git a/Telegram/Resources/export_html/images/photos@2x.png b/Telegram/Resources/export_html/images/section_photos@2x.png
similarity index 100%
rename from Telegram/Resources/export_html/images/photos@2x.png
rename to Telegram/Resources/export_html/images/section_photos@2x.png
diff --git a/Telegram/Resources/export_html/images/sessions.png b/Telegram/Resources/export_html/images/section_sessions.png
similarity index 100%
rename from Telegram/Resources/export_html/images/sessions.png
rename to Telegram/Resources/export_html/images/section_sessions.png
diff --git a/Telegram/Resources/export_html/images/sessions@2x.png b/Telegram/Resources/export_html/images/section_sessions@2x.png
similarity index 100%
rename from Telegram/Resources/export_html/images/sessions@2x.png
rename to Telegram/Resources/export_html/images/section_sessions@2x.png
diff --git a/Telegram/Resources/export_html/images/web.png b/Telegram/Resources/export_html/images/section_web.png
similarity index 100%
rename from Telegram/Resources/export_html/images/web.png
rename to Telegram/Resources/export_html/images/section_web.png
diff --git a/Telegram/Resources/export_html/images/web@2x.png b/Telegram/Resources/export_html/images/section_web@2x.png
similarity index 100%
rename from Telegram/Resources/export_html/images/web@2x.png
rename to Telegram/Resources/export_html/images/section_web@2x.png
diff --git a/Telegram/Resources/qrc/telegram.qrc b/Telegram/Resources/qrc/telegram.qrc
index ab56db558..00763851f 100644
--- a/Telegram/Resources/qrc/telegram.qrc
+++ b/Telegram/Resources/qrc/telegram.qrc
@@ -3,20 +3,40 @@
../export_html/css/style.css
../export_html/images/back.png
../export_html/images/back@2x.png
- ../export_html/images/calls.png
- ../export_html/images/calls@2x.png
- ../export_html/images/chats.png
- ../export_html/images/chats@2x.png
- ../export_html/images/contacts.png
- ../export_html/images/contacts@2x.png
- ../export_html/images/frequent.png
- ../export_html/images/frequent@2x.png
- ../export_html/images/photos.png
- ../export_html/images/photos@2x.png
- ../export_html/images/sessions.png
- ../export_html/images/sessions@2x.png
- ../export_html/images/web.png
- ../export_html/images/web@2x.png
+ ../export_html/images/media_call.png
+ ../export_html/images/media_call@2x.png
+ ../export_html/images/media_contact.png
+ ../export_html/images/media_contact@2x.png
+ ../export_html/images/media_file.png
+ ../export_html/images/media_file@2x.png
+ ../export_html/images/media_game.png
+ ../export_html/images/media_game@2x.png
+ ../export_html/images/media_location.png
+ ../export_html/images/media_location@2x.png
+ ../export_html/images/media_music.png
+ ../export_html/images/media_music@2x.png
+ ../export_html/images/media_shop.png
+ ../export_html/images/media_shop@2x.png
+ ../export_html/images/media_voice.png
+ ../export_html/images/media_voice@2x.png
+ ../export_html/images/section_calls.png
+ ../export_html/images/section_calls@2x.png
+ ../export_html/images/section_chats.png
+ ../export_html/images/section_chats@2x.png
+ ../export_html/images/section_contacts.png
+ ../export_html/images/section_contacts@2x.png
+ ../export_html/images/section_frequent.png
+ ../export_html/images/section_frequent@2x.png
+ ../export_html/images/section_leftchats.png
+ ../export_html/images/section_leftchats@2x.png
+ ../export_html/images/section_other.png
+ ../export_html/images/section_other@2x.png
+ ../export_html/images/section_photos.png
+ ../export_html/images/section_photos@2x.png
+ ../export_html/images/section_sessions.png
+ ../export_html/images/section_sessions@2x.png
+ ../export_html/images/section_web.png
+ ../export_html/images/section_web@2x.png
../fonts/OpenSans-Regular.ttf
diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp
index c513494fd..eba6f47c4 100644
--- a/Telegram/SourceFiles/export/data/export_data_types.cpp
+++ b/Telegram/SourceFiles/export/data/export_data_types.cpp
@@ -20,6 +20,7 @@ QString formatPhone(QString phone);
} // namespace App
QString FillAmountAndCurrency(uint64 amount, const QString ¤cy);
QString formatSizeText(qint64 size);
+QString formatDurationText(qint64 duration);
namespace Export {
namespace Data {
@@ -940,19 +941,32 @@ Message ParseMessage(
const MTPMessage &data,
const QString &mediaFolder) {
auto result = Message();
- data.match([&](const MTPDmessage &data) {
+ data.match([&](const auto &data) {
result.id = data.vid.v;
- const auto peerId = ParsePeerId(data.vto_id);
- if (IsChatPeerId(peerId)) {
- result.chatId = BarePeerId(peerId);
+ if constexpr (!MTPDmessageEmpty::Is()) {
+ result.toId = ParsePeerId(data.vto_id);
+ const auto peerId = (!data.is_out()
+ && data.has_from_id()
+ && data.vto_id.type() == mtpc_peerUser)
+ ? UserPeerId(data.vfrom_id.v)
+ : result.toId;
+ if (IsChatPeerId(peerId)) {
+ result.chatId = BarePeerId(peerId);
+ }
+ if (data.has_from_id()) {
+ result.fromId = data.vfrom_id.v;
+ }
+ if (data.has_reply_to_msg_id()) {
+ result.replyToMsgId = data.vreply_to_msg_id.v;
+ }
+ result.date = data.vdate.v;
+ result.out = data.is_out();
}
- result.date = data.vdate.v;
+ });
+ data.match([&](const MTPDmessage &data) {
if (data.has_edit_date()) {
result.edited = data.vedit_date.v;
}
- if (data.has_from_id()) {
- result.fromId = data.vfrom_id.v;
- }
if (data.has_fwd_from()) {
result.forwardedFromId = data.vfwd_from.match(
[](const MTPDmessageFwdHeader &data) {
@@ -963,6 +977,10 @@ Message ParseMessage(
}
return PeerId(0);
});
+ result.forwardedDate = data.vfwd_from.match(
+ [](const MTPDmessageFwdHeader &data) {
+ return data.vdate.v;
+ });
result.savedFromChatId = data.vfwd_from.match(
[](const MTPDmessageFwdHeader &data) {
if (data.has_saved_from_peer()) {
@@ -998,22 +1016,10 @@ Message ParseMessage(
? data.ventities.v
: QVector{}));
}, [&](const MTPDmessageService &data) {
- result.id = data.vid.v;
- const auto peerId = ParsePeerId(data.vto_id);
- if (IsChatPeerId(peerId)) {
- result.chatId = BarePeerId(peerId);
- }
- result.date = data.vdate.v;
result.action = ParseServiceAction(
context,
data.vaction,
mediaFolder);
- if (data.has_from_id()) {
- result.fromId = data.vfrom_id.v;
- }
- if (data.has_reply_to_msg_id()) {
- result.replyToMsgId = data.vreply_to_msg_id.v;
- }
}, [&](const MTPDmessageEmpty &data) {
result.id = data.vid.v;
});
@@ -1373,9 +1379,9 @@ Utf8String FormatDateTime(
const auto value = QDateTime::fromTime_t(date);
return (QString("%1") + dateSeparator + "%2" + dateSeparator + "%3"
+ separator + "%4" + timeSeparator + "%5" + timeSeparator + "%6"
- ).arg(value.date().year()
- ).arg(value.date().month(), 2, 10, QChar('0')
).arg(value.date().day(), 2, 10, QChar('0')
+ ).arg(value.date().month(), 2, 10, QChar('0')
+ ).arg(value.date().year()
).arg(value.time().hour(), 2, 10, QChar('0')
).arg(value.time().minute(), 2, 10, QChar('0')
).arg(value.time().second(), 2, 10, QChar('0')
@@ -1392,5 +1398,9 @@ Utf8String FormatFileSize(int64 size) {
return formatSizeText(size).toUtf8();
}
+Utf8String FormatDuration(int64 seconds) {
+ return formatDurationText(seconds).toUtf8();
+}
+
} // namespace Data
} // namespace Export
diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h
index 7449f934a..7facf8ec7 100644
--- a/Telegram/SourceFiles/export/data/export_data_types.h
+++ b/Telegram/SourceFiles/export/data/export_data_types.h
@@ -26,6 +26,8 @@ using PeerId = uint64;
PeerId UserPeerId(int32 userId);
PeerId ChatPeerId(int32 chatId);
int32 BarePeerId(PeerId peerId);
+bool IsChatPeerId(PeerId peerId);
+bool IsUserPeerId(PeerId peerId);
int PeerColorIndex(int32 bareId);
int ApplicationColorIndex(int applicationId);
int DomainApplicationId(const Utf8String &data);
@@ -462,7 +464,9 @@ struct Message {
TimeId date = 0;
TimeId edited = 0;
int32 fromId = 0;
+ PeerId toId = 0;
PeerId forwardedFromId = 0;
+ TimeId forwardedDate = 0;
PeerId savedFromChatId = 0;
Utf8String signature;
int32 viaBotId = 0;
@@ -470,6 +474,7 @@ struct Message {
std::vector text;
Media media;
ServiceAction action;
+ bool out = false;
File &file();
const File &file() const;
@@ -546,6 +551,7 @@ Utf8String FormatDateTime(
QChar separator = QChar(' '));
Utf8String FormatMoneyAmount(uint64 amount, const Utf8String ¤cy);
Utf8String FormatFileSize(int64 size);
+Utf8String FormatDuration(int64 seconds);
} // namespace Data
} // namespace Export
diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.cpp b/Telegram/SourceFiles/export/output/export_output_abstract.cpp
index 4d8c20881..28c9831ed 100644
--- a/Telegram/SourceFiles/export/output/export_output_abstract.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_abstract.cpp
@@ -197,7 +197,11 @@ Stats AbstractWriter::produceTestExample(
message.id = counter();
message.date = prevdate();
message.edited = date();
- message.forwardedFromId = user.info.userId;
+ static auto count = 0;
+ if (++count % 3 == 0) {
+ message.forwardedFromId = Data::UserPeerId(user.info.userId);
+ message.forwardedDate = date();
+ }
message.fromId = user.info.userId;
message.replyToMsgId = counter();
message.viaBotId = bot.info.userId;
@@ -485,6 +489,5 @@ Stats AbstractWriter::produceTestExample(
return result;
}
-
} // namespace Output
} // namespace Export
diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp
index 0acaf11c2..e226fc0fd 100644
--- a/Telegram/SourceFiles/export/output/export_output_html.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_html.cpp
@@ -22,12 +22,21 @@ constexpr auto kMessagesInFile = 1000;
constexpr auto kPersonalUserpicSize = 90;
constexpr auto kEntryUserpicSize = 48;
constexpr auto kServiceMessagePhotoSize = 60;
+constexpr auto kHistoryUserpicSize = 42;
constexpr auto kSavedMessagesColorIndex = 3;
+constexpr auto kJoinWithinSeconds = 900;
const auto kLineBreak = QByteArrayLiteral("
");
using Context = details::HtmlContext;
using UserpicData = details::UserpicData;
+using PeersMap = details::PeersMap;
+using MediaData = details::MediaData;
+
+bool IsGlobalLink(const QString &link) {
+ return link.startsWith(qstr("http://"), Qt::CaseInsensitive)
+ || link.startsWith(qstr("https://"), Qt::CaseInsensitive);
+}
QByteArray SerializeString(const QByteArray &value) {
const auto size = value.size();
@@ -76,6 +85,19 @@ QByteArray SerializeString(const QByteArray &value) {
return result;
}
+QByteArray SerializeList(const std::vector &values) {
+ const auto count = values.size();
+ if (count == 1) {
+ return values[0];
+ } else if (count > 1) {
+ auto result = values[0];
+ for (auto i = 1; i != count - 1; ++i) {
+ result += ", " + values[i];
+ }
+ return result + " and " + values[count - 1];
+ }
+ return QByteArray();
+}
QByteArray MakeLinks(const QByteArray &value) {
const auto domain = QByteArray("https://telegram.org/");
auto result = QByteArray();
@@ -162,6 +184,55 @@ QByteArray JoinList(
return result;
}
+QByteArray FormatText(
+ const std::vector &data,
+ const QString &internalLinksDomain) {
+ return JoinList(QByteArray(), ranges::view::all(
+ data
+ ) | ranges::view::transform([&](const Data::TextPart &part) {
+ const auto text = SerializeString(part.text);
+ using Type = Data::TextPart::Type;
+ switch (part.type) {
+ case Type::Text: return text;
+ case Type::Unknown: return text;
+ case Type::Mention:
+ return "" + text + "";
+ case Type::Hashtag: return "" + text + "";
+ case Type::BotCommand: return "" + text + "";
+ case Type::Url: return "" + text + "";
+ case Type::Email: return "" + text + "";
+ case Type::Bold: return "" + text + "";
+ case Type::Italic: return "" + text + "";
+ case Type::Code: return "" + text + "
";
+ case Type::Pre: return "" + text + "
";
+ case Type::TextUrl: return "" + text + "";
+ case Type::MentionName: return "" + text + "";
+ case Type::Phone: return "" + text + "";
+ case Type::Cashtag: return "" + text + "";
+ }
+ Unexpected("Type in text entities serialization.");
+ }) | ranges::to_vector);
+}
+
QByteArray SerializeKeyValue(
std::vector> &&values) {
auto result = QByteArray();
@@ -229,6 +300,13 @@ QByteArray FormatDateText(TimeId date) {
+ Data::NumberToString(parsed.year());
}
+QByteArray FormatTimeText(TimeId date) {
+ const auto parsed = QDateTime::fromTime_t(date).time();
+ return Data::NumberToString(parsed.hour(), 2)
+ + ':'
+ + Data::NumberToString(parsed.minute(), 2);
+}
+
QByteArray SerializeLink(
const Data::Utf8String &text,
const QString &path) {
@@ -248,6 +326,85 @@ struct UserpicData {
QByteArray lastName;
};
+class PeersMap {
+public:
+ using PeerId = Data::PeerId;
+ using Peer = Data::Peer;
+ using User = Data::User;
+ using Chat = Data::Chat;
+
+ PeersMap(const std::map &data);
+
+ const Peer &peer(PeerId peerId) const;
+ const User &user(int32 userId) const;
+ const Chat &chat(int32 chatId) const;
+
+ QByteArray wrapPeerName(PeerId peerId) const;
+ QByteArray wrapUserName(int32 userId) const;
+ QByteArray wrapUserNames(const std::vector &data) const;
+
+private:
+ const std::map &_data;
+
+};
+
+struct MediaData {
+ QByteArray title;
+ QByteArray description;
+ QByteArray status;
+ QByteArray classes;
+ QString link;
+};
+
+PeersMap::PeersMap(const std::map &data) : _data(data) {
+}
+
+auto PeersMap::peer(PeerId peerId) const -> const Peer & {
+ if (const auto i = _data.find(peerId); i != end(_data)) {
+ return i->second;
+ }
+ static auto empty = Peer{ User() };
+ return empty;
+}
+
+auto PeersMap::user(int32 userId) const -> const User & {
+ if (const auto result = peer(Data::UserPeerId(userId)).user()) {
+ return *result;
+ }
+ static auto empty = User();
+ return empty;
+}
+
+auto PeersMap::chat(int32 chatId) const -> const Chat & {
+ if (const auto result = peer(Data::ChatPeerId(chatId)).chat()) {
+ return *result;
+ }
+ static auto empty = Chat();
+ return empty;
+}
+
+QByteArray PeersMap::wrapPeerName(PeerId peerId) const {
+ const auto result = peer(peerId).name();
+ return result.isEmpty()
+ ? QByteArray("Deleted")
+ : SerializeString(result);
+}
+
+QByteArray PeersMap::wrapUserName(int32 userId) const {
+ const auto result = user(userId).name();
+ return result.isEmpty()
+ ? QByteArray("Deleted Account")
+ : SerializeString(result);
+}
+
+QByteArray PeersMap::wrapUserNames(const std::vector &data) const {
+ auto list = std::vector();
+ for (const auto userId : data) {
+ list.push_back(wrapUserName(userId));
+ }
+ return SerializeList(list);
+}
+
QByteArray HtmlContext::pushTag(
const QByteArray &tag,
std::map &&attributes) {
@@ -294,6 +451,18 @@ bool HtmlContext::empty() const {
} // namespace details
+struct HtmlWriter::MessageInfo {
+ enum class Type {
+ Service,
+ Default,
+ };
+ Type type = Type::Service;
+ int32 fromId = 0;
+ TimeId date = 0;
+ Data::PeerId forwardedFromId = 0;
+ TimeId forwardedDate = 0;
+};
+
class HtmlWriter::Wrap {
public:
Wrap(const QString &path, const QString &base, Stats *stats);
@@ -341,11 +510,17 @@ public:
const QString &basePath,
const QByteArray &text,
const Data::Photo *photo = nullptr);
- [[nodiscard]] QByteArray pushMessage(
+ [[nodiscard]] std::pair pushMessage(
const Data::Message &message,
+ const MessageInfo *previous,
const Data::DialogInfo &dialog,
const QString &basePath,
- const std::map &peers,
+ const PeersMap &peers,
+ const QString &internalLinksDomain);
+ [[nodiscard]] QByteArray pushMedia(
+ const Data::Message &message,
+ const QString &basePath,
+ const PeersMap &peers,
const QString &internalLinksDomain);
[[nodiscard]] Result writeBlock(const QByteArray &block);
@@ -367,6 +542,19 @@ private:
std::initializer_list details,
const QByteArray &info);
+ [[nodiscard]] bool messageNeedsWrap(
+ const Data::Message &message,
+ const MessageInfo *previous) const;
+ [[nodiscard]] bool forwardedNeedsWrap(
+ const Data::Message &message,
+ const MessageInfo *previous) const;
+
+ [[nodiscard]] MediaData prepareMediaData(
+ const Data::Message &message,
+ const QString &basePath,
+ const PeersMap &peers,
+ const QString &internalLinksDomain) const;
+
File _file;
bool _closed = false;
QByteArray _base;
@@ -382,6 +570,15 @@ struct HtmlWriter::SavedSection {
QString path;
};
+void FillUserpicNames(UserpicData &data, const Data::Peer &peer) {
+ if (peer.user()) {
+ data.firstName = peer.user()->info.firstName;
+ data.lastName = peer.user()->info.lastName;
+ } else if (peer.chat()) {
+ data.firstName = peer.name();
+ }
+}
+
QByteArray ComposeName(const UserpicData &data, const QByteArray &empty) {
return ((data.firstName.isEmpty() && data.lastName.isEmpty())
? empty
@@ -640,7 +837,7 @@ QByteArray HtmlWriter::Wrap::pushServiceMessage(
{ "class", "message service" },
{ "id", "message" + Data::NumberToString(messageId) }
});
- result.append(pushDiv("content details"));
+ result.append(pushDiv("body details"));
result.append(serialized);
result.append(popTag());
if (photo) {
@@ -663,114 +860,30 @@ QByteArray HtmlWriter::Wrap::pushServiceMessage(
return result;
}
-QByteArray HtmlWriter::Wrap::pushMessage(
+auto HtmlWriter::Wrap::pushMessage(
const Data::Message &message,
+ const MessageInfo *previous,
const Data::DialogInfo &dialog,
const QString &basePath,
- const std::map &peers,
- const QString &internalLinksDomain) {
+ const PeersMap &peers,
+ const QString &internalLinksDomain
+) -> std::pair {
using namespace Data;
+ auto info = MessageInfo();
+ info.fromId = message.fromId;
+ info.date = message.date;
+ info.forwardedFromId = message.forwardedFromId;
+ info.forwardedDate = message.forwardedDate;
if (message.media.content.is()) {
- return pushServiceMessage(
+ return { info, pushServiceMessage(
message.id,
dialog,
basePath,
"This message is not supported by this version "
- "of Telegram Desktop. Please update the application.");
+ "of Telegram Desktop. Please update the application.") };
}
- const auto peer = [&](PeerId peerId) -> const Peer& {
- if (const auto i = peers.find(peerId); i != end(peers)) {
- return i->second;
- }
- static auto empty = Peer{ User() };
- return empty;
- };
- const auto user = [&](int32 userId) -> const User& {
- if (const auto result = peer(UserPeerId(userId)).user()) {
- return *result;
- }
- static auto empty = User();
- return empty;
- };
- const auto chat = [&](int32 chatId) -> const Chat& {
- if (const auto result = peer(ChatPeerId(chatId)).chat()) {
- return *result;
- }
- static auto empty = Chat();
- return empty;
- };
-
- auto values = std::vector>{
- { "ID", SerializeString(NumberToString(message.id)) },
- { "Date", SerializeString(FormatDateTime(message.date)) },
- { "Edited", SerializeString(FormatDateTime(message.edited)) },
- };
- const auto pushBare = [&](
- const QByteArray &key,
- const QByteArray &value) {
- values.emplace_back(key, value);
- };
- const auto push = [&](const QByteArray &key, const QByteArray &value) {
- if (!value.isEmpty()) {
- pushBare(key, SerializeString(value));
- }
- };
- const auto wrapPeerName = [&](PeerId peerId) {
- const auto result = peer(peerId).name();
- return result.isEmpty() ? QByteArray("(deleted peer)") : result;
- };
- const auto wrapUserName = [&](int32 userId) {
- const auto result = user(userId).name();
- return result.isEmpty()
- ? QByteArray("Deleted Account")
- : SerializeString(result);
- };
- const auto pushFrom = [&](const QByteArray &label = "From") {
- if (message.fromId) {
- push(label, wrapUserName(message.fromId));
- }
- };
- const auto pushReplyToMsgId = [&](
- const QByteArray &label = "Reply to message") {
- if (message.replyToMsgId) {
- push(label, "ID-" + NumberToString(message.replyToMsgId));
- }
- };
- const auto wrapList = [&](const std::vector &values) {
- const auto count = values.size();
- if (count == 1) {
- return values[0];
- } else if (count > 1) {
- auto result = values[0];
- for (auto i = 1; i != count - 1; ++i) {
- result += ", " + values[i];
- }
- return result + " and " + values[count - 1];
- }
- return QByteArray();
- };
- const auto wrapUserNames = [&](const std::vector &data) {
- auto list = std::vector();
- for (const auto userId : data) {
- list.push_back(wrapUserName(userId));
- }
- return wrapList(list);
- };
- const auto pushActor = [&] {
- pushFrom("Actor");
- };
- const auto pushAction = [&](const QByteArray &action) {
- push("Action", action);
- };
- const auto pushTTL = [&](
- const QByteArray &label = "Self destruct period") {
- if (const auto ttl = message.media.ttl) {
- push(label, NumberToString(ttl) + " sec.");
- }
- };
-
using SkipReason = Data::File::SkipReason;
const auto formatPath = [&](
const Data::File &file,
@@ -798,19 +911,6 @@ QByteArray HtmlWriter::Wrap::pushMessage(
}
Unexpected("Skip reason while writing file path.");
};
- const auto pushPath = [&](
- const Data::File &file,
- const QByteArray &label,
- const QByteArray &name = QByteArray()) {
- pushBare(label, formatPath(file, label, name));
- };
- const auto pushPhoto = [&](const Image &image) {
- pushPath(image.file, "Photo");
- if (image.width && image.height) {
- push("Width", NumberToString(image.width));
- push("Height", NumberToString(image.height));
- }
- };
const auto wrapReplyToLink = [&](const QByteArray &text) {
return "";
};
- const auto serviceFrom = wrapUserName(message.fromId);
+ const auto serviceFrom = peers.wrapUserName(message.fromId);
const auto serviceText = message.action.content.match(
[&](const ActionChatCreate &data) {
return serviceFrom
+ " created group «" + data.title + "»"
+ (data.userIds.empty()
? QByteArray()
- : " with members " + wrapUserNames(data.userIds));
+ : " with members " + peers.wrapUserNames(data.userIds));
}, [&](const ActionChatEditTitle &data) {
return serviceFrom
+ " changed group title to «" + data.title + "»";
@@ -838,15 +938,15 @@ QByteArray HtmlWriter::Wrap::pushMessage(
}, [&](const ActionChatAddUser &data) {
return serviceFrom
+ " invited "
- + wrapUserNames(data.userIds);
+ + peers.wrapUserNames(data.userIds);
}, [&](const ActionChatDeleteUser &data) {
return serviceFrom
+ " removed "
- + wrapUserName(data.userId);
+ + peers.wrapUserName(data.userId);
}, [&](const ActionChatJoinedByLink &data) {
return serviceFrom
+ " joined group by link from "
- + wrapUserName(data.inviterId);
+ + peers.wrapUserName(data.inviterId);
}, [&](const ActionChannelCreate &data) {
return "Channel «" + data.title + "» created";
}, [&](const ActionChatMigrateTo &data) {
@@ -907,7 +1007,8 @@ QByteArray HtmlWriter::Wrap::pushMessage(
return "";
}());
}
- return "You have sent the following documents: " + wrapList(list);
+ return "You have sent the following documents: "
+ + SerializeList(list);
}, [](const base::none_type &) { return QByteArray(); });
if (!serviceText.isEmpty()) {
@@ -915,164 +1016,360 @@ QByteArray HtmlWriter::Wrap::pushMessage(
const auto photo = content.is()
? &content.get_unchecked().photo
: nullptr;
- return pushServiceMessage(
+ return { info, pushServiceMessage(
message.id,
dialog,
basePath,
serviceText,
- photo);
+ photo) };
+ }
+ info.type = MessageInfo::Type::Default;
+
+ const auto wrap = messageNeedsWrap(message, previous);
+ const auto fromPeerId = message.fromId
+ ? UserPeerId(message.fromId)
+ : ChatPeerId(message.chatId);
+ auto userpic = UserpicData();
+ userpic.colorIndex = PeerColorIndex(BarePeerId(fromPeerId));
+ userpic.pixelSize = kHistoryUserpicSize;
+ FillUserpicNames(userpic, peers.peer(fromPeerId));
+
+ const auto via = [&] {
+ if (message.viaBotId) {
+ const auto &user = peers.user(message.viaBotId);
+ if (!user.username.isEmpty()) {
+ return SerializeString(user.username);
+ }
+ }
+ return QByteArray();
+ }();
+
+ const auto className = wrap
+ ? "message default clearfix"
+ : "message default clearfix joined";
+ auto block = pushTag("div", {
+ { "class", className },
+ { "id", "message" + NumberToString(message.id) }
+ });
+ if (wrap) {
+ block.append(pushDiv("pull_left userpic_wrap"));
+ block.append(pushUserpic(userpic));
+ block.append(popTag());
+ }
+ block.append(pushDiv("body"));
+ block.append(pushTag("div", {
+ { "class", "pull_right date details" },
+ { "title", FormatDateTime(message.date) },
+ }));
+ block.append(FormatTimeText(message.date));
+ block.append(popTag());
+ if (wrap) {
+ block.append(pushDiv("from_name"));
+ block.append(SerializeString(
+ ComposeName(userpic, "Deleted Account")));
+ if (!via.isEmpty() && !message.forwardedFromId) {
+ block.append(" via @" + via);
+ }
+ block.append(popTag());
+ }
+ if (message.forwardedFromId) {
+ auto forwardedUserpic = UserpicData();
+ forwardedUserpic.colorIndex = PeerColorIndex(
+ BarePeerId(message.forwardedFromId));
+ forwardedUserpic.pixelSize = kHistoryUserpicSize;
+ FillUserpicNames(
+ forwardedUserpic,
+ peers.peer(message.forwardedFromId));
+
+ const auto forwardedWrap = forwardedNeedsWrap(message, previous);
+ if (forwardedWrap) {
+ block.append(pushDiv("pull_left forwarded userpic_wrap"));
+ block.append(pushUserpic(forwardedUserpic));
+ block.append(popTag());
+ }
+ block.append(pushDiv("forwarded body"));
+ if (forwardedWrap) {
+ block.append(pushDiv("from_name"));
+ block.append(SerializeString(
+ ComposeName(forwardedUserpic, "Deleted Account")));
+ if (!via.isEmpty()) {
+ block.append(" via @" + via);
+ }
+ block.append(pushTag("span", {
+ { "class", "details" },
+ { "inline", "" }
+ }));
+ block.append(' ' + FormatDateTime(message.forwardedDate));
+ block.append(popTag());
+ block.append(popTag());
+ }
+ }
+ if (message.replyToMsgId) {
+ block.append(pushDiv("reply_to details"));
+ block.append("In reply to ");
+ block.append(wrapReplyToLink("this message"));
+ block.append(popTag());
}
- if (!message.action.content) {
- pushFrom();
- push("Author", message.signature);
- if (message.forwardedFromId) {
- push("Forwarded from", wrapPeerName(message.forwardedFromId));
- }
- if (message.savedFromChatId) {
- push("Saved from", wrapPeerName(message.savedFromChatId));
- }
- pushReplyToMsgId();
- if (message.viaBotId) {
- push("Via", user(message.viaBotId).username);
+ block.append(pushMedia(message, basePath, peers, internalLinksDomain));
+
+ const auto text = FormatText(message.text, internalLinksDomain);
+ if (!text.isEmpty()) {
+ block.append(pushDiv("text"));
+ block.append(text);
+ block.append(popTag());
+ }
+ if (!message.signature.isEmpty()) {
+ block.append(pushDiv("signature details"));
+ block.append(SerializeString(message.signature));
+ block.append(popTag());
+ }
+ if (message.forwardedFromId) {
+ block.append(popTag());
+ }
+ block.append(popTag());
+ block.append(popTag());
+
+ return { info, block };
+}
+
+bool HtmlWriter::Wrap::messageNeedsWrap(
+ const Data::Message &message,
+ const MessageInfo *previous) const {
+ if (!previous) {
+ return true;
+ } else if (previous->type != MessageInfo::Type::Default) {
+ return true;
+ } else if (!message.fromId || previous->fromId != message.fromId) {
+ return true;
+ } else if (QDateTime::fromTime_t(previous->date).date()
+ != QDateTime::fromTime_t(message.date).date()) {
+ return true;
+ } else if (!message.forwardedFromId != !previous->forwardedFromId) {
+ return true;
+ } else if (std::abs(message.date - previous->date)
+ > (message.forwardedFromId ? 1 : kJoinWithinSeconds)) {
+ return true;
+ }
+ return false;
+}
+
+QByteArray HtmlWriter::Wrap::pushMedia(
+ const Data::Message &message,
+ const QString &basePath,
+ const PeersMap &peers,
+ const QString &internalLinksDomain) {
+ const auto data = prepareMediaData(
+ message,
+ basePath,
+ peers,
+ internalLinksDomain);
+ if (data.classes.isEmpty()) {
+ return QByteArray();
+ }
+ auto result = pushDiv("media_wrap clearfix");
+ if (data.link.isEmpty()) {
+ result.append(pushDiv("media clearfix pull_left " + data.classes));
+ } else {
+ result.append(pushTag("a", {
+ {
+ "class",
+ "media clearfix pull_left block_link " + data.classes
+ },
+ {
+ "href",
+ (IsGlobalLink(data.link)
+ ? data.link.toUtf8()
+ : relativePath(data.link).toUtf8())
+ }
+ }));
+ }
+ result.append(pushDiv("thumb pull_left"));
+ result.append(popTag());
+ result.append(pushDiv("body"));
+ if (!data.title.isEmpty()) {
+ result.append(pushDiv("title bold"));
+ result.append(SerializeString(data.title));
+ result.append(popTag());
+ }
+ if (!data.description.isEmpty()) {
+ result.append(pushDiv("description"));
+ result.append(SerializeString(data.description));
+ result.append(popTag());
+ }
+ if (!data.status.isEmpty()) {
+ result.append(pushDiv("status details"));
+ result.append(SerializeString(data.status));
+ result.append(popTag());
+ }
+ result.append(popTag());
+ result.append(popTag());
+ result.append(popTag());
+ return result;
+}
+
+MediaData HtmlWriter::Wrap::prepareMediaData(
+ const Data::Message &message,
+ const QString &basePath,
+ const PeersMap &peers,
+ const QString &internalLinksDomain) const {
+ using namespace Data;
+
+ auto result = MediaData();
+ const auto &action = message.action;
+ if (const auto call = base::get_if(&action.content)) {
+ result.classes = "media_call";
+ result.title = peers.peer(message.toId).name();
+ result.status = [&] {
+ using Reason = ActionPhoneCall::DiscardReason;
+ const auto reason = call->discardReason;
+ if (message.out) {
+ return reason == Reason::Missed ? "Cancelled" : "Outgoing";
+ } else if (reason == Reason::Missed) {
+ return "Missed";
+ } else if (reason == Reason::Busy) {
+ return "Declined";
+ }
+ return "Incoming";
+ }();
+ if (call->duration > 0) {
+ result.classes += " success";
+ result.status += " ("
+ + NumberToString(call->duration)
+ + " seconds)";
}
+ return result;
}
message.media.content.match([&](const Photo &photo) {
- pushPhoto(photo.image);
- pushTTL();
+ // #TODO export: photo + self destruct (ttl)
+ result.title = "Photo";
+ result.status = NumberToString(photo.image.width)
+ + "x"
+ + NumberToString(photo.image.height);
+ result.classes = "media_file"; // #TODO export
+ result.link = FormatFilePath(photo.image.file);
}, [&](const Document &data) {
- const auto pushMyPath = [&](const QByteArray &label) {
- return pushPath(data.file, label);
- };
+ // #TODO export: sticker + thumb (video, video message) + self destruct (ttl)
+ result.link = FormatFilePath(data.file);
if (data.isSticker) {
- pushMyPath("Sticker");
- push("Emoji", data.stickerEmoji);
+ result.title = "Sticker";
+ result.status = data.stickerEmoji;
+ result.classes = "media_file"; // #TODO export
} else if (data.isVideoMessage) {
- pushMyPath("Video message");
+ result.title = "Video message";
+ result.status = FormatDuration(data.duration);
+ result.classes = "media_file"; // #TODO export
} else if (data.isVoiceMessage) {
- pushMyPath("Voice message");
+ result.title = "Voice message";
+ result.status = FormatDuration(data.duration);
+ result.classes = "media_voice_message";
} else if (data.isAnimated) {
- pushMyPath("Animation");
+ result.title = "Animation";
+ result.status = FormatFileSize(data.duration);
+ result.classes = "media_file"; // #TODO export
} else if (data.isVideoFile) {
- pushMyPath("Video file");
+ result.title = "Video file";
+ result.status = FormatDuration(data.duration);
+ result.classes = "media_file"; // #TODO export
} else if (data.isAudioFile) {
- pushMyPath("Audio file");
- push("Performer", data.songPerformer);
- push("Title", data.songTitle);
+ result.title = (data.songPerformer.isEmpty()
+ || data.songTitle.isEmpty())
+ ? QByteArray("Audio file")
+ : data.songPerformer + " \xe2\x80\x93 " + data.songTitle;
+ result.status = FormatDuration(data.duration);
+ result.classes = "media_audio_file";
} else {
- pushMyPath("File");
+ result.title = data.name.isEmpty()
+ ? QByteArray("File")
+ : data.name;
+ result.status = FormatFileSize(data.duration);
+ result.classes = "media_file";
}
- if (!data.isSticker) {
- push("Mime type", data.mime);
- }
- if (data.duration) {
- push("Duration", NumberToString(data.duration) + " sec.");
- }
- if (data.width && data.height) {
- push("Width", NumberToString(data.width));
- push("Height", NumberToString(data.height));
- }
- pushTTL();
}, [&](const SharedContact &data) {
- pushBare("Contact information", SerializeBlockquote({
- { "First name", data.info.firstName },
- { "Last name", data.info.lastName },
- { "Phone number", FormatPhoneNumber(data.info.phoneNumber) },
- { "vCard", (data.vcard.content.isEmpty()
- ? QByteArray()
- : formatPath(data.vcard, "vCard")) }
- }));
+ result.title = data.info.firstName + ' ' + data.info.lastName;
+ result.classes = "media_contact";
+ result.status = FormatPhoneNumber(data.info.phoneNumber);
+ if (!data.vcard.content.isEmpty()) {
+ result.status += " - vCard";
+ result.link = FormatFilePath(data.vcard);
+ }
}, [&](const GeoPoint &data) {
- pushBare("Location", data.valid ? SerializeBlockquote({
- { "Latitude", NumberToString(data.latitude) },
- { "Longitude", NumberToString(data.longitude) },
- }) : QByteArray("(empty value)"));
- pushTTL("Live location period");
+ if (message.media.ttl) {
+ result.classes = "media_live_location";
+ result.title = "Live location";
+ result.status = "";
+ } else {
+ result.classes = "media_location";
+ result.title = "Location";
+ }
+ if (data.valid) {
+ const auto latitude = NumberToString(data.latitude);
+ const auto longitude = NumberToString(data.longitude);
+ const auto coords = latitude + ',' + longitude;
+ result.status = latitude + ", " + longitude;
+ result.link = "https://maps.google.com/maps?q="
+ + coords
+ + "&ll="
+ + coords
+ + "&z=16";
+ }
}, [&](const Venue &data) {
- push("Place name", data.title);
- push("Address", data.address);
+ result.classes = "media_venue";
+ result.title = data.title;
+ result.description = data.address;
if (data.point.valid) {
- pushBare("Location", SerializeBlockquote({
- { "Latitude", NumberToString(data.point.latitude) },
- { "Longitude", NumberToString(data.point.longitude) },
- }));
+ const auto latitude = NumberToString(data.point.latitude);
+ const auto longitude = NumberToString(data.point.longitude);
+ const auto coords = latitude + ',' + longitude;
+ result.link = "https://maps.google.com/maps?q="
+ + coords
+ + "&ll="
+ + coords
+ + "&z=16";
}
}, [&](const Game &data) {
- push("Game", data.title);
- push("Description", data.description);
+ result.classes = "media_game";
+ result.title = data.title;
+ result.description = data.description;
if (data.botId != 0 && !data.shortName.isEmpty()) {
- const auto bot = user(data.botId);
+ const auto bot = peers.user(data.botId);
if (bot.isBot && !bot.username.isEmpty()) {
- push("Link", internalLinksDomain.toUtf8()
+ const auto link = internalLinksDomain.toUtf8()
+ bot.username
+ "?game="
- + data.shortName);
+ + data.shortName;
+ result.link = link;
+ result.status = link;
}
}
}, [&](const Invoice &data) {
- pushBare("Invoice", SerializeBlockquote({
- { "Title", data.title },
- { "Description", data.description },
- {
- "Amount",
- Data::FormatMoneyAmount(data.amount, data.currency)
- },
- { "Receipt message", (data.receiptMsgId
- ? "ID-" + NumberToString(data.receiptMsgId)
- : QByteArray()) }
- }));
+ result.classes = "media_invoice";
+ result.title = data.title;
+ result.description = data.description;
+ result.status = Data::FormatMoneyAmount(data.amount, data.currency);
}, [](const UnsupportedMedia &data) {
Unexpected("Unsupported message.");
}, [](const base::none_type &) {});
+ return result;
+}
- auto value = JoinList(QByteArray(), ranges::view::all(
- message.text
- ) | ranges::view::transform([&](const Data::TextPart &part) {
- const auto text = SerializeString(part.text);
- using Type = Data::TextPart::Type;
- switch (part.type) {
- case Type::Text: return text;
- case Type::Unknown: return text;
- case Type::Mention:
- return "" + text + "";
- case Type::Hashtag: return "" + text + "";
- case Type::BotCommand: return "" + text + "";
- case Type::Url: return "" + text + "";
- case Type::Email: return "" + text + "";
- case Type::Bold: return "" + text + "";
- case Type::Italic: return "" + text + "";
- case Type::Code: return "" + text + "
";
- case Type::Pre: return "" + text + "
";
- case Type::TextUrl: return "" + text + "";
- case Type::MentionName: return "" + text + "";
- case Type::Phone: return "" + text + "";
- case Type::Cashtag: return "" + text + "";
- }
- Unexpected("Type in text entities serialization.");
- }) | ranges::to_vector);
- pushBare("Text", value);
+bool HtmlWriter::Wrap::forwardedNeedsWrap(
+ const Data::Message &message,
+ const MessageInfo *previous) const {
+ Expects(message.forwardedFromId != 0);
- return SerializeKeyValue(std::move(values));
+ if (messageNeedsWrap(message, previous)) {
+ return true;
+ } else if (message.forwardedFromId != previous->forwardedFromId) {
+ return true;
+ } else if (Data::IsChatPeerId(message.forwardedFromId)) {
+ return true;
+ } else if (abs(message.forwardedDate - previous->forwardedDate)
+ > kJoinWithinSeconds) {
+ return true;
+ }
+ return false;
}
Result HtmlWriter::Wrap::close() {
@@ -1149,13 +1446,23 @@ Result HtmlWriter::start(
const auto files = {
"css/style.css",
"images/back.png",
- "images/calls.png",
- "images/chats.png",
- "images/contacts.png",
- "images/frequent.png",
- "images/photos.png",
- "images/sessions.png",
- "images/web.png",
+ "images/media_call.png",
+ "images/media_contact.png",
+ "images/media_file.png",
+ "images/media_game.png",
+ "images/media_location.png",
+ "images/media_music.png",
+ "images/media_shop.png",
+ "images/media_voice.png",
+ "images/section_calls.png",
+ "images/section_chats.png",
+ "images/section_contacts.png",
+ "images/section_frequent.png",
+ "images/section_leftchats.png",
+ "images/section_other.png",
+ "images/section_photos.png",
+ "images/section_sessions.png",
+ "images/section_web.png",
};
for (const auto path : files) {
const auto name = QString(path);
@@ -1446,9 +1753,7 @@ Result HtmlWriter::writeFrequentContacts(const Data::ContactsList &data) {
userpic.lastName = lastName;
block.append(file->pushListEntry(
userpic,
- ((name.isEmpty() && lastName.isEmpty())
- ? QByteArray("Deleted Account")
- : (name + ' ' + lastName)),
+ ComposeName(userpic, "Deleted Account"),
"Rating: " + Data::NumberToString(top.rating),
category));
}
@@ -1586,18 +1891,20 @@ Result HtmlWriter::writeWebSessions(const Data::SessionsList &data) {
Result HtmlWriter::writeOtherData(const Data::File &data) {
Expects(_summary != nullptr);
- const auto header = SerializeLink(
+ pushSection(
+ 7,
"Other data",
- _summary->relativePath(data))
- + kLineBreak
- + kLineBreak;
- return _summary->writeBlock(header);
+ "other",
+ 1,
+ data.relativePath);
+ return Result::Success();
}
Result HtmlWriter::writeDialogsStart(const Data::DialogsInfo &data) {
return writeChatsStart(
data,
"Chats",
+ "chats",
_environment.aboutChats,
"lists/chats.html");
}
@@ -1622,6 +1929,7 @@ Result HtmlWriter::writeLeftChannelsStart(const Data::DialogsInfo &data) {
return writeChatsStart(
data,
"Left chats",
+ "leftchats",
_environment.aboutLeftChats,
"lists/left_chats.html");
}
@@ -1645,6 +1953,7 @@ Result HtmlWriter::writeLeftChannelsEnd() {
Result HtmlWriter::writeChatsStart(
const Data::DialogsInfo &data,
const QByteArray &listName,
+ const QByteArray &buttonClass,
const QByteArray &about,
const QString &fileName) {
Expects(_summary != nullptr);
@@ -1672,7 +1981,7 @@ Result HtmlWriter::writeChatsStart(
pushSection(
0,
listName,
- "chats",
+ buttonClass,
data.list.size(),
fileName);
return writeSections();
@@ -1687,7 +1996,7 @@ Result HtmlWriter::writeChatStart(const Data::DialogInfo &data) {
_chat = fileWithRelativePath(data.relativePath + messagesFile(0));
_messagesCount = 0;
_dateMessageId = 0;
- _lastMessageDate = 0;
+ _lastMessageInfo = nullptr;
_dialog = data;
return Result::Success();
}
@@ -1697,8 +2006,12 @@ Result HtmlWriter::writeChatSlice(const Data::MessagesSlice &data) {
Expects(!data.list.empty());
if (_chat->empty()) {
+ const auto name = (_dialog.name.isEmpty()
+ && _dialog.lastName.isEmpty())
+ ? QByteArray("Deleted Account")
+ : (_dialog.name + ' ' + _dialog.lastName);
auto block = _chat->pushHeader(
- _dialog.name + ' ' + _dialog.lastName,
+ name,
_dialogsRelativePath);
block.append(_chat->pushDiv("page_body chat_page"));
block.append(_chat->pushDiv("history"));
@@ -1716,24 +2029,30 @@ Result HtmlWriter::writeChatSlice(const Data::MessagesSlice &data) {
}
}
+ auto previous = _lastMessageInfo.get();
+ auto saved = MessageInfo();
auto block = QByteArray();
for (const auto &message : data.list) {
const auto date = message.date;
- if (DisplayDate(date, _lastMessageDate)) {
+ if (DisplayDate(date, previous ? previous->date : 0)) {
block.append(_chat->pushServiceMessage(
--_dateMessageId,
_dialog,
_settings.path,
FormatDateText(date)));
}
- block.append(_chat->pushMessage(
+ const auto [info, content] = _chat->pushMessage(
message,
+ previous,
_dialog,
_settings.path,
data.peers,
- _environment.internalLinksDomain));
- _lastMessageDate = date;
+ _environment.internalLinksDomain);
+ block.append(content);
+ saved = info;
+ previous = &saved;
}
+ _lastMessageInfo = std::make_unique(saved);
return _chat->writeBlock(block);
}
@@ -1791,6 +2110,8 @@ Result HtmlWriter::writeChatEnd() {
const auto CountString = [](int count, bool outgoing) -> QByteArray {
if (count == 1) {
return outgoing ? "1 outgoing message" : "1 message";
+ } else if (!count) {
+ return outgoing ? "No outgoing messages" : "No messages";
}
return Data::NumberToString(count)
+ (outgoing ? " outgoing messages" : " messages");
@@ -1805,9 +2126,7 @@ Result HtmlWriter::writeChatEnd() {
userpic.lastName = LastNameString(_dialog);
return _chats->writeBlock(_chats->pushListEntry(
userpic,
- ((userpic.firstName.isEmpty() && userpic.lastName.isEmpty())
- ? QByteArray(DeletedString(_dialog.type))
- : (userpic.firstName + ' ' + userpic.lastName)),
+ ComposeName(userpic, DeletedString(_dialog.type)),
CountString(_messagesCount, _dialog.onlyMyMessages),
TypeString(_dialog.type),
(_messagesCount > 0
diff --git a/Telegram/SourceFiles/export/output/export_output_html.h b/Telegram/SourceFiles/export/output/export_output_html.h
index 6a8cfc2b6..4614ae53e 100644
--- a/Telegram/SourceFiles/export/output/export_output_html.h
+++ b/Telegram/SourceFiles/export/output/export_output_html.h
@@ -35,6 +35,8 @@ private:
};
struct UserpicData;
+struct PeersMap;
+struct MediaData;
} // namespace details
@@ -84,7 +86,9 @@ public:
private:
using Context = details::HtmlContext;
using UserpicData = details::UserpicData;
+ using MediaData = details::MediaData;
class Wrap;
+ struct MessageInfo;
[[nodiscard]] Result copyFile(
const QString &source,
@@ -104,6 +108,7 @@ private:
[[nodiscard]] Result writeChatsStart(
const Data::DialogsInfo &data,
const QByteArray &listName,
+ const QByteArray &buttonClass,
const QByteArray &about,
const QString &fileName);
[[nodiscard]] Result writeChatStart(const Data::DialogInfo &data);
@@ -153,7 +158,7 @@ private:
Data::DialogInfo _dialog;
int _messagesCount = 0;
- TimeId _lastMessageDate = 0;
+ std::unique_ptr _lastMessageInfo;
int _dateMessageId = 0;
std::unique_ptr _chats;
std::unique_ptr _chat;
diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp
index 9d1ff0508..96585a137 100644
--- a/Telegram/SourceFiles/mainwidget.cpp
+++ b/Telegram/SourceFiles/mainwidget.cpp
@@ -261,9 +261,9 @@ MainWidget::MainWidget(
Messenger::Instance().mtp()->setUpdatesHandler(rpcDone(&MainWidget::updateReceived));
Messenger::Instance().mtp()->setGlobalFailHandler(rpcFail(&MainWidget::updateFail));
- Export::Output::HtmlWriter writer;
- writer.produceTestExample(psDownloadPath(), Export::View::PrepareEnvironment());
- crl::on_main([] { App::quit(); });
+ //Export::Output::HtmlWriter writer;
+ //writer.produceTestExample(psDownloadPath(), Export::View::PrepareEnvironment());
+ //crl::on_main([] { App::quit(); });
_ptsWaiter.setRequesting(true);
updateScrollColors();