From cb8ff398a53e592f283ebbdcf52f0323552590e6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 4 Jul 2018 00:09:25 +0100 Subject: [PATCH] Improved html message layout. --- Telegram/Resources/export_html/css/style.css | 286 ++++-- .../export_html/images/media_call.png | Bin 0 -> 417 bytes .../export_html/images/media_call@2x.png | Bin 0 -> 815 bytes .../export_html/images/media_contact.png | Bin 0 -> 323 bytes .../export_html/images/media_contact@2x.png | Bin 0 -> 600 bytes .../export_html/images/media_file.png | Bin 0 -> 236 bytes .../export_html/images/media_file@2x.png | Bin 0 -> 408 bytes .../export_html/images/media_game.png | Bin 0 -> 271 bytes .../export_html/images/media_game@2x.png | Bin 0 -> 510 bytes .../export_html/images/media_location.png | Bin 0 -> 480 bytes .../export_html/images/media_location@2x.png | Bin 0 -> 991 bytes .../export_html/images/media_music.png | Bin 0 -> 275 bytes .../export_html/images/media_music@2x.png | Bin 0 -> 464 bytes .../export_html/images/media_shop.png | Bin 0 -> 377 bytes .../export_html/images/media_shop@2x.png | Bin 0 -> 790 bytes .../export_html/images/media_voice.png | Bin 0 -> 328 bytes .../export_html/images/media_voice@2x.png | Bin 0 -> 660 bytes .../images/{calls.png => section_calls.png} | Bin .../{calls@2x.png => section_calls@2x.png} | Bin .../images/{chats.png => section_chats.png} | Bin .../{chats@2x.png => section_chats@2x.png} | Bin .../{contacts.png => section_contacts.png} | Bin ...ontacts@2x.png => section_contacts@2x.png} | Bin .../{frequent.png => section_frequent.png} | Bin ...requent@2x.png => section_frequent@2x.png} | Bin .../export_html/images/section_leftchats.png | Bin 0 -> 402 bytes .../images/section_leftchats@2x.png | Bin 0 -> 619 bytes .../export_html/images/section_other.png | Bin 0 -> 155 bytes .../export_html/images/section_other@2x.png | Bin 0 -> 269 bytes .../images/{photos.png => section_photos.png} | Bin .../{photos@2x.png => section_photos@2x.png} | Bin .../{sessions.png => section_sessions.png} | Bin ...essions@2x.png => section_sessions@2x.png} | Bin .../images/{web.png => section_web.png} | Bin .../images/{web@2x.png => section_web@2x.png} | Bin Telegram/Resources/qrc/telegram.qrc | 48 +- .../export/data/export_data_types.cpp | 54 +- .../export/data/export_data_types.h | 6 + .../export/output/export_output_abstract.cpp | 7 +- .../export/output/export_output_html.cpp | 847 ++++++++++++------ .../export/output/export_output_html.h | 7 +- Telegram/SourceFiles/mainwidget.cpp | 6 +- 42 files changed, 895 insertions(+), 366 deletions(-) create mode 100644 Telegram/Resources/export_html/images/media_call.png create mode 100644 Telegram/Resources/export_html/images/media_call@2x.png create mode 100644 Telegram/Resources/export_html/images/media_contact.png create mode 100644 Telegram/Resources/export_html/images/media_contact@2x.png create mode 100644 Telegram/Resources/export_html/images/media_file.png create mode 100644 Telegram/Resources/export_html/images/media_file@2x.png create mode 100644 Telegram/Resources/export_html/images/media_game.png create mode 100644 Telegram/Resources/export_html/images/media_game@2x.png create mode 100644 Telegram/Resources/export_html/images/media_location.png create mode 100644 Telegram/Resources/export_html/images/media_location@2x.png create mode 100644 Telegram/Resources/export_html/images/media_music.png create mode 100644 Telegram/Resources/export_html/images/media_music@2x.png create mode 100644 Telegram/Resources/export_html/images/media_shop.png create mode 100644 Telegram/Resources/export_html/images/media_shop@2x.png create mode 100644 Telegram/Resources/export_html/images/media_voice.png create mode 100644 Telegram/Resources/export_html/images/media_voice@2x.png rename Telegram/Resources/export_html/images/{calls.png => section_calls.png} (100%) rename Telegram/Resources/export_html/images/{calls@2x.png => section_calls@2x.png} (100%) rename Telegram/Resources/export_html/images/{chats.png => section_chats.png} (100%) rename Telegram/Resources/export_html/images/{chats@2x.png => section_chats@2x.png} (100%) rename Telegram/Resources/export_html/images/{contacts.png => section_contacts.png} (100%) rename Telegram/Resources/export_html/images/{contacts@2x.png => section_contacts@2x.png} (100%) rename Telegram/Resources/export_html/images/{frequent.png => section_frequent.png} (100%) rename Telegram/Resources/export_html/images/{frequent@2x.png => section_frequent@2x.png} (100%) create mode 100644 Telegram/Resources/export_html/images/section_leftchats.png create mode 100644 Telegram/Resources/export_html/images/section_leftchats@2x.png create mode 100644 Telegram/Resources/export_html/images/section_other.png create mode 100644 Telegram/Resources/export_html/images/section_other@2x.png rename Telegram/Resources/export_html/images/{photos.png => section_photos.png} (100%) rename Telegram/Resources/export_html/images/{photos@2x.png => section_photos@2x.png} (100%) rename Telegram/Resources/export_html/images/{sessions.png => section_sessions.png} (100%) rename Telegram/Resources/export_html/images/{sessions@2x.png => section_sessions@2x.png} (100%) rename Telegram/Resources/export_html/images/{web.png => section_web.png} (100%) rename Telegram/Resources/export_html/images/{web@2x.png => section_web@2x.png} (100%) diff --git a/Telegram/Resources/export_html/css/style.css b/Telegram/Resources/export_html/css/style.css index 63c444d2a5..31136ab9c1 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 0000000000000000000000000000000000000000..9614b9562698f1fd7c3b6e4e1d8c00ea2b0e9199 GIT binary patch literal 417 zcmV;S0bc%zP)Px$TS-JgR7ef&l&ww!K@^2M1lJ%dXhlQt1O&JSQUfI@1e)*&^dWc&Bocz*1rRt$ znovoQs0vbpplTK)iB+GIp_>@GEY!P0^=KyZtL*N zcwpRxPmqS08|p}4Jb;;TmgmVe2jyFs#Y2!(=+~PtZ@~+E!!umN6&&O}Ofbg+6;T1{ z4mu!nwQ!!ML9@^WT=cn;JyCb5g(V%v4cLVXh!yf#m}g}d)Lm-fL^|=8&;z|$tc6B@ zUBp4>M29hb;$!3FC+*?VlOkix)IEoA4EpzN8;{Pp6UIyZ?+g3@!M1P3Wx~Nl00000 LNkvXXu0mjfht9F6 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..92cdbe618bea5edcce64ac9761b879bf273f6d81 GIT binary patch literal 815 zcmV+~1JL}5P)Px%=}AOERA>e5n7wNiK@`RF6cr4D0X1621QbCmLhQJz31w@752hm=gggtbMJdQGdn{=jWro) zGSFn8mKn&hY#O`;F>B+Tu4@3@#IFa|9%KNmvdK~R1K;*Czts=7TCJ~EmxOMK%6zAZ`cv;Hq;j z@iPV8>Q#lvg@OS75AjMR;*S>uFjucMcTqzC_brGg@YoXEWyHj8#GbSOL}wGrs5Tvf z7cB^_{iD@JP1qZ;6X3fAaW|oa>Qf>9h6Nycz52+BMnJ6gg(ayD>=)(wZf2ev#EyfH zK>#X#1L0}ayVZ*$_8d^=LwgVDD+t<;fFodQ@(7vq8gSDu(bMsQb~qorCk@+Qp z4_l2hf&NbZPH19ncC(;!?BMw);^~%U)fD#7n>qAi`|6~*WjZ^}0sq_R5~??0MlG?vPiztr5Q)E7HfzwXQ!H!uS5K5&+v&z`3fu&`TaDQ>oLYU> zFA8F9OMRu6yQMvPbRcYkrnkY_E?w5}>jp%3O-jJ)94~opnC>(;NXi0%=qT>r6IAtQ zDK!_)gPx#{YgYYR7ef&lRXN;Fc^ldIO^`u(M53X6~xiW1u1@gz8nk@@vHp+-TWgZrlahW8(0f?me;Apxvdi1Cc+5$Cr6b3r+&i?+yUYC VNi0{y`5yoP002ovPDHLkV1iXTkD>qo literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..e4be8b0377646223fb4276b9d399b65e01af7370 GIT binary patch literal 600 zcmV-e0;m0nP)Px%5=lfsRA>e5m%B;>K@^5v5la;dAw)}2qJsaU1AOyyIg_YKnIEmBwtf`XSy z5Nra9omwfVm{y734~q=r4%uPH)rC3mk=Zk6&TVHlyJKTfVh3UeVh3UeVg~|tU^pDE zLI?UVNYPlj)qtVGx+1wr=)xzsM02v43>&?mHzfqekXK~;e~KU(b=6n81^X6eIazDZ z+uP;=T?=X{8cS!2*8KgMAPF5#Q?{5t44eKG-jFsB(+e%15KLDhSquQwS~c+5ELaa9)R#&{=2r)8}SA zZy~wP01mftcmO{>RMJ2n{9eI1T*D1K!W&3tU>=rX4Ypt%CLl{*Q0{U@&ax)GbR@q3 z&yXcYDEDC6%PGcMldhGIEK$q_U%x->anj51euz$;3?lLc@@Y%2Z~#2MSzg9f%#s?7$b)t7^+XyO5dy0000iT&xr@%#>N#;J_wc&ui3NXr zu5X8>dD7K(kg7N7DE(f7UmFoo=(&d`(UzD4haW;M3>q@7S z^JXt_YbsN1Gqv<(NP4e3)nw&?PXU3S!xgr*>D{}(=*s#>4WHOvUS58wwetD%m14dV kS_EFVdQ&MBb@0EydOg#Z8m literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ad2c6cc2c986e60d8245b5d99a2e87d976a4a30f GIT binary patch literal 408 zcmV;J0cZY+P)Px$Qb|NXRA>e5SX~OjFbv+r6Zkgq4thkd;)CKbyn;s%oIZ#*Q0CW%j4?LSu1V`4 zh0wV+$@eoiXi?ZO1xx`}fu?EJh$Es##Ew1NP;vv%R)|+D>Rx_25@>wrHJAFCh6K>w zq!16((I5c0s@YY9*IN*^hhKmphF@t0eKP`2*{GiL_m&2Mgm?W zO76XmDqrkb(7qCbu0v*rCS*VD<@&Y2C}D2%G8JG>Nx~88)a)HF0%U!z*&N8FJoWW) zVA6Y^8fecx1hg?k)i(lB@(CoH0|FvOl`sdQPx#$w@>(R7ef&Q`-%LFbpJB$3WeOMam>ypd&Wjfc~31ijm2KO2~hWC1dC8yTD0w zU5n;`ivuDuAS2Q%UJx4ia)A#VB1+HRF=7bew&>|=dx=}UOHI@+)piue?gKfoDz>X7 zu@bf;nZqhh%0$?5voEm2blGU8?GwNU$(n~1)jqA3h1sDc%H22_fJq5c0%r7^u~?$G z*6&D!=gcBq`caQ9Ny+xcbav)!^{%VH8@1=)j9`VspLet9j-Hoz1;s}%v8MCy4$K;} VB-0?;1+xGE002ovPDHLkV1h%Ib<_X= literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..09c2eb395e694807baa3c1650cffc5448123d80e GIT binary patch literal 510 zcmVPx$xJg7oRA>e5SW!;HFbrK0+6DN69b(s<;1)=54Y&e_*!IO4%JZl#&yAZDvCfkm zDa3hkoadJ&Ygw(bqynixDv%1K0;xbMkP4g>==*+y`i6QyjaK#`MBFxdyE^U+17GMr zFc!4^2Cr?`b$jA>r%1lQj!vHF631sUsOTLyp#OM6&||JIAD~vnt+!%Cr}$_zlQd;;8Vg1Zh$-@7O(Zo z(d#JUP99>r?kJ+?VDVai6nFqIZ<3%lk8!&$tcWve8=o3iB>;#7+Jq5shH-Nd;1YR3H^d1yX@jU}*(@0NCJVIF`O!JOBUy07*qoM6N<$f>=W6 As{jB1 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ab8080b7e91f9a0004a4a776d0ea566ffa653d98 GIT binary patch literal 480 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 XGiJl8P@z{v00000NkvXXu0mjf@h+4L literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..9cfe5512b53be10f9e1436466e9c31314c61f58f GIT binary patch literal 790 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 FV1mo}4SoOs literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..a60ed7a40120c24560098b53f059515242d95e18 GIT binary patch literal 155 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjNuDl_As(G?&n^^fFyL`}_~gBm zfk5{K#sUr&rsE86m3UaPRU8vfC00r7sQEP0{mGo)c6x^m{N{F~r5Q04#y(rSEIxqG z#Y8wVIc@E#Ww-uWRNT%LV5(BGn^DOwkiEP3oxdNCwkyZ>M%5KS+Za4u{an^LB{Ts5 DVj?tY literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..497fb33383cc6caddf758d9a0e0b98175cbd518a GIT binary patch literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUtE1oWnAsLNtuhdl?7X{6|=myM&Y87ga7Q6e{gV3Xry|Qrvg9y8{ePoE6<%KIh{)?!Bjh{$ucT L^>bP0l+XkKDKT&J literal 0 HcmV?d00001 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 ab56db5589..00763851f4 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 c513494fdc..eba6f47c48 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 7449f934ad..7facf8ec73 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 4d8c20881e..28c9831ed9 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 0acaf11c26..e226fc0fd4 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 6a8cfc2b66..4614ae53e2 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 9d1ff05089..96585a1373 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();