mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Proof-of-concept (WebView2 / Local) iv.
This commit is contained in:
parent
672ad64e53
commit
125f856e67
25 changed files with 3164 additions and 4 deletions
|
@ -30,8 +30,9 @@ include(cmake/lib_tgvoip.cmake)
|
||||||
include(cmake/lib_tgcalls.cmake)
|
include(cmake/lib_tgcalls.cmake)
|
||||||
include(cmake/lib_prisma.cmake)
|
include(cmake/lib_prisma.cmake)
|
||||||
include(cmake/td_export.cmake)
|
include(cmake/td_export.cmake)
|
||||||
include(cmake/td_mtproto.cmake)
|
include(cmake/td_iv.cmake)
|
||||||
include(cmake/td_lang.cmake)
|
include(cmake/td_lang.cmake)
|
||||||
|
include(cmake/td_mtproto.cmake)
|
||||||
include(cmake/td_scheme.cmake)
|
include(cmake/td_scheme.cmake)
|
||||||
include(cmake/td_ui.cmake)
|
include(cmake/td_ui.cmake)
|
||||||
include(cmake/generate_appdata_changelog.cmake)
|
include(cmake/generate_appdata_changelog.cmake)
|
||||||
|
@ -62,8 +63,9 @@ PRIVATE
|
||||||
desktop-app::external_minizip
|
desktop-app::external_minizip
|
||||||
|
|
||||||
tdesktop::td_export
|
tdesktop::td_export
|
||||||
tdesktop::td_mtproto
|
tdesktop::td_iv
|
||||||
tdesktop::td_lang
|
tdesktop::td_lang
|
||||||
|
tdesktop::td_mtproto
|
||||||
tdesktop::td_scheme
|
tdesktop::td_scheme
|
||||||
tdesktop::td_ui
|
tdesktop::td_ui
|
||||||
desktop-app::lib_webrtc
|
desktop-app::lib_webrtc
|
||||||
|
@ -995,6 +997,8 @@ PRIVATE
|
||||||
intro/intro_step.h
|
intro/intro_step.h
|
||||||
intro/intro_widget.cpp
|
intro/intro_widget.cpp
|
||||||
intro/intro_widget.h
|
intro/intro_widget.h
|
||||||
|
iv/iv_instance.cpp
|
||||||
|
iv/iv_instance.h
|
||||||
lang/lang_cloud_manager.cpp
|
lang/lang_cloud_manager.cpp
|
||||||
lang/lang_cloud_manager.h
|
lang/lang_cloud_manager.h
|
||||||
lang/lang_instance.cpp
|
lang/lang_instance.cpp
|
||||||
|
@ -1567,6 +1571,7 @@ PRIVATE
|
||||||
qrc/emoji_preview.qrc
|
qrc/emoji_preview.qrc
|
||||||
qrc/telegram/animations.qrc
|
qrc/telegram/animations.qrc
|
||||||
qrc/telegram/export.qrc
|
qrc/telegram/export.qrc
|
||||||
|
qrc/telegram/iv.qrc
|
||||||
qrc/telegram/telegram.qrc
|
qrc/telegram/telegram.qrc
|
||||||
qrc/telegram/sounds.qrc
|
qrc/telegram/sounds.qrc
|
||||||
winrc/Telegram.rc
|
winrc/Telegram.rc
|
||||||
|
|
1
Telegram/Resources/iv_html/highlight.9.12.0.css
Normal file
1
Telegram/Resources/iv_html/highlight.9.12.0.css
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.hljs{display:block;overflow-x:auto;padding:0.5em;background:#F0F0F0}.hljs,.hljs-subst{color:#444}.hljs-comment{color:#888888}.hljs-keyword,.hljs-attribute,.hljs-selector-tag,.hljs-meta-keyword,.hljs-doctag,.hljs-name{font-weight:bold}.hljs-type,.hljs-string,.hljs-number,.hljs-selector-id,.hljs-selector-class,.hljs-quote,.hljs-template-tag,.hljs-deletion{color:#880000}.hljs-title,.hljs-section{color:#880000;font-weight:bold}.hljs-regexp,.hljs-symbol,.hljs-variable,.hljs-template-variable,.hljs-link,.hljs-selector-attr,.hljs-selector-pseudo{color:#BC6060}.hljs-literal{color:#78A960}.hljs-built_in,.hljs-bullet,.hljs-code,.hljs-addition{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta-string{color:#4d99bf}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}
|
3
Telegram/Resources/iv_html/highlight.9.12.0.js
Normal file
3
Telegram/Resources/iv_html/highlight.9.12.0.js
Normal file
File diff suppressed because one or more lines are too long
871
Telegram/Resources/iv_html/page.css
Normal file
871
Telegram/Resources/iv_html/page.css
Normal file
|
@ -0,0 +1,871 @@
|
||||||
|
body {
|
||||||
|
font-family: 'Helvetica Neue';
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 25px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
article {
|
||||||
|
padding-bottom: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
max-width: 732px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
article h1,
|
||||||
|
article h2 {
|
||||||
|
font-family: 'Georgia';
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 31px;
|
||||||
|
margin: 21px 18px 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
min-height: 31px;
|
||||||
|
}
|
||||||
|
article h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 30px;
|
||||||
|
margin: -6px 18px 12px;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
article h6.kicker {
|
||||||
|
font-family: 'Helvetica Neue';
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 17px;
|
||||||
|
margin: 21px 18px 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #79828B;
|
||||||
|
}
|
||||||
|
article h6.kicker + h1 {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
article address {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #79828B;
|
||||||
|
margin: 12px 18px 21px;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
article.rtl address {
|
||||||
|
direction: ltr;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
article address figure {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
float: right;
|
||||||
|
margin: 0 0 0 12px;
|
||||||
|
background: no-repeat center;
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
article address a,
|
||||||
|
article address a[href] {
|
||||||
|
color: #79828B;
|
||||||
|
}
|
||||||
|
article address a[href] {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
article a[href] {
|
||||||
|
color: #007EE5;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
article span.reference {
|
||||||
|
border: dotted #ddd;
|
||||||
|
border-width: 1px 1px 1px 2px;
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
margin: 0 1px;
|
||||||
|
padding: 2px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
article.rtl span.reference {
|
||||||
|
border-width: 1px 0 1px 1px;
|
||||||
|
}
|
||||||
|
article code {
|
||||||
|
font-size: 0.94em;
|
||||||
|
background: #eee;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 0 3px 1px;
|
||||||
|
}
|
||||||
|
article mark {
|
||||||
|
background: #fcf8e3;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 0 3px 1px;
|
||||||
|
}
|
||||||
|
article sup,
|
||||||
|
article sub {
|
||||||
|
font-size: 0.75em;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
article p {
|
||||||
|
margin: 0 18px 12px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
article ul p,
|
||||||
|
article ol p {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
}
|
||||||
|
article pre,
|
||||||
|
article pre.hljs {
|
||||||
|
font-family: Menlo;
|
||||||
|
margin: 14px 0;
|
||||||
|
padding: 7px 18px;
|
||||||
|
background: #F5F8FC;
|
||||||
|
font-size: 16px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-x: visible;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
article ul pre,
|
||||||
|
article ol pre,
|
||||||
|
article ul pre.hljs,
|
||||||
|
article ol pre.hljs {
|
||||||
|
margin: 6px 0 6px -18px;
|
||||||
|
}
|
||||||
|
article.rtl ul pre,
|
||||||
|
article.rtl ol pre,
|
||||||
|
article.rtl ul pre.hljs,
|
||||||
|
article.rtl ol pre.hljs {
|
||||||
|
margin-right: -18px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
article pre + pre {
|
||||||
|
margin-top: -14px;
|
||||||
|
}
|
||||||
|
article h3,
|
||||||
|
article h4 {
|
||||||
|
font-family: 'Georgia';
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 30px;
|
||||||
|
margin: 18px 18px 9px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
article h4 {
|
||||||
|
font-size: 19px;
|
||||||
|
margin: 18px 18px 7px;
|
||||||
|
}
|
||||||
|
article ul h3,
|
||||||
|
article ol h3 {
|
||||||
|
margin: 12px 0 7px;
|
||||||
|
}
|
||||||
|
article ul h4,
|
||||||
|
article ol h4 {
|
||||||
|
margin: 10px 0 5px;
|
||||||
|
}
|
||||||
|
article blockquote {
|
||||||
|
font-family: 'Georgia';
|
||||||
|
margin: 18px 18px 16px;
|
||||||
|
padding-left: 22px;
|
||||||
|
position: relative;
|
||||||
|
font-style: italic;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
article blockquote:before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 1px;
|
||||||
|
top: 3px;
|
||||||
|
bottom: 3px;
|
||||||
|
width: 3px;
|
||||||
|
background-color: #000;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
article.rtl blockquote {
|
||||||
|
padding-right: 22px;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
article.rtl blockquote:before {
|
||||||
|
right: 1px;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
article aside {
|
||||||
|
font-family: 'Georgia';
|
||||||
|
margin: 18px 18px 16px;
|
||||||
|
padding: 0 18px;
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
article ul blockquote,
|
||||||
|
article ol blockquote,
|
||||||
|
article ul aside,
|
||||||
|
article ol aside {
|
||||||
|
margin: 12px 0 10px;
|
||||||
|
}
|
||||||
|
article blockquote cite,
|
||||||
|
article aside cite,
|
||||||
|
article footer cite,
|
||||||
|
article .iv-pullquote cite {
|
||||||
|
font-family: 'Helvetica Neue';
|
||||||
|
font-size: 15px;
|
||||||
|
display: block;
|
||||||
|
color: #79828B;
|
||||||
|
line-height: 19px;
|
||||||
|
padding: 5px 0 2px;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
article hr {
|
||||||
|
width: 50%;
|
||||||
|
margin: 36px auto 26px;
|
||||||
|
padding: 2px 0 10px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
article ul hr,
|
||||||
|
article ol hr {
|
||||||
|
margin: 18px auto;
|
||||||
|
}
|
||||||
|
article hr:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
border-top: 1px solid #c9cdd1;
|
||||||
|
padding: 0 0 2px;
|
||||||
|
}
|
||||||
|
article footer hr {
|
||||||
|
margin: 18px auto 6px;
|
||||||
|
}
|
||||||
|
article ul,
|
||||||
|
article ol {
|
||||||
|
margin: 0 18px 12px;
|
||||||
|
padding-left: 18px;
|
||||||
|
}
|
||||||
|
article.rtl ul,
|
||||||
|
article.rtl ol {
|
||||||
|
padding-right: 18px;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
/*article ul {
|
||||||
|
list-style: none;
|
||||||
|
}*/
|
||||||
|
article ul > li,
|
||||||
|
article ol > li {
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
article.rtl ul > li,
|
||||||
|
article.rtl ol > li {
|
||||||
|
padding-right: 4px;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
/*article ul > li {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
article ul > li:before {
|
||||||
|
content: '\2022';
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
font-size: 163%;
|
||||||
|
left: -19px;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
article.rtl ul > li:before {
|
||||||
|
left: auto;
|
||||||
|
right: -19px;
|
||||||
|
}*/
|
||||||
|
article ul ul,
|
||||||
|
article ul ol,
|
||||||
|
article ol ul,
|
||||||
|
article ol ol {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
article table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
article table.bordered,
|
||||||
|
article table.bordered td,
|
||||||
|
article table.bordered th {
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
article table.striped tr:nth-child(odd) td {
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
}
|
||||||
|
article table caption {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 18px;
|
||||||
|
margin: 4px 0 7px;
|
||||||
|
text-align: left;
|
||||||
|
color: #79828B;
|
||||||
|
}
|
||||||
|
article.rtl table caption {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
article td,
|
||||||
|
article th {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 21px;
|
||||||
|
padding: 6px 5px 5px;
|
||||||
|
background-color: #fff;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
article th {
|
||||||
|
background-color: #efefef;
|
||||||
|
}
|
||||||
|
article.rtl table td,
|
||||||
|
article.rtl table th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
article details {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
padding: 0 0 1px;
|
||||||
|
}
|
||||||
|
article details:before {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
border-bottom: 1px solid #c8c7cb;
|
||||||
|
position: absolute;
|
||||||
|
left: 18px;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
article.rtl details:before {
|
||||||
|
right: 18px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
article details + details {
|
||||||
|
margin-top: -12px;
|
||||||
|
}
|
||||||
|
article details > details:last-child {
|
||||||
|
margin-bottom: -1px;
|
||||||
|
}
|
||||||
|
article summary {
|
||||||
|
padding: 10px 18px 10px 42px;
|
||||||
|
line-height: 25px;
|
||||||
|
min-height: 25px;
|
||||||
|
}
|
||||||
|
article.rtl summary {
|
||||||
|
padding-left: 18px;
|
||||||
|
padding-right: 42px;
|
||||||
|
}
|
||||||
|
article summary:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
article summary:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
article summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
article summary::marker {
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
article summary:before {
|
||||||
|
content: '';
|
||||||
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAICAYAAADN5B7xAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAH1JREFUeNqEjUEKgCAQRSfrNi1bdZFadJjsMC46SSAIHqjB5mcFqdFfhD3eUyKZtb6ln92O2janmXdvrRu+ZTfAgasu1jAHU4qiHAwc/Ff4oCQKsxxZ0NT33XrxUTjkWvgiXFf3TWkU6Vt+XihH515yFiQRpfLnEMUw3yHAABZNTT9emBrvAAAAAElFTkSuQmCC');
|
||||||
|
transition: all .2s ease;
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
width: 12px;
|
||||||
|
height: 8px;
|
||||||
|
left: 18px;
|
||||||
|
top: 18px;
|
||||||
|
}
|
||||||
|
article.rtl summary:before {
|
||||||
|
right: 18px;
|
||||||
|
left: auto;
|
||||||
|
}
|
||||||
|
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
||||||
|
article summary:before {
|
||||||
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAQCAYAAAAMJL+VAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPxJREFUeNq8lEESgiAUhgFbZ0epSW28gB2pZbrrSukBHDWto1TrwHih45AiaDOxesLP9w1PBlzXNfrLSNPqkGWV8ysHGMBqv4mAlyFC7MRPk+T51Z0Lh73AAJZgIoRFUR/bEMb4TggJPG9TTIUzxmIuWHWzOCLfQQgwRiedRMBpIsObFvn+NgSTLEE2bCiKm6eDQ0bAkS2v4AjYuPvJcqtEu9DDshaB665zFZzSV6yCfyr5JplLTOA9wZiEg/a+72Qic9nxubMOPijQSZraCK4UjEiezSVYmsBHBSrJAEIJ1wr0knG4kUAt0cONBX2JGXzGi1uG7SNmOt4CDADc4r+K4txg+wAAAABJRU5ErkJggg==');
|
||||||
|
background-size: 12px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
article details[open] > summary:before {
|
||||||
|
/*transform: rotateZ(-180deg);*/
|
||||||
|
transform: scaleY(-1);
|
||||||
|
}
|
||||||
|
article li summary {
|
||||||
|
padding-left: 24px;
|
||||||
|
}
|
||||||
|
article li details:before,
|
||||||
|
article li summary:before {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
video,
|
||||||
|
iframe {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 400px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
video {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
audio {
|
||||||
|
width: 100%;
|
||||||
|
width: calc(100% - 36px);
|
||||||
|
margin: 0 18px;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
img.pic {
|
||||||
|
max-height: none;
|
||||||
|
font-size: inherit;
|
||||||
|
vertical-align: middle;
|
||||||
|
position: relative;
|
||||||
|
top: -0.1em;
|
||||||
|
}
|
||||||
|
img.pic.optional {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
body:hover img.pic.optional {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
iframe.autosize {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
.iframe-wrap {
|
||||||
|
max-width: 100%;
|
||||||
|
vertical-align: top;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.iframe-wrap iframe {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
figure {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
figure.nowide {
|
||||||
|
margin-left: 18px;
|
||||||
|
margin-right: 18px;
|
||||||
|
}
|
||||||
|
figure.nowide figcaption {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
ul figure.nowide,
|
||||||
|
ol figure.nowide {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
figure > figure {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
figcaption {
|
||||||
|
font-size: 15px;
|
||||||
|
color: #79828B;
|
||||||
|
padding: 6px 18px 0;
|
||||||
|
line-height: 19px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
article.rtl figcaption {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
ul figcaption,
|
||||||
|
ol figcaption {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
figcaption > cite {
|
||||||
|
font-family: 'Helvetica Neue';
|
||||||
|
font-size: 12px;
|
||||||
|
display: block;
|
||||||
|
line-height: 15px;
|
||||||
|
padding: 2px 0 0;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
footer {
|
||||||
|
margin: 12px 18px;
|
||||||
|
color: #79828B;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure.slideshow-wrap {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
figure.slideshow {
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 100%;
|
||||||
|
background: #000;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
figure.slideshow > figure {
|
||||||
|
position: static !important;
|
||||||
|
display: inline-block;
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: margin .3s;
|
||||||
|
}
|
||||||
|
figure.slideshow > figure figcaption {
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 36px;
|
||||||
|
}
|
||||||
|
figure.slideshow > figure figcaption:after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
top: -75px;
|
||||||
|
background: -moz-linear-gradient(top,rgba(64,64,64,0),rgba(64,64,64,.55));
|
||||||
|
background: -webkit-gradient(linear,0 0,0 100%,from(rgba(64,64,64,0)),to(rgba(64,64,64,.55)));
|
||||||
|
background: -o-linear-gradient(rgba(64,64,64,0),rgba(64,64,64,.55));
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
figure.slideshow > figure figcaption > span,
|
||||||
|
figure.slideshow > figure figcaption > cite {
|
||||||
|
position: relative;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 1px rgba(0, 0, 0, .4);
|
||||||
|
z-index: 1;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
figure.slideshow > figure figcaption > span {
|
||||||
|
display: -webkit-box;
|
||||||
|
max-height: 3.8em;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
figure.slideshow > figure figcaption code {
|
||||||
|
text-shadow: none;
|
||||||
|
background: rgba(204, 204, 204, .7);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
figure.slideshow > figure figcaption mark {
|
||||||
|
text-shadow: none;
|
||||||
|
background: rgba(33, 123, 134, .7);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
figure.slideshow > figure figcaption a,
|
||||||
|
figure.slideshow > figure figcaption a:hover {
|
||||||
|
color: #66baff;
|
||||||
|
}
|
||||||
|
.slideshow-buttons {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 10px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.slideshow-buttons > fieldset {
|
||||||
|
padding: 0 10px 20px;
|
||||||
|
margin: 0 0 -20px;
|
||||||
|
border: none;
|
||||||
|
line-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-x: auto;
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
.slideshow-buttons label {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 7px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.slideshow-buttons input {
|
||||||
|
position: absolute;
|
||||||
|
left: -10000px;
|
||||||
|
}
|
||||||
|
.slideshow-buttons label i {
|
||||||
|
display: inline-block;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 0 3px rgba(0, 0, 0, .4);
|
||||||
|
border-radius: 3.5px;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
opacity: .6;
|
||||||
|
transition: opacity .3s;
|
||||||
|
}
|
||||||
|
.slideshow-buttons input:checked ~ i {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure.collage {
|
||||||
|
margin: -2px 16px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
figure.collage > figure {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
width: calc(25% - 4px);
|
||||||
|
margin: 2px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
figure.collage > figure > i {
|
||||||
|
background: no-repeat center;
|
||||||
|
background-size: cover;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure.table-wrap {
|
||||||
|
overflow: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
figure.table {
|
||||||
|
display: table-cell;
|
||||||
|
padding: 0 18px;
|
||||||
|
}
|
||||||
|
article ol figure.table-wrap,
|
||||||
|
article ul figure.table-wrap {
|
||||||
|
margin-top: 7px;
|
||||||
|
}
|
||||||
|
article ol figure.table,
|
||||||
|
article ul figure.table {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure blockquote.embed-post {
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
article.rtl figure blockquote.embed-post {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
blockquote.embed-post address {
|
||||||
|
margin: 0;
|
||||||
|
padding: 5px 0 9px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
blockquote.embed-post address figure {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
float: left;
|
||||||
|
margin: 0 12px 0 0;
|
||||||
|
background: no-repeat center;
|
||||||
|
background-size: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
article.rtl blockquote.embed-post address figure {
|
||||||
|
float: right;
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
blockquote.embed-post address a {
|
||||||
|
display: inline-block;
|
||||||
|
padding-top: 2px;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
blockquote.embed-post address time {
|
||||||
|
display: block;
|
||||||
|
line-height: 19px;
|
||||||
|
}
|
||||||
|
blockquote.embed-post p,
|
||||||
|
blockquote.embed-post blockquote {
|
||||||
|
margin: 0 0 7px;
|
||||||
|
clear: left;
|
||||||
|
}
|
||||||
|
blockquote.embed-post figcaption {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
blockquote.embed-post figure.collage {
|
||||||
|
margin-left: -2px;
|
||||||
|
margin-right: -2px;
|
||||||
|
}
|
||||||
|
blockquote.embed-post footer {
|
||||||
|
margin: 12px 0 0;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
blockquote.embed-post footer hr {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.embed-post {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
background: #f7f7f7;
|
||||||
|
margin: 0 18px 12px;
|
||||||
|
padding: 24px 18px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
section.embed-post strong {
|
||||||
|
font-size: 21px;
|
||||||
|
font-weight: normal;
|
||||||
|
display: block;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
section.embed-post small {
|
||||||
|
display: block;
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.related {
|
||||||
|
margin: 7px 0 12px;
|
||||||
|
}
|
||||||
|
section.related h4 {
|
||||||
|
font-family: 'Helvetica Neue';
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 26px;
|
||||||
|
font-weight: 500;
|
||||||
|
display: block;
|
||||||
|
padding: 7px 18px;
|
||||||
|
background: #f7f7f7;
|
||||||
|
margin: 0;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
section.related a.related-link {
|
||||||
|
display: block;
|
||||||
|
padding: 15px 18px 16px;
|
||||||
|
background: #fff;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
section.related a.related-link:after {
|
||||||
|
content: '';
|
||||||
|
display: block;
|
||||||
|
border-bottom: 1px solid #c8c7cb;
|
||||||
|
position: absolute;
|
||||||
|
left: 18px;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
section.related .related-link-url {
|
||||||
|
display: block;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 18px;
|
||||||
|
padding: 7px 0;
|
||||||
|
color: #999;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
section.related .related-link-thumb {
|
||||||
|
display: inline-block;
|
||||||
|
float: right;
|
||||||
|
width: 87px;
|
||||||
|
height: 87px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: no-repeat center;
|
||||||
|
background-size: cover;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
section.related .related-link-content {
|
||||||
|
display: block;
|
||||||
|
margin: -3px 0;
|
||||||
|
}
|
||||||
|
section.related .related-link-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 18px;
|
||||||
|
display: block;
|
||||||
|
display: -webkit-box;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
max-height: 36px;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
section.related .related-link-desc {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 17px;
|
||||||
|
display: block;
|
||||||
|
display: -webkit-box;
|
||||||
|
max-height: 51px;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
section.related .related-link-source {
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 17px;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 4px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: #818181;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.message {
|
||||||
|
position: absolute;
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
section.message.static {
|
||||||
|
position: static;
|
||||||
|
min-height: 200px;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
section.message > aside {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
text-align: center;
|
||||||
|
color: #999;
|
||||||
|
font-size: 24px;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
section.message > aside > cite {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 10px 0 0;
|
||||||
|
font-style: normal;
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
section.channel {
|
||||||
|
margin-top: -16px;
|
||||||
|
margin-bottom: -9px;
|
||||||
|
}
|
||||||
|
section.channel:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
section.channel > a {
|
||||||
|
display: block;
|
||||||
|
padding: 7px 18px;
|
||||||
|
background: #f7f7f7;
|
||||||
|
}
|
||||||
|
section.channel > a:before {
|
||||||
|
content: 'Join';
|
||||||
|
color: #3196e8;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-left: 7px;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
section.channel > a > h4 {
|
||||||
|
font-family: 'Helvetica Neue';
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 26px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
color: #000;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iv-pullquote {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 420px;
|
||||||
|
font-size: 19px;
|
||||||
|
display: block;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.iv-photo-wrap {
|
||||||
|
width: 100%;
|
||||||
|
background-size: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.iv-photo {
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
86
Telegram/Resources/iv_html/page.js
Normal file
86
Telegram/Resources/iv_html/page.js
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
var IV = {
|
||||||
|
sendPostMessage: function(data) {
|
||||||
|
try {
|
||||||
|
window.parent.postMessage(JSON.stringify(data), window.parentOrigin);
|
||||||
|
} catch(e) {}
|
||||||
|
},
|
||||||
|
frameClickHandler: function(e) {
|
||||||
|
var target = e.target, href;
|
||||||
|
do {
|
||||||
|
if (target.tagName == 'SUMMARY') return;
|
||||||
|
if (target.tagName == 'DETAILS') return;
|
||||||
|
if (target.tagName == 'LABEL') return;
|
||||||
|
if (target.tagName == 'AUDIO') return;
|
||||||
|
if (target.tagName == 'A') break;
|
||||||
|
} while (target = target.parentNode);
|
||||||
|
if (target && target.hasAttribute('href')) {
|
||||||
|
var base_loc = document.createElement('A');
|
||||||
|
base_loc.href = window.currentUrl;
|
||||||
|
if (base_loc.origin != target.origin ||
|
||||||
|
base_loc.pathname != target.pathname ||
|
||||||
|
base_loc.search != target.search) {
|
||||||
|
IV.sendPostMessage({event: 'link_click', url: target.href});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
},
|
||||||
|
postMessageHandler: function(event) {
|
||||||
|
if (event.source !== window.parent ||
|
||||||
|
event.origin != window.parentOrigin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
} catch(e) {
|
||||||
|
var data = {};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
slideshowSlide: function(el, next) {
|
||||||
|
var dir = window.getComputedStyle(el, null).direction || 'ltr';
|
||||||
|
var marginProp = dir == 'rtl' ? 'marginRight' : 'marginLeft';
|
||||||
|
if (next) {
|
||||||
|
var s = el.previousSibling.s;
|
||||||
|
s.value = (+s.value + 1 == s.length) ? 0 : +s.value + 1;
|
||||||
|
s.forEach(function(el){ el.checked && el.parentNode.scrollIntoView && el.parentNode.scrollIntoView({behavior: 'smooth', block: 'center', inline: 'center'}); });
|
||||||
|
el.firstChild.style[marginProp] = (-100 * s.value) + '%';
|
||||||
|
} else {
|
||||||
|
el.form.nextSibling.firstChild.style[marginProp] = (-100 * el.value) + '%';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
initPreBlocks: function() {
|
||||||
|
if (!hljs) return;
|
||||||
|
var pres = document.getElementsByTagName('pre');
|
||||||
|
for (var i = 0; i < pres.length; i++) {
|
||||||
|
if (pres[i].hasAttribute('data-language')) {
|
||||||
|
hljs.highlightBlock(pres[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
initEmbedBlocks: function() {
|
||||||
|
var iframes = document.getElementsByTagName('iframe');
|
||||||
|
for (var i = 0; i < iframes.length; i++) {
|
||||||
|
(function(iframe) {
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
if (event.source !== iframe.contentWindow ||
|
||||||
|
event.origin != window.origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(event.data);
|
||||||
|
} catch(e) {
|
||||||
|
var data = {};
|
||||||
|
}
|
||||||
|
if (data.eventType == 'resize_frame') {
|
||||||
|
if (data.eventData.height) {
|
||||||
|
iframe.style.height = data.eventData.height + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
})(iframes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.onclick = IV.frameClickHandler;
|
||||||
|
window.onmessage = IV.postMessageHandler;
|
8
Telegram/Resources/qrc/telegram/iv.qrc
Normal file
8
Telegram/Resources/qrc/telegram/iv.qrc
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="/iv">
|
||||||
|
<file alias="page.css">../../iv_html/page.css</file>
|
||||||
|
<file alias="page.js">../../iv_html/page.js</file>
|
||||||
|
<file alias="highlight.css">../../iv_html/highlight.9.12.0.css</file>
|
||||||
|
<file alias="highlight.js">../../iv_html/highlight.9.12.0.js</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
|
@ -33,6 +33,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "info/boosts/info_boosts_widget.h"
|
#include "info/boosts/info_boosts_widget.h"
|
||||||
#include "info/profile/info_profile_emoji_status_panel.h"
|
#include "info/profile/info_profile_emoji_status_panel.h"
|
||||||
#include "info/info_memento.h"
|
#include "info/info_memento.h"
|
||||||
|
#include "iv/iv_data.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "lottie/lottie_icon.h"
|
#include "lottie/lottie_icon.h"
|
||||||
#include "lottie/lottie_single_player.h"
|
#include "lottie/lottie_single_player.h"
|
||||||
|
@ -310,6 +311,7 @@ PreviewWrap::PreviewWrap(
|
||||||
nullptr, // photo
|
nullptr, // photo
|
||||||
nullptr, // document
|
nullptr, // document
|
||||||
WebPageCollage(),
|
WebPageCollage(),
|
||||||
|
nullptr, // iv
|
||||||
0, // duration
|
0, // duration
|
||||||
QString(), // author
|
QString(), // author
|
||||||
false, // hasLargeMedia
|
false, // hasLargeMedia
|
||||||
|
|
|
@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "api/api_updates.h"
|
#include "api/api_updates.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "countries/countries_manager.h"
|
#include "countries/countries_manager.h"
|
||||||
|
#include "iv/iv_instance.h"
|
||||||
#include "lang/lang_file_parser.h"
|
#include "lang/lang_file_parser.h"
|
||||||
#include "lang/lang_translator.h"
|
#include "lang/lang_translator.h"
|
||||||
#include "lang/lang_cloud_manager.h"
|
#include "lang/lang_cloud_manager.h"
|
||||||
|
@ -162,6 +163,7 @@ Application::Application()
|
||||||
, _domain(std::make_unique<Main::Domain>(cDataFile()))
|
, _domain(std::make_unique<Main::Domain>(cDataFile()))
|
||||||
, _exportManager(std::make_unique<Export::Manager>())
|
, _exportManager(std::make_unique<Export::Manager>())
|
||||||
, _calls(std::make_unique<Calls::Instance>())
|
, _calls(std::make_unique<Calls::Instance>())
|
||||||
|
, _iv(std::make_unique<Iv::Instance>())
|
||||||
, _langpack(std::make_unique<Lang::Instance>())
|
, _langpack(std::make_unique<Lang::Instance>())
|
||||||
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
|
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
|
||||||
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
|
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
|
||||||
|
@ -218,6 +220,7 @@ Application::~Application() {
|
||||||
// Domain::finish() and there is a violation on Ensures(started()).
|
// Domain::finish() and there is a violation on Ensures(started()).
|
||||||
Payments::CheckoutProcess::ClearAll();
|
Payments::CheckoutProcess::ClearAll();
|
||||||
InlineBots::AttachWebView::ClearAll();
|
InlineBots::AttachWebView::ClearAll();
|
||||||
|
_iv->closeAll();
|
||||||
|
|
||||||
_domain->finish();
|
_domain->finish();
|
||||||
|
|
||||||
|
@ -1272,6 +1275,8 @@ bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||||
return false;
|
return false;
|
||||||
} else if (_calls->hasActivePanel(session)) {
|
} else if (_calls->hasActivePanel(session)) {
|
||||||
return true;
|
return true;
|
||||||
|
} else if (_iv->hasActiveWindow(session)) {
|
||||||
|
return true;
|
||||||
} else if (const auto window = _lastActiveWindow) {
|
} else if (const auto window = _lastActiveWindow) {
|
||||||
return (window->account().maybeSession() == session)
|
return (window->account().maybeSession() == session)
|
||||||
&& window->widget()->isActive();
|
&& window->widget()->isActive();
|
||||||
|
|
|
@ -45,6 +45,10 @@ class Account;
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Main
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
class Instance;
|
||||||
|
} // namespace Iv
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Animations {
|
namespace Animations {
|
||||||
class Manager;
|
class Manager;
|
||||||
|
@ -280,6 +284,11 @@ public:
|
||||||
return *_calls;
|
return *_calls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iv.
|
||||||
|
Iv::Instance &iv() const {
|
||||||
|
return *_iv;
|
||||||
|
}
|
||||||
|
|
||||||
void logout(Main::Account *account = nullptr);
|
void logout(Main::Account *account = nullptr);
|
||||||
void logoutWithChecks(Main::Account *account);
|
void logoutWithChecks(Main::Account *account);
|
||||||
void forceLogOut(
|
void forceLogOut(
|
||||||
|
@ -409,6 +418,7 @@ private:
|
||||||
const std::unique_ptr<Main::Domain> _domain;
|
const std::unique_ptr<Main::Domain> _domain;
|
||||||
const std::unique_ptr<Export::Manager> _exportManager;
|
const std::unique_ptr<Export::Manager> _exportManager;
|
||||||
const std::unique_ptr<Calls::Instance> _calls;
|
const std::unique_ptr<Calls::Instance> _calls;
|
||||||
|
const std::unique_ptr<Iv::Instance> _iv;
|
||||||
base::flat_map<
|
base::flat_map<
|
||||||
Main::Account*,
|
Main::Account*,
|
||||||
std::unique_ptr<Window::Controller>> _primaryWindows;
|
std::unique_ptr<Window::Controller>> _primaryWindows;
|
||||||
|
|
|
@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
#include "boxes/abstract_box.h"
|
#include "boxes/abstract_box.h"
|
||||||
#include "passport/passport_form_controller.h"
|
#include "passport/passport_form_controller.h"
|
||||||
|
#include "iv/iv_data.h"
|
||||||
#include "lang/lang_keys.h" // tr::lng_deleted(tr::now) in user name
|
#include "lang/lang_keys.h" // tr::lng_deleted(tr::now) in user name
|
||||||
#include "data/business/data_business_chatbots.h"
|
#include "data/business/data_business_chatbots.h"
|
||||||
#include "data/business/data_business_info.h"
|
#include "data/business/data_business_info.h"
|
||||||
|
@ -3365,6 +3366,7 @@ not_null<WebPageData*> Session::processWebpage(
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
WebPageCollage(),
|
WebPageCollage(),
|
||||||
|
nullptr,
|
||||||
0,
|
0,
|
||||||
QString(),
|
QString(),
|
||||||
false,
|
false,
|
||||||
|
@ -3389,6 +3391,7 @@ not_null<WebPageData*> Session::webpage(
|
||||||
nullptr,
|
nullptr,
|
||||||
nullptr,
|
nullptr,
|
||||||
WebPageCollage(),
|
WebPageCollage(),
|
||||||
|
nullptr,
|
||||||
0,
|
0,
|
||||||
QString(),
|
QString(),
|
||||||
false,
|
false,
|
||||||
|
@ -3406,6 +3409,7 @@ not_null<WebPageData*> Session::webpage(
|
||||||
PhotoData *photo,
|
PhotoData *photo,
|
||||||
DocumentData *document,
|
DocumentData *document,
|
||||||
WebPageCollage &&collage,
|
WebPageCollage &&collage,
|
||||||
|
std::unique_ptr<Iv::Data> iv,
|
||||||
int duration,
|
int duration,
|
||||||
const QString &author,
|
const QString &author,
|
||||||
bool hasLargeMedia,
|
bool hasLargeMedia,
|
||||||
|
@ -3423,6 +3427,7 @@ not_null<WebPageData*> Session::webpage(
|
||||||
photo,
|
photo,
|
||||||
document,
|
document,
|
||||||
std::move(collage),
|
std::move(collage),
|
||||||
|
std::move(iv),
|
||||||
duration,
|
duration,
|
||||||
author,
|
author,
|
||||||
hasLargeMedia,
|
hasLargeMedia,
|
||||||
|
@ -3503,6 +3508,14 @@ void Session::webpageApplyFields(
|
||||||
}, [](const auto &) {});
|
}, [](const auto &) {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (const auto page = data.vcached_page()) {
|
||||||
|
for (const auto photo : page->data().vphotos().v) {
|
||||||
|
processPhoto(photo);
|
||||||
|
}
|
||||||
|
for (const auto document : page->data().vdocuments().v) {
|
||||||
|
processDocument(document);
|
||||||
|
}
|
||||||
|
}
|
||||||
webpageApplyFields(
|
webpageApplyFields(
|
||||||
page,
|
page,
|
||||||
(story ? WebPageType::Story : ParseWebPageType(data)),
|
(story ? WebPageType::Story : ParseWebPageType(data)),
|
||||||
|
@ -3523,6 +3536,9 @@ void Session::webpageApplyFields(
|
||||||
? processDocument(*document).get()
|
? processDocument(*document).get()
|
||||||
: lookupThemeDocument()),
|
: lookupThemeDocument()),
|
||||||
WebPageCollage(this, data),
|
WebPageCollage(this, data),
|
||||||
|
(data.vcached_page()
|
||||||
|
? std::make_unique<Iv::Data>(data, *data.vcached_page())
|
||||||
|
: nullptr),
|
||||||
data.vduration().value_or_empty(),
|
data.vduration().value_or_empty(),
|
||||||
qs(data.vauthor().value_or_empty()),
|
qs(data.vauthor().value_or_empty()),
|
||||||
data.is_has_large_media(),
|
data.is_has_large_media(),
|
||||||
|
@ -3541,6 +3557,7 @@ void Session::webpageApplyFields(
|
||||||
PhotoData *photo,
|
PhotoData *photo,
|
||||||
DocumentData *document,
|
DocumentData *document,
|
||||||
WebPageCollage &&collage,
|
WebPageCollage &&collage,
|
||||||
|
std::unique_ptr<Iv::Data> iv,
|
||||||
int duration,
|
int duration,
|
||||||
const QString &author,
|
const QString &author,
|
||||||
bool hasLargeMedia,
|
bool hasLargeMedia,
|
||||||
|
@ -3557,6 +3574,7 @@ void Session::webpageApplyFields(
|
||||||
photo,
|
photo,
|
||||||
document,
|
document,
|
||||||
std::move(collage),
|
std::move(collage),
|
||||||
|
std::move(iv),
|
||||||
duration,
|
duration,
|
||||||
author,
|
author,
|
||||||
hasLargeMedia,
|
hasLargeMedia,
|
||||||
|
|
|
@ -38,6 +38,10 @@ namespace Passport {
|
||||||
struct SavedCredentials;
|
struct SavedCredentials;
|
||||||
} // namespace Passport
|
} // namespace Passport
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
class Data;
|
||||||
|
} // namespace Iv
|
||||||
|
|
||||||
namespace Data {
|
namespace Data {
|
||||||
|
|
||||||
class Folder;
|
class Folder;
|
||||||
|
@ -581,6 +585,7 @@ public:
|
||||||
PhotoData *photo,
|
PhotoData *photo,
|
||||||
DocumentData *document,
|
DocumentData *document,
|
||||||
WebPageCollage &&collage,
|
WebPageCollage &&collage,
|
||||||
|
std::unique_ptr<Iv::Data> iv,
|
||||||
int duration,
|
int duration,
|
||||||
const QString &author,
|
const QString &author,
|
||||||
bool hasLargeMedia,
|
bool hasLargeMedia,
|
||||||
|
@ -858,6 +863,7 @@ private:
|
||||||
PhotoData *photo,
|
PhotoData *photo,
|
||||||
DocumentData *document,
|
DocumentData *document,
|
||||||
WebPageCollage &&collage,
|
WebPageCollage &&collage,
|
||||||
|
std::unique_ptr<Iv::Data> iv,
|
||||||
int duration,
|
int duration,
|
||||||
const QString &author,
|
const QString &author,
|
||||||
bool hasLargeMedia,
|
bool hasLargeMedia,
|
||||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "data/data_channel.h"
|
#include "data/data_channel.h"
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
|
#include "iv/iv_data.h"
|
||||||
#include "ui/image/image.h"
|
#include "ui/image/image.h"
|
||||||
#include "ui/text/text_entity.h"
|
#include "ui/text/text_entity.h"
|
||||||
|
|
||||||
|
@ -206,6 +207,8 @@ WebPageData::WebPageData(not_null<Data::Session*> owner, const WebPageId &id)
|
||||||
, _owner(owner) {
|
, _owner(owner) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebPageData::~WebPageData() = default;
|
||||||
|
|
||||||
Data::Session &WebPageData::owner() const {
|
Data::Session &WebPageData::owner() const {
|
||||||
return *_owner;
|
return *_owner;
|
||||||
}
|
}
|
||||||
|
@ -225,6 +228,7 @@ bool WebPageData::applyChanges(
|
||||||
PhotoData *newPhoto,
|
PhotoData *newPhoto,
|
||||||
DocumentData *newDocument,
|
DocumentData *newDocument,
|
||||||
WebPageCollage &&newCollage,
|
WebPageCollage &&newCollage,
|
||||||
|
std::unique_ptr<Iv::Data> newIv,
|
||||||
int newDuration,
|
int newDuration,
|
||||||
const QString &newAuthor,
|
const QString &newAuthor,
|
||||||
bool newHasLargeMedia,
|
bool newHasLargeMedia,
|
||||||
|
@ -276,6 +280,7 @@ bool WebPageData::applyChanges(
|
||||||
&& photo == newPhoto
|
&& photo == newPhoto
|
||||||
&& document == newDocument
|
&& document == newDocument
|
||||||
&& collage.items == newCollage.items
|
&& collage.items == newCollage.items
|
||||||
|
&& (!iv == !newIv)
|
||||||
&& duration == newDuration
|
&& duration == newDuration
|
||||||
&& author == resultAuthor
|
&& author == resultAuthor
|
||||||
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
|
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
|
||||||
|
@ -296,6 +301,7 @@ bool WebPageData::applyChanges(
|
||||||
photo = newPhoto;
|
photo = newPhoto;
|
||||||
document = newDocument;
|
document = newDocument;
|
||||||
collage = std::move(newCollage);
|
collage = std::move(newCollage);
|
||||||
|
iv = std::move(newIv);
|
||||||
duration = newDuration;
|
duration = newDuration;
|
||||||
author = resultAuthor;
|
author = resultAuthor;
|
||||||
pendingTill = newPendingTill;
|
pendingTill = newPendingTill;
|
||||||
|
|
|
@ -17,6 +17,10 @@ namespace Data {
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
class Data;
|
||||||
|
} // namespace Iv
|
||||||
|
|
||||||
enum class WebPageType : uint8 {
|
enum class WebPageType : uint8 {
|
||||||
None,
|
None,
|
||||||
|
|
||||||
|
@ -64,6 +68,7 @@ struct WebPageCollage {
|
||||||
|
|
||||||
struct WebPageData {
|
struct WebPageData {
|
||||||
WebPageData(not_null<Data::Session*> owner, const WebPageId &id);
|
WebPageData(not_null<Data::Session*> owner, const WebPageId &id);
|
||||||
|
~WebPageData();
|
||||||
|
|
||||||
[[nodiscard]] Data::Session &owner() const;
|
[[nodiscard]] Data::Session &owner() const;
|
||||||
[[nodiscard]] Main::Session &session() const;
|
[[nodiscard]] Main::Session &session() const;
|
||||||
|
@ -79,6 +84,7 @@ struct WebPageData {
|
||||||
PhotoData *newPhoto,
|
PhotoData *newPhoto,
|
||||||
DocumentData *newDocument,
|
DocumentData *newDocument,
|
||||||
WebPageCollage &&newCollage,
|
WebPageCollage &&newCollage,
|
||||||
|
std::unique_ptr<Iv::Data> newIv,
|
||||||
int newDuration,
|
int newDuration,
|
||||||
const QString &newAuthor,
|
const QString &newAuthor,
|
||||||
bool newHasLargeMedia,
|
bool newHasLargeMedia,
|
||||||
|
@ -105,6 +111,7 @@ struct WebPageData {
|
||||||
PhotoData *photo = nullptr;
|
PhotoData *photo = nullptr;
|
||||||
DocumentData *document = nullptr;
|
DocumentData *document = nullptr;
|
||||||
WebPageCollage collage;
|
WebPageCollage collage;
|
||||||
|
std::unique_ptr<Iv::Data> iv;
|
||||||
int duration = 0;
|
int duration = 0;
|
||||||
TimeId pendingTill = 0;
|
TimeId pendingTill = 0;
|
||||||
uint32 version : 30 = 0;
|
uint32 version : 30 = 0;
|
||||||
|
|
|
@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/history.h"
|
#include "history/history.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "history/view/history_view_cursor_state.h"
|
#include "history/view/history_view_cursor_state.h"
|
||||||
|
#include "iv/iv_data.h"
|
||||||
|
#include "iv/iv_controller.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "ui/effects/ripple_animation.h"
|
#include "ui/effects/ripple_animation.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
|
@ -18,6 +20,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "iv/iv_instance.h"
|
||||||
|
|
||||||
namespace HistoryView {
|
namespace HistoryView {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "history/view/media/history_view_web_page.h"
|
#include "history/view/media/history_view_web_page.h"
|
||||||
|
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "base/qt/qt_key_modifiers.h"
|
||||||
|
#include "window/window_session_controller.h"
|
||||||
|
#include "iv/iv_instance.h"
|
||||||
#include "core/click_handler_types.h"
|
#include "core/click_handler_types.h"
|
||||||
#include "core/ui_integration.h"
|
#include "core/ui_integration.h"
|
||||||
#include "data/data_file_click_handler.h"
|
#include "data/data_file_click_handler.h"
|
||||||
|
@ -82,7 +86,29 @@ constexpr auto kMaxOriginalEntryLines = 8192;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] ClickHandlerPtr IvClickHandler(not_null<WebPageData*> webpage) {
|
||||||
|
return std::make_shared<LambdaClickHandler>([=](ClickContext context) {
|
||||||
|
const auto my = context.other.value<ClickHandlerContext>();
|
||||||
|
if (const auto controller = my.sessionWindow.get()) {
|
||||||
|
if (const auto iv = webpage->iv.get()) {
|
||||||
|
#ifdef _DEBUG
|
||||||
|
const auto local = base::IsCtrlPressed();
|
||||||
|
#else // _DEBUG
|
||||||
|
const auto local = false;
|
||||||
|
#endif // _DEBUG
|
||||||
|
Core::App().iv().show(controller->uiShow(), iv, local);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
HiddenUrlClickHandler::Open(webpage->url, context.other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] QString PageToPhrase(not_null<WebPageData*> webpage) {
|
[[nodiscard]] QString PageToPhrase(not_null<WebPageData*> webpage) {
|
||||||
|
if (webpage->iv) {
|
||||||
|
return u"Instant View"_q;
|
||||||
|
}
|
||||||
const auto type = webpage->type;
|
const auto type = webpage->type;
|
||||||
return Ui::Text::Upper((type == WebPageType::Theme)
|
return Ui::Text::Upper((type == WebPageType::Theme)
|
||||||
? tr::lng_view_button_theme(tr::now)
|
? tr::lng_view_button_theme(tr::now)
|
||||||
|
@ -118,7 +144,8 @@ constexpr auto kMaxOriginalEntryLines = 8192;
|
||||||
|
|
||||||
[[nodiscard]] bool HasButton(not_null<WebPageData*> webpage) {
|
[[nodiscard]] bool HasButton(not_null<WebPageData*> webpage) {
|
||||||
const auto type = webpage->type;
|
const auto type = webpage->type;
|
||||||
return (type == WebPageType::Message)
|
return webpage->iv
|
||||||
|
|| (type == WebPageType::Message)
|
||||||
|| (type == WebPageType::Group)
|
|| (type == WebPageType::Group)
|
||||||
|| (type == WebPageType::Channel)
|
|| (type == WebPageType::Channel)
|
||||||
|| (type == WebPageType::ChannelBoost)
|
|| (type == WebPageType::ChannelBoost)
|
||||||
|
@ -245,7 +272,7 @@ QSize WebPage::countOptimalSize() {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}();
|
}();
|
||||||
_openl = (previewOfHiddenUrl
|
_openl = _data->iv ? IvClickHandler(_data) : (previewOfHiddenUrl
|
||||||
|| UrlClickHandler::IsSuspicious(_data->url))
|
|| UrlClickHandler::IsSuspicious(_data->url))
|
||||||
? std::make_shared<HiddenUrlClickHandler>(_data->url)
|
? std::make_shared<HiddenUrlClickHandler>(_data->url)
|
||||||
: std::make_shared<UrlClickHandler>(_data->url, true);
|
: std::make_shared<UrlClickHandler>(_data->url, true);
|
||||||
|
|
115
Telegram/SourceFiles/iv/iv_controller.cpp
Normal file
115
Telegram/SourceFiles/iv/iv_controller.cpp
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "iv/iv_controller.h"
|
||||||
|
|
||||||
|
#include "iv/iv_data.h"
|
||||||
|
#include "ui/widgets/rp_window.h"
|
||||||
|
#include "webview/webview_data_stream_memory.h"
|
||||||
|
#include "webview/webview_embed.h"
|
||||||
|
#include "webview/webview_interface.h"
|
||||||
|
|
||||||
|
#include <QtCore/QRegularExpression>
|
||||||
|
#include <QtCore/QFile>
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
|
||||||
|
Controller::Controller() = default;
|
||||||
|
|
||||||
|
Controller::~Controller() {
|
||||||
|
_webview = nullptr;
|
||||||
|
_window = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::show(const QString &dataPath, Prepared page) {
|
||||||
|
_window = std::make_unique<Ui::RpWindow>();
|
||||||
|
const auto window = _window.get();
|
||||||
|
|
||||||
|
window->setGeometry({ 200, 200, 800, 600 });
|
||||||
|
|
||||||
|
const auto container = Ui::CreateChild<Ui::RpWidget>(
|
||||||
|
window->body().get());
|
||||||
|
window->sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||||
|
container->setGeometry(QRect(QPoint(), size));
|
||||||
|
}, container->lifetime());
|
||||||
|
container->show();
|
||||||
|
|
||||||
|
_webview = std::make_unique<Webview::Window>(
|
||||||
|
container,
|
||||||
|
Webview::WindowConfig{ .userDataPath = dataPath });
|
||||||
|
const auto raw = _webview.get();
|
||||||
|
|
||||||
|
window->lifetime().add([=] {
|
||||||
|
_webview = nullptr;
|
||||||
|
});
|
||||||
|
if (!raw->widget()) {
|
||||||
|
_webview = nullptr;
|
||||||
|
_window = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
raw->widget()->show();
|
||||||
|
|
||||||
|
container->geometryValue(
|
||||||
|
) | rpl::start_with_next([=](QRect geometry) {
|
||||||
|
raw->widget()->setGeometry(geometry);
|
||||||
|
}, _lifetime);
|
||||||
|
|
||||||
|
raw->setNavigationStartHandler([=](const QString &uri, bool newWindow) {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
raw->setNavigationDoneHandler([=](bool success) {
|
||||||
|
});
|
||||||
|
raw->setDataRequestHandler([=](Webview::DataRequest request) {
|
||||||
|
if (!request.id.starts_with("iv/")) {
|
||||||
|
_dataRequests.fire(std::move(request));
|
||||||
|
return Webview::DataResult::Pending;
|
||||||
|
}
|
||||||
|
const auto finishWith = [&](QByteArray data, std::string mime) {
|
||||||
|
request.done({
|
||||||
|
.stream = std::make_unique<Webview::DataStreamFromMemory>(
|
||||||
|
std::move(data),
|
||||||
|
std::move(mime)),
|
||||||
|
});
|
||||||
|
return Webview::DataResult::Done;
|
||||||
|
};
|
||||||
|
const auto id = std::string_view(request.id).substr(3);
|
||||||
|
if (id == "page.html") {
|
||||||
|
return finishWith(page.html, "text/html");
|
||||||
|
}
|
||||||
|
const auto css = id.ends_with(".css");
|
||||||
|
const auto js = !css && id.ends_with(".js");
|
||||||
|
if (!css && !js) {
|
||||||
|
return Webview::DataResult::Failed;
|
||||||
|
}
|
||||||
|
const auto qstring = QString::fromUtf8(id.data(), id.size());
|
||||||
|
const auto pattern = u"^[a-zA-Z\\.\\-_0-9]+$"_q;
|
||||||
|
if (QRegularExpression(pattern).match(qstring).hasMatch()) {
|
||||||
|
auto file = QFile(u":/iv/"_q + qstring);
|
||||||
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
|
const auto mime = css ? "text/css" : "text/javascript";
|
||||||
|
return finishWith(file.readAll(), mime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Webview::DataResult::Failed;
|
||||||
|
});
|
||||||
|
|
||||||
|
raw->init(R"(
|
||||||
|
)");
|
||||||
|
raw->navigateToData("iv/page.html");
|
||||||
|
|
||||||
|
window->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<Webview::DataRequest> Controller::dataRequests() const {
|
||||||
|
return _dataRequests.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::lifetime &Controller::lifetime() {
|
||||||
|
return _lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Iv
|
42
Telegram/SourceFiles/iv/iv_controller.h
Normal file
42
Telegram/SourceFiles/iv/iv_controller.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Webview {
|
||||||
|
struct DataRequest;
|
||||||
|
class Window;
|
||||||
|
} // namespace Webview
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class RpWindow;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
|
||||||
|
struct Prepared;
|
||||||
|
|
||||||
|
class Controller final {
|
||||||
|
public:
|
||||||
|
Controller();
|
||||||
|
~Controller();
|
||||||
|
|
||||||
|
void show(const QString &dataPath, Prepared page);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<Webview::DataRequest> dataRequests() const;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Ui::RpWindow> _window;
|
||||||
|
std::unique_ptr<Webview::Window> _webview;
|
||||||
|
rpl::event_stream<Webview::DataRequest> _dataRequests;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Iv
|
63
Telegram/SourceFiles/iv/iv_data.cpp
Normal file
63
Telegram/SourceFiles/iv/iv_data.cpp
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "iv/iv_data.h"
|
||||||
|
|
||||||
|
#include "iv/iv_prepare.h"
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
|
||||||
|
QByteArray GeoPointId(Geo point) {
|
||||||
|
const auto lat = int(point.lat * 1000000);
|
||||||
|
const auto lon = int(point.lon * 1000000);
|
||||||
|
const auto combined = (std::uint64_t(std::uint32_t(lat)) << 32)
|
||||||
|
| std::uint64_t(std::uint32_t(lon));
|
||||||
|
return QByteArray::number(combined)
|
||||||
|
+ ','
|
||||||
|
+ QByteArray::number(point.access);
|
||||||
|
}
|
||||||
|
|
||||||
|
Geo GeoPointFromId(QByteArray data) {
|
||||||
|
const auto parts = data.split(',');
|
||||||
|
if (parts.size() != 2) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const auto combined = parts[0].toULongLong();
|
||||||
|
const auto lat = int(std::uint32_t(combined >> 32));
|
||||||
|
const auto lon = int(std::uint32_t(combined & 0xFFFFFFFFULL));
|
||||||
|
return {
|
||||||
|
.lat = lat / 1000000.,
|
||||||
|
.lon = lon / 1000000.,
|
||||||
|
.access = parts[1].toULongLong(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::Data(const MTPDwebPage &webpage, const MTPPage &page)
|
||||||
|
: _source(std::make_unique<Source>(Source{
|
||||||
|
.page = page,
|
||||||
|
.webpagePhoto = (webpage.vphoto()
|
||||||
|
? *webpage.vphoto()
|
||||||
|
: std::optional<MTPPhoto>()),
|
||||||
|
.webpageDocument = (webpage.vdocument()
|
||||||
|
? *webpage.vdocument()
|
||||||
|
: std::optional<MTPDocument>()),
|
||||||
|
})) {
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Data::id() const {
|
||||||
|
return qs(_source->page.data().vurl());
|
||||||
|
}
|
||||||
|
|
||||||
|
Data::~Data() = default;
|
||||||
|
|
||||||
|
void Data::prepare(const Options &options, Fn<void(Prepared)> done) const {
|
||||||
|
crl::async([source = *_source, options, done = std::move(done)] {
|
||||||
|
done(Prepare(source, options));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Iv
|
47
Telegram/SourceFiles/iv/iv_data.h
Normal file
47
Telegram/SourceFiles/iv/iv_data.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
|
||||||
|
struct Source;
|
||||||
|
|
||||||
|
struct Options {
|
||||||
|
QString saveToFolder;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Prepared {
|
||||||
|
QByteArray html;
|
||||||
|
std::vector<QByteArray> resources;
|
||||||
|
base::flat_map<QByteArray, QByteArray> embeds;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Geo {
|
||||||
|
float64 lat = 0.;
|
||||||
|
float64 lon = 0.;
|
||||||
|
uint64 access = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray GeoPointId(Geo point);
|
||||||
|
[[nodiscard]] Geo GeoPointFromId(QByteArray data);
|
||||||
|
|
||||||
|
class Data final {
|
||||||
|
public:
|
||||||
|
Data(const MTPDwebPage &webpage, const MTPPage &page);
|
||||||
|
~Data();
|
||||||
|
|
||||||
|
[[nodiscard]] QString id() const;
|
||||||
|
|
||||||
|
void prepare(const Options &options, Fn<void(Prepared)> done) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::unique_ptr<Source> _source;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Iv
|
663
Telegram/SourceFiles/iv/iv_instance.cpp
Normal file
663
Telegram/SourceFiles/iv/iv_instance.cpp
Normal file
|
@ -0,0 +1,663 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "iv/iv_instance.h"
|
||||||
|
|
||||||
|
#include "core/file_utilities.h"
|
||||||
|
#include "data/data_cloud_file.h"
|
||||||
|
#include "data/data_document.h"
|
||||||
|
#include "data/data_file_origin.h"
|
||||||
|
#include "data/data_photo_media.h"
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "iv/iv_controller.h"
|
||||||
|
#include "iv/iv_data.h"
|
||||||
|
#include "main/main_account.h"
|
||||||
|
#include "main/main_domain.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "main/session/session_show.h"
|
||||||
|
#include "media/streaming/media_streaming_loader.h"
|
||||||
|
#include "storage/file_download.h"
|
||||||
|
#include "storage/storage_domain.h"
|
||||||
|
#include "ui/boxes/confirm_box.h"
|
||||||
|
#include "webview/webview_data_stream_memory.h"
|
||||||
|
#include "webview/webview_interface.h"
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kGeoPointScale = 1;
|
||||||
|
constexpr auto kGeoPointZoomMin = 13;
|
||||||
|
constexpr auto kMaxLoadParts = 3;
|
||||||
|
constexpr auto kKeepLoadingParts = 8;
|
||||||
|
|
||||||
|
[[nodiscard]] QString LookupLocalPath(
|
||||||
|
const std::shared_ptr<Main::SessionShow> show) {
|
||||||
|
const auto &domain = show->session().account().domain();
|
||||||
|
const auto &base = domain.local().webviewDataPath();
|
||||||
|
static auto counter = 0;
|
||||||
|
return base + u"/iv/"_q + QString::number(++counter);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] Storage::Cache::Key IvBaseCacheKey(
|
||||||
|
not_null<DocumentData*> document) {
|
||||||
|
auto big = document->bigFileBaseCacheKey();
|
||||||
|
big.low += 0x7FF;
|
||||||
|
return big;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
class Shown final : public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
Shown(
|
||||||
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
|
not_null<Data*> data,
|
||||||
|
bool local);
|
||||||
|
|
||||||
|
[[nodiscard]] bool showing(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
not_null<Data*> data) const;
|
||||||
|
[[nodiscard]] bool showingFrom(not_null<Main::Session*> session) const;
|
||||||
|
[[nodiscard]] bool activeFor(not_null<Main::Session*> session) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct MapPreview {
|
||||||
|
std::unique_ptr<::Data::CloudFile> file;
|
||||||
|
QByteArray bytes;
|
||||||
|
};
|
||||||
|
struct PartRequest {
|
||||||
|
Webview::DataRequest request;
|
||||||
|
QByteArray data;
|
||||||
|
std::vector<bool> loaded;
|
||||||
|
int64 offset = 0;
|
||||||
|
};
|
||||||
|
struct FileLoad {
|
||||||
|
not_null<DocumentData*> document;
|
||||||
|
std::unique_ptr<Media::Streaming::Loader> loader;
|
||||||
|
std::vector<PartRequest> requests;
|
||||||
|
std::string mime;
|
||||||
|
rpl::lifetime lifetime;
|
||||||
|
};
|
||||||
|
|
||||||
|
void showLocal(Prepared result);
|
||||||
|
void showWindowed(Prepared result);
|
||||||
|
|
||||||
|
// Local.
|
||||||
|
void showProgress(int index);
|
||||||
|
void loadResource(int index);
|
||||||
|
void finishLocal(const QString &path);
|
||||||
|
[[nodiscard]] QString localRoot() const;
|
||||||
|
void writeLocal(const QString &relative, const QByteArray &data);
|
||||||
|
void loadPhoto(QString id, PhotoId photoId);
|
||||||
|
void loadDocument(QString id, DocumentId documentId);
|
||||||
|
void loadPage(QString id, QString tag);
|
||||||
|
void loadMap(QString id, QString params);
|
||||||
|
void writeEmbed(QString id, QString hash);
|
||||||
|
|
||||||
|
// Windowed.
|
||||||
|
void streamPhoto(PhotoId photoId, Webview::DataRequest request);
|
||||||
|
void streamFile(DocumentId documentId, Webview::DataRequest request);
|
||||||
|
void streamFile(FileLoad &file, Webview::DataRequest request);
|
||||||
|
void processPartInFile(
|
||||||
|
FileLoad &file,
|
||||||
|
Media::Streaming::LoadedPart &&part);
|
||||||
|
bool finishRequestWithPart(
|
||||||
|
PartRequest &request,
|
||||||
|
const Media::Streaming::LoadedPart &part);
|
||||||
|
void streamMap(QString params, Webview::DataRequest request);
|
||||||
|
void sendEmbed(QByteArray hash, Webview::DataRequest request);
|
||||||
|
|
||||||
|
void requestDone(
|
||||||
|
Webview::DataRequest request,
|
||||||
|
QByteArray bytes,
|
||||||
|
std::string mime,
|
||||||
|
int64 offset = 0,
|
||||||
|
int64 total = 0);
|
||||||
|
void requestFail(Webview::DataRequest request);
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
std::shared_ptr<Main::SessionShow> _show;
|
||||||
|
QString _id;
|
||||||
|
std::unique_ptr<Controller> _controller;
|
||||||
|
base::flat_map<DocumentId, FileLoad> _files;
|
||||||
|
|
||||||
|
QString _localBase;
|
||||||
|
base::flat_map<QByteArray, QByteArray> _embeds;
|
||||||
|
base::flat_map<QString, MapPreview> _maps;
|
||||||
|
std::vector<QByteArray> _resources;
|
||||||
|
int _resource = -1;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Shown::Shown(
|
||||||
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
|
not_null<Data*> data,
|
||||||
|
bool local)
|
||||||
|
: _session(&show->session())
|
||||||
|
, _show(show)
|
||||||
|
, _id(data->id()) {
|
||||||
|
const auto weak = base::make_weak(this);
|
||||||
|
|
||||||
|
const auto base = local ? LookupLocalPath(show) : QString();
|
||||||
|
data->prepare({ .saveToFolder = base }, [=](Prepared result) {
|
||||||
|
crl::on_main(weak, [=, result = std::move(result)]() mutable {
|
||||||
|
_embeds = std::move(result.embeds);
|
||||||
|
if (!base.isEmpty()) {
|
||||||
|
_localBase = base;
|
||||||
|
showLocal(std::move(result));
|
||||||
|
} else {
|
||||||
|
showWindowed(std::move(result));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::showLocal(Prepared result) {
|
||||||
|
showProgress(0);
|
||||||
|
|
||||||
|
QDir(_localBase).removeRecursively();
|
||||||
|
QDir().mkpath(_localBase);
|
||||||
|
|
||||||
|
_resources = std::move(result.resources);
|
||||||
|
writeLocal(localRoot(), result.html);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::showProgress(int index) {
|
||||||
|
const auto count = int(_resources.size() + 1);
|
||||||
|
_show->showToast(u"Saving %1 / %2..."_q.arg(index + 1).arg(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::finishLocal(const QString &path) {
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
_show->showToast(u"Failed!"_q);
|
||||||
|
} else {
|
||||||
|
_show->showToast(u"Done!"_q);
|
||||||
|
File::Launch(path);
|
||||||
|
}
|
||||||
|
_id = QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Shown::localRoot() const {
|
||||||
|
return u"page.html"_q;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::writeLocal(const QString &relative, const QByteArray &data) {
|
||||||
|
const auto path = _localBase + '/' + relative;
|
||||||
|
QFileInfo(path).absoluteDir().mkpath(".");
|
||||||
|
|
||||||
|
auto f = QFile(path);
|
||||||
|
if (!f.open(QIODevice::WriteOnly) || f.write(data) != data.size()) {
|
||||||
|
finishLocal({});
|
||||||
|
} else {
|
||||||
|
crl::on_main(this, [=] {
|
||||||
|
loadResource(_resource + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::loadResource(int index) {
|
||||||
|
_resource = index;
|
||||||
|
if (_resource == _resources.size()) {
|
||||||
|
finishLocal(_localBase + '/' + localRoot());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showProgress(_resource + 1);
|
||||||
|
const auto id = QString::fromUtf8(_resources[_resource]);
|
||||||
|
if (id.startsWith(u"photo/"_q)) {
|
||||||
|
loadPhoto(id, id.mid(6).toULongLong());
|
||||||
|
} else if (id.startsWith(u"document/"_q)) {
|
||||||
|
loadDocument(id, id.mid(9).toULongLong());
|
||||||
|
} else if (id.startsWith(u"iv/"_q)) {
|
||||||
|
loadPage(id, id.mid(3));
|
||||||
|
} else if (id.startsWith(u"map/"_q)) {
|
||||||
|
loadMap(id, id.mid(4));
|
||||||
|
} else if (id.startsWith(u"html/"_q)) {
|
||||||
|
writeEmbed(id, id.mid(5));
|
||||||
|
} else {
|
||||||
|
_show->show(
|
||||||
|
Ui::MakeInformBox(u"Skipping resource %1..."_q.arg(id)));
|
||||||
|
crl::on_main(this, [=] {
|
||||||
|
loadResource(index + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::loadPhoto(QString id, PhotoId photoId) {
|
||||||
|
const auto photo = _session->data().photo(photoId);
|
||||||
|
const auto media = photo->createMediaView();
|
||||||
|
media->wanted(::Data::PhotoSize::Large, ::Data::FileOrigin());
|
||||||
|
const auto finish = [=](QByteArray bytes) {
|
||||||
|
writeLocal(id, bytes);
|
||||||
|
};
|
||||||
|
if (media->loaded()) {
|
||||||
|
finish(media->imageBytes(::Data::PhotoSize::Large));
|
||||||
|
} else {
|
||||||
|
photo->session().downloaderTaskFinished(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
return media->loaded();
|
||||||
|
}) | rpl::take(1) | rpl::start_with_next([=] {
|
||||||
|
finish(media->imageBytes(::Data::PhotoSize::Large));
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::loadDocument(QString id, DocumentId documentId) {
|
||||||
|
const auto path = _localBase + '/' + id;
|
||||||
|
QFileInfo(path).absoluteDir().mkpath(".");
|
||||||
|
|
||||||
|
const auto document = _session->data().document(documentId);
|
||||||
|
document->save(::Data::FileOrigin(), path);
|
||||||
|
if (!document->loading()) {
|
||||||
|
crl::on_main(this, [=] {
|
||||||
|
loadResource(_resource + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document->session().downloaderTaskFinished(
|
||||||
|
) | rpl::filter([=] {
|
||||||
|
return !document->loading();
|
||||||
|
}) | rpl::take(1) | rpl::start_with_next([=] {
|
||||||
|
crl::on_main(this, [=] {
|
||||||
|
loadResource(_resource + 1);
|
||||||
|
});
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::loadPage(QString id, QString tag) {
|
||||||
|
if (!id.endsWith(u".css"_q) && !id.endsWith(u".js"_q)) {
|
||||||
|
finishLocal({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto pattern = u"^[a-zA-Z\\.\\-_0-9]+$"_q;
|
||||||
|
if (QRegularExpression(pattern).match(tag).hasMatch()) {
|
||||||
|
auto file = QFile(u":/iv/"_q + tag);
|
||||||
|
if (file.open(QIODevice::ReadOnly)) {
|
||||||
|
writeLocal(id, file.readAll());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finishLocal({});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::loadMap(QString id, QString params) {
|
||||||
|
using namespace ::Data;
|
||||||
|
const auto i = _maps.find(params);
|
||||||
|
if (i != end(_maps)) {
|
||||||
|
writeLocal(id, i->second.bytes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto parts = params.split(u'&');
|
||||||
|
if (parts.size() != 3) {
|
||||||
|
finishLocal({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto point = GeoPointFromId(parts[0].toUtf8());
|
||||||
|
const auto size = parts[1].split(',');
|
||||||
|
const auto zoom = parts[2].toInt();
|
||||||
|
if (size.size() != 2) {
|
||||||
|
finishLocal({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto location = GeoPointLocation{
|
||||||
|
.lat = point.lat,
|
||||||
|
.lon = point.lon,
|
||||||
|
.access = point.access,
|
||||||
|
.width = size[0].toInt(),
|
||||||
|
.height = size[1].toInt(),
|
||||||
|
.zoom = std::max(zoom, kGeoPointZoomMin),
|
||||||
|
.scale = kGeoPointScale,
|
||||||
|
};
|
||||||
|
const auto prepared = ImageWithLocation{
|
||||||
|
.location = ImageLocation(
|
||||||
|
{ location },
|
||||||
|
location.width,
|
||||||
|
location.height)
|
||||||
|
};
|
||||||
|
auto &preview = _maps.emplace(params, MapPreview()).first->second;
|
||||||
|
preview.file = std::make_unique<CloudFile>();
|
||||||
|
|
||||||
|
UpdateCloudFile(
|
||||||
|
*preview.file,
|
||||||
|
prepared,
|
||||||
|
_session->data().cache(),
|
||||||
|
kImageCacheTag,
|
||||||
|
[=](FileOrigin origin) { /* restartLoader not used here */ });
|
||||||
|
const auto autoLoading = false;
|
||||||
|
const auto finalCheck = [=] { return true; };
|
||||||
|
const auto done = [=](QByteArray bytes) {
|
||||||
|
const auto i = _maps.find(params);
|
||||||
|
Assert(i != end(_maps));
|
||||||
|
i->second.bytes = std::move(bytes);
|
||||||
|
writeLocal(id, i->second.bytes);
|
||||||
|
};
|
||||||
|
LoadCloudFile(
|
||||||
|
_session,
|
||||||
|
*preview.file,
|
||||||
|
FileOrigin(),
|
||||||
|
LoadFromCloudOrLocal,
|
||||||
|
autoLoading,
|
||||||
|
kImageCacheTag,
|
||||||
|
finalCheck,
|
||||||
|
done,
|
||||||
|
[=](bool) { done("failed..."); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::writeEmbed(QString id, QString hash) {
|
||||||
|
const auto i = _embeds.find(hash.toUtf8());
|
||||||
|
if (i != end(_embeds)) {
|
||||||
|
writeLocal(id, i->second);
|
||||||
|
} else {
|
||||||
|
finishLocal({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::showWindowed(Prepared result) {
|
||||||
|
_controller = std::make_unique<Controller>();
|
||||||
|
_controller->dataRequests(
|
||||||
|
) | rpl::start_with_next([=](Webview::DataRequest request) {
|
||||||
|
const auto requested = QString::fromStdString(request.id);
|
||||||
|
const auto id = QStringView(requested);
|
||||||
|
if (id.startsWith(u"photo/")) {
|
||||||
|
streamPhoto(id.mid(6).toULongLong(), std::move(request));
|
||||||
|
} else if (id.startsWith(u"document/"_q)) {
|
||||||
|
streamFile(id.mid(9).toULongLong(), std::move(request));
|
||||||
|
} else if (id.startsWith(u"map/"_q)) {
|
||||||
|
streamMap(id.mid(4).toUtf8(), std::move(request));
|
||||||
|
} else if (id.startsWith(u"html/"_q)) {
|
||||||
|
sendEmbed(id.mid(5).toUtf8(), std::move(request));
|
||||||
|
}
|
||||||
|
}, _controller->lifetime());
|
||||||
|
const auto domain = &_session->domain();
|
||||||
|
_controller->show(domain->local().webviewDataPath(), std::move(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::streamPhoto(PhotoId photoId, Webview::DataRequest request) {
|
||||||
|
using namespace Data;
|
||||||
|
|
||||||
|
const auto photo = _session->data().photo(photoId);
|
||||||
|
if (photo->isNull()) {
|
||||||
|
requestFail(std::move(request));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto media = photo->createMediaView();
|
||||||
|
media->wanted(PhotoSize::Large, FileOrigin());
|
||||||
|
const auto check = [=] {
|
||||||
|
if (!media->loaded() && !media->owner()->failed(PhotoSize::Large)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
requestDone(
|
||||||
|
request,
|
||||||
|
media->imageBytes(PhotoSize::Large),
|
||||||
|
"image/jpeg");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (!check()) {
|
||||||
|
photo->session().downloaderTaskFinished(
|
||||||
|
) | rpl::filter(
|
||||||
|
check
|
||||||
|
) | rpl::take(1) | rpl::start(_controller->lifetime());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::streamFile(
|
||||||
|
DocumentId documentId,
|
||||||
|
Webview::DataRequest request) {
|
||||||
|
using namespace Data;
|
||||||
|
|
||||||
|
const auto i = _files.find(documentId);
|
||||||
|
if (i != end(_files)) {
|
||||||
|
streamFile(i->second, std::move(request));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto document = _session->data().document(documentId);
|
||||||
|
auto loader = document->createStreamingLoader(FileOrigin(), false);
|
||||||
|
if (!loader) {
|
||||||
|
requestFail(std::move(request));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &file = _files.emplace(
|
||||||
|
documentId,
|
||||||
|
FileLoad{
|
||||||
|
.document = document,
|
||||||
|
.loader = std::move(loader),
|
||||||
|
.mime = document->mimeString().toStdString(),
|
||||||
|
}).first->second;
|
||||||
|
|
||||||
|
file.loader->parts(
|
||||||
|
) | rpl::start_with_next([=](Media::Streaming::LoadedPart &&part) {
|
||||||
|
const auto i = _files.find(documentId);
|
||||||
|
Assert(i != end(_files));
|
||||||
|
processPartInFile(i->second, std::move(part));
|
||||||
|
}, file.lifetime);
|
||||||
|
|
||||||
|
streamFile(file, std::move(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::streamFile(FileLoad &file, Webview::DataRequest request) {
|
||||||
|
constexpr auto kPart = Media::Streaming::Loader::kPartSize;
|
||||||
|
const auto size = file.document->size;
|
||||||
|
const auto last = int((size + kPart - 1) / kPart);
|
||||||
|
const auto from = int(std::min(request.offset, size) / kPart);
|
||||||
|
const auto till = (request.limit > 0)
|
||||||
|
? std::min(request.offset + request.limit, size)
|
||||||
|
: size;
|
||||||
|
const auto parts = std::min(
|
||||||
|
int((till + kPart - 1) / kPart) - from,
|
||||||
|
kMaxLoadParts);
|
||||||
|
//auto base = IvBaseCacheKey(document);
|
||||||
|
|
||||||
|
const auto length = std::min((from + parts) * kPart, size)
|
||||||
|
- from * kPart;
|
||||||
|
file.requests.push_back(PartRequest{
|
||||||
|
.request = std::move(request),
|
||||||
|
.data = QByteArray(length, 0),
|
||||||
|
.loaded = std::vector<bool>(parts, false),
|
||||||
|
.offset = from * kPart,
|
||||||
|
});
|
||||||
|
|
||||||
|
file.loader->resetPriorities();
|
||||||
|
const auto load = std::min(from + kKeepLoadingParts, last) - from;
|
||||||
|
for (auto i = 0; i != load; ++i) {
|
||||||
|
file.loader->load((from + i) * kPart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::processPartInFile(
|
||||||
|
FileLoad &file,
|
||||||
|
Media::Streaming::LoadedPart &&part) {
|
||||||
|
for (auto i = begin(file.requests); i != end(file.requests);) {
|
||||||
|
if (finishRequestWithPart(*i, part)) {
|
||||||
|
auto done = base::take(*i);
|
||||||
|
i = file.requests.erase(i);
|
||||||
|
requestDone(
|
||||||
|
std::move(done.request),
|
||||||
|
done.data,
|
||||||
|
file.mime,
|
||||||
|
done.offset,
|
||||||
|
file.document->size);
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Shown::finishRequestWithPart(
|
||||||
|
PartRequest &request,
|
||||||
|
const Media::Streaming::LoadedPart &part) {
|
||||||
|
const auto offset = part.offset;
|
||||||
|
if (offset == Media::Streaming::LoadedPart::kFailedOffset) {
|
||||||
|
request.data = QByteArray();
|
||||||
|
return true;
|
||||||
|
} else if (offset < request.offset
|
||||||
|
|| offset >= request.offset + request.data.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
constexpr auto kPart = Media::Streaming::Loader::kPartSize;
|
||||||
|
const auto copy = std::min(
|
||||||
|
int(part.bytes.size()),
|
||||||
|
int(request.data.size() - (offset - request.offset)));
|
||||||
|
const auto index = (offset - request.offset) / kPart;
|
||||||
|
Assert(index < request.loaded.size());
|
||||||
|
if (request.loaded[index]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
request.loaded[index] = true;
|
||||||
|
memcpy(
|
||||||
|
request.data.data() + index * kPart,
|
||||||
|
part.bytes.constData(),
|
||||||
|
copy);
|
||||||
|
return !ranges::contains(request.loaded, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::streamMap(QString params, Webview::DataRequest request) {
|
||||||
|
using namespace ::Data;
|
||||||
|
|
||||||
|
const auto parts = params.split(u'&');
|
||||||
|
if (parts.size() != 3) {
|
||||||
|
finishLocal({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto point = GeoPointFromId(parts[0].toUtf8());
|
||||||
|
const auto size = parts[1].split(',');
|
||||||
|
const auto zoom = parts[2].toInt();
|
||||||
|
if (size.size() != 2) {
|
||||||
|
finishLocal({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto location = GeoPointLocation{
|
||||||
|
.lat = point.lat,
|
||||||
|
.lon = point.lon,
|
||||||
|
.access = point.access,
|
||||||
|
.width = size[0].toInt(),
|
||||||
|
.height = size[1].toInt(),
|
||||||
|
.zoom = std::max(zoom, kGeoPointZoomMin),
|
||||||
|
.scale = kGeoPointScale,
|
||||||
|
};
|
||||||
|
const auto prepared = ImageWithLocation{
|
||||||
|
.location = ImageLocation(
|
||||||
|
{ location },
|
||||||
|
location.width,
|
||||||
|
location.height)
|
||||||
|
};
|
||||||
|
auto &preview = _maps.emplace(params, MapPreview()).first->second;
|
||||||
|
preview.file = std::make_unique<CloudFile>();
|
||||||
|
|
||||||
|
UpdateCloudFile(
|
||||||
|
*preview.file,
|
||||||
|
prepared,
|
||||||
|
_session->data().cache(),
|
||||||
|
kImageCacheTag,
|
||||||
|
[=](FileOrigin origin) { /* restartLoader not used here */ });
|
||||||
|
const auto autoLoading = false;
|
||||||
|
const auto finalCheck = [=] { return true; };
|
||||||
|
const auto done = [=](QByteArray bytes) {
|
||||||
|
const auto i = _maps.find(params);
|
||||||
|
Assert(i != end(_maps));
|
||||||
|
i->second.bytes = std::move(bytes);
|
||||||
|
requestDone(request, i->second.bytes, "image/png");
|
||||||
|
};
|
||||||
|
LoadCloudFile(
|
||||||
|
_session,
|
||||||
|
*preview.file,
|
||||||
|
FileOrigin(),
|
||||||
|
LoadFromCloudOrLocal,
|
||||||
|
autoLoading,
|
||||||
|
kImageCacheTag,
|
||||||
|
finalCheck,
|
||||||
|
done,
|
||||||
|
[=](bool) { done("failed..."); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::sendEmbed(QByteArray hash, Webview::DataRequest request) {
|
||||||
|
const auto i = _embeds.find(hash);
|
||||||
|
if (i != end(_embeds)) {
|
||||||
|
requestDone(std::move(request), i->second, "text/html");
|
||||||
|
} else {
|
||||||
|
requestFail(std::move(request));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::requestDone(
|
||||||
|
Webview::DataRequest request,
|
||||||
|
QByteArray bytes,
|
||||||
|
std::string mime,
|
||||||
|
int64 offset,
|
||||||
|
int64 total) {
|
||||||
|
if (bytes.isEmpty() && mime.empty()) {
|
||||||
|
requestFail(std::move(request));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
crl::on_main([
|
||||||
|
done = std::move(request.done),
|
||||||
|
data = std::move(bytes),
|
||||||
|
mime = std::move(mime),
|
||||||
|
offset,
|
||||||
|
total
|
||||||
|
] {
|
||||||
|
using namespace Webview;
|
||||||
|
done({
|
||||||
|
.stream = std::make_unique<DataStreamFromMemory>(data, mime),
|
||||||
|
.streamOffset = offset,
|
||||||
|
.totalSize = total,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shown::requestFail(Webview::DataRequest request) {
|
||||||
|
crl::on_main([done = std::move(request.done)] {
|
||||||
|
done({});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Shown::showing(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
not_null<Data*> data) const {
|
||||||
|
return showingFrom(session) && (_id == data->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Shown::showingFrom(not_null<Main::Session*> session) const {
|
||||||
|
return (_session == session);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Shown::activeFor(not_null<Main::Session*> session) const {
|
||||||
|
return showingFrom(session) && _controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance::Instance() = default;
|
||||||
|
|
||||||
|
Instance::~Instance() = default;
|
||||||
|
|
||||||
|
void Instance::show(
|
||||||
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
|
not_null<Data*> data,
|
||||||
|
bool local) {
|
||||||
|
const auto session = &show->session();
|
||||||
|
if (_shown && _shown->showing(session, data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_shown = std::make_unique<Shown>(show, data, local);
|
||||||
|
if (!_tracking.contains(session)) {
|
||||||
|
_tracking.emplace(session);
|
||||||
|
session->lifetime().add([=] {
|
||||||
|
_tracking.remove(session);
|
||||||
|
if (_shown && _shown->showingFrom(session)) {
|
||||||
|
_shown = nullptr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Instance::hasActiveWindow(not_null<Main::Session*> session) const {
|
||||||
|
return _shown && _shown->activeFor(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::closeAll() {
|
||||||
|
_shown = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Iv
|
45
Telegram/SourceFiles/iv/iv_instance.h
Normal file
45
Telegram/SourceFiles/iv/iv_instance.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Main {
|
||||||
|
class Session;
|
||||||
|
class SessionShow;
|
||||||
|
} // namespace Main
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
|
||||||
|
class Data;
|
||||||
|
class Shown;
|
||||||
|
|
||||||
|
class Instance final {
|
||||||
|
public:
|
||||||
|
Instance();
|
||||||
|
~Instance();
|
||||||
|
|
||||||
|
void show(
|
||||||
|
std::shared_ptr<Main::SessionShow> show,
|
||||||
|
not_null<Data*> data,
|
||||||
|
bool local);
|
||||||
|
|
||||||
|
[[nodiscard]] bool hasActiveWindow(
|
||||||
|
not_null<Main::Session*> session) const;
|
||||||
|
|
||||||
|
void closeAll();
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::lifetime &lifetime();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Shown> _shown;
|
||||||
|
base::flat_set<not_null<Main::Session*>> _tracking;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Iv
|
27
Telegram/SourceFiles/iv/iv_pch.h
Normal file
27
Telegram/SourceFiles/iv/iv_pch.h
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
|
||||||
|
#include <crl/crl.h>
|
||||||
|
#include <rpl/rpl.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
#include <set>
|
||||||
|
#include <deque>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
|
||||||
|
#include "base/flat_map.h"
|
||||||
|
#include "base/flat_set.h"
|
||||||
|
|
||||||
|
#include "scheme.h"
|
||||||
|
#include "logs.h"
|
1038
Telegram/SourceFiles/iv/iv_prepare.cpp
Normal file
1038
Telegram/SourceFiles/iv/iv_prepare.cpp
Normal file
File diff suppressed because it is too large
Load diff
25
Telegram/SourceFiles/iv/iv_prepare.h
Normal file
25
Telegram/SourceFiles/iv/iv_prepare.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace Iv {
|
||||||
|
|
||||||
|
struct Options;
|
||||||
|
struct Prepared;
|
||||||
|
|
||||||
|
struct Source {
|
||||||
|
MTPPage page;
|
||||||
|
std::optional<MTPPhoto> webpagePhoto;
|
||||||
|
std::optional<MTPDocument> webpageDocument;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] Prepared Prepare(
|
||||||
|
const Source &source,
|
||||||
|
const Options &options);
|
||||||
|
|
||||||
|
} // namespace Iv
|
35
Telegram/cmake/td_iv.cmake
Normal file
35
Telegram/cmake/td_iv.cmake
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# This file is part of Telegram Desktop,
|
||||||
|
# the official desktop application for the Telegram messaging service.
|
||||||
|
#
|
||||||
|
# For license and copyright information please follow this link:
|
||||||
|
# https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
|
add_library(td_iv OBJECT)
|
||||||
|
init_non_host_target(td_iv)
|
||||||
|
add_library(tdesktop::td_iv ALIAS td_iv)
|
||||||
|
|
||||||
|
target_precompile_headers(td_iv PRIVATE ${src_loc}/iv/iv_pch.h)
|
||||||
|
nice_target_sources(td_iv ${src_loc}
|
||||||
|
PRIVATE
|
||||||
|
iv/iv_controller.cpp
|
||||||
|
iv/iv_controller.h
|
||||||
|
iv/iv_data.cpp
|
||||||
|
iv/iv_data.h
|
||||||
|
iv/iv_pch.h
|
||||||
|
iv/iv_prepare.cpp
|
||||||
|
iv/iv_prepare.h
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(td_iv
|
||||||
|
PUBLIC
|
||||||
|
${src_loc}
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(td_iv
|
||||||
|
PUBLIC
|
||||||
|
desktop-app::lib_ui
|
||||||
|
tdesktop::td_scheme
|
||||||
|
PRIVATE
|
||||||
|
desktop-app::lib_webview
|
||||||
|
tdesktop::td_lang
|
||||||
|
)
|
Loading…
Add table
Reference in a new issue