mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +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_prisma.cmake)
|
||||
include(cmake/td_export.cmake)
|
||||
include(cmake/td_mtproto.cmake)
|
||||
include(cmake/td_iv.cmake)
|
||||
include(cmake/td_lang.cmake)
|
||||
include(cmake/td_mtproto.cmake)
|
||||
include(cmake/td_scheme.cmake)
|
||||
include(cmake/td_ui.cmake)
|
||||
include(cmake/generate_appdata_changelog.cmake)
|
||||
|
@ -62,8 +63,9 @@ PRIVATE
|
|||
desktop-app::external_minizip
|
||||
|
||||
tdesktop::td_export
|
||||
tdesktop::td_mtproto
|
||||
tdesktop::td_iv
|
||||
tdesktop::td_lang
|
||||
tdesktop::td_mtproto
|
||||
tdesktop::td_scheme
|
||||
tdesktop::td_ui
|
||||
desktop-app::lib_webrtc
|
||||
|
@ -995,6 +997,8 @@ PRIVATE
|
|||
intro/intro_step.h
|
||||
intro/intro_widget.cpp
|
||||
intro/intro_widget.h
|
||||
iv/iv_instance.cpp
|
||||
iv/iv_instance.h
|
||||
lang/lang_cloud_manager.cpp
|
||||
lang/lang_cloud_manager.h
|
||||
lang/lang_instance.cpp
|
||||
|
@ -1567,6 +1571,7 @@ PRIVATE
|
|||
qrc/emoji_preview.qrc
|
||||
qrc/telegram/animations.qrc
|
||||
qrc/telegram/export.qrc
|
||||
qrc/telegram/iv.qrc
|
||||
qrc/telegram/telegram.qrc
|
||||
qrc/telegram/sounds.qrc
|
||||
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/profile/info_profile_emoji_status_panel.h"
|
||||
#include "info/info_memento.h"
|
||||
#include "iv/iv_data.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "lottie/lottie_icon.h"
|
||||
#include "lottie/lottie_single_player.h"
|
||||
|
@ -310,6 +311,7 @@ PreviewWrap::PreviewWrap(
|
|||
nullptr, // photo
|
||||
nullptr, // document
|
||||
WebPageCollage(),
|
||||
nullptr, // iv
|
||||
0, // duration
|
||||
QString(), // author
|
||||
false, // hasLargeMedia
|
||||
|
|
|
@ -44,6 +44,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "api/api_updates.h"
|
||||
#include "calls/calls_instance.h"
|
||||
#include "countries/countries_manager.h"
|
||||
#include "iv/iv_instance.h"
|
||||
#include "lang/lang_file_parser.h"
|
||||
#include "lang/lang_translator.h"
|
||||
#include "lang/lang_cloud_manager.h"
|
||||
|
@ -162,6 +163,7 @@ Application::Application()
|
|||
, _domain(std::make_unique<Main::Domain>(cDataFile()))
|
||||
, _exportManager(std::make_unique<Export::Manager>())
|
||||
, _calls(std::make_unique<Calls::Instance>())
|
||||
, _iv(std::make_unique<Iv::Instance>())
|
||||
, _langpack(std::make_unique<Lang::Instance>())
|
||||
, _langCloudManager(std::make_unique<Lang::CloudManager>(langpack()))
|
||||
, _emojiKeywords(std::make_unique<ChatHelpers::EmojiKeywords>())
|
||||
|
@ -218,6 +220,7 @@ Application::~Application() {
|
|||
// Domain::finish() and there is a violation on Ensures(started()).
|
||||
Payments::CheckoutProcess::ClearAll();
|
||||
InlineBots::AttachWebView::ClearAll();
|
||||
_iv->closeAll();
|
||||
|
||||
_domain->finish();
|
||||
|
||||
|
@ -1272,6 +1275,8 @@ bool Application::hasActiveWindow(not_null<Main::Session*> session) const {
|
|||
return false;
|
||||
} else if (_calls->hasActivePanel(session)) {
|
||||
return true;
|
||||
} else if (_iv->hasActiveWindow(session)) {
|
||||
return true;
|
||||
} else if (const auto window = _lastActiveWindow) {
|
||||
return (window->account().maybeSession() == session)
|
||||
&& window->widget()->isActive();
|
||||
|
|
|
@ -45,6 +45,10 @@ class Account;
|
|||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace Iv {
|
||||
class Instance;
|
||||
} // namespace Iv
|
||||
|
||||
namespace Ui {
|
||||
namespace Animations {
|
||||
class Manager;
|
||||
|
@ -280,6 +284,11 @@ public:
|
|||
return *_calls;
|
||||
}
|
||||
|
||||
// Iv.
|
||||
Iv::Instance &iv() const {
|
||||
return *_iv;
|
||||
}
|
||||
|
||||
void logout(Main::Account *account = nullptr);
|
||||
void logoutWithChecks(Main::Account *account);
|
||||
void forceLogOut(
|
||||
|
@ -409,6 +418,7 @@ private:
|
|||
const std::unique_ptr<Main::Domain> _domain;
|
||||
const std::unique_ptr<Export::Manager> _exportManager;
|
||||
const std::unique_ptr<Calls::Instance> _calls;
|
||||
const std::unique_ptr<Iv::Instance> _iv;
|
||||
base::flat_map<
|
||||
Main::Account*,
|
||||
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 "boxes/abstract_box.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 "data/business/data_business_chatbots.h"
|
||||
#include "data/business/data_business_info.h"
|
||||
|
@ -3365,6 +3366,7 @@ not_null<WebPageData*> Session::processWebpage(
|
|||
nullptr,
|
||||
nullptr,
|
||||
WebPageCollage(),
|
||||
nullptr,
|
||||
0,
|
||||
QString(),
|
||||
false,
|
||||
|
@ -3389,6 +3391,7 @@ not_null<WebPageData*> Session::webpage(
|
|||
nullptr,
|
||||
nullptr,
|
||||
WebPageCollage(),
|
||||
nullptr,
|
||||
0,
|
||||
QString(),
|
||||
false,
|
||||
|
@ -3406,6 +3409,7 @@ not_null<WebPageData*> Session::webpage(
|
|||
PhotoData *photo,
|
||||
DocumentData *document,
|
||||
WebPageCollage &&collage,
|
||||
std::unique_ptr<Iv::Data> iv,
|
||||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
|
@ -3423,6 +3427,7 @@ not_null<WebPageData*> Session::webpage(
|
|||
photo,
|
||||
document,
|
||||
std::move(collage),
|
||||
std::move(iv),
|
||||
duration,
|
||||
author,
|
||||
hasLargeMedia,
|
||||
|
@ -3503,6 +3508,14 @@ void Session::webpageApplyFields(
|
|||
}, [](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(
|
||||
page,
|
||||
(story ? WebPageType::Story : ParseWebPageType(data)),
|
||||
|
@ -3523,6 +3536,9 @@ void Session::webpageApplyFields(
|
|||
? processDocument(*document).get()
|
||||
: lookupThemeDocument()),
|
||||
WebPageCollage(this, data),
|
||||
(data.vcached_page()
|
||||
? std::make_unique<Iv::Data>(data, *data.vcached_page())
|
||||
: nullptr),
|
||||
data.vduration().value_or_empty(),
|
||||
qs(data.vauthor().value_or_empty()),
|
||||
data.is_has_large_media(),
|
||||
|
@ -3541,6 +3557,7 @@ void Session::webpageApplyFields(
|
|||
PhotoData *photo,
|
||||
DocumentData *document,
|
||||
WebPageCollage &&collage,
|
||||
std::unique_ptr<Iv::Data> iv,
|
||||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
|
@ -3557,6 +3574,7 @@ void Session::webpageApplyFields(
|
|||
photo,
|
||||
document,
|
||||
std::move(collage),
|
||||
std::move(iv),
|
||||
duration,
|
||||
author,
|
||||
hasLargeMedia,
|
||||
|
|
|
@ -38,6 +38,10 @@ namespace Passport {
|
|||
struct SavedCredentials;
|
||||
} // namespace Passport
|
||||
|
||||
namespace Iv {
|
||||
class Data;
|
||||
} // namespace Iv
|
||||
|
||||
namespace Data {
|
||||
|
||||
class Folder;
|
||||
|
@ -581,6 +585,7 @@ public:
|
|||
PhotoData *photo,
|
||||
DocumentData *document,
|
||||
WebPageCollage &&collage,
|
||||
std::unique_ptr<Iv::Data> iv,
|
||||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
|
@ -858,6 +863,7 @@ private:
|
|||
PhotoData *photo,
|
||||
DocumentData *document,
|
||||
WebPageCollage &&collage,
|
||||
std::unique_ptr<Iv::Data> iv,
|
||||
int duration,
|
||||
const QString &author,
|
||||
bool hasLargeMedia,
|
||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "data/data_channel.h"
|
||||
#include "data/data_document.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "iv/iv_data.h"
|
||||
#include "ui/image/image.h"
|
||||
#include "ui/text/text_entity.h"
|
||||
|
||||
|
@ -206,6 +207,8 @@ WebPageData::WebPageData(not_null<Data::Session*> owner, const WebPageId &id)
|
|||
, _owner(owner) {
|
||||
}
|
||||
|
||||
WebPageData::~WebPageData() = default;
|
||||
|
||||
Data::Session &WebPageData::owner() const {
|
||||
return *_owner;
|
||||
}
|
||||
|
@ -225,6 +228,7 @@ bool WebPageData::applyChanges(
|
|||
PhotoData *newPhoto,
|
||||
DocumentData *newDocument,
|
||||
WebPageCollage &&newCollage,
|
||||
std::unique_ptr<Iv::Data> newIv,
|
||||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
bool newHasLargeMedia,
|
||||
|
@ -276,6 +280,7 @@ bool WebPageData::applyChanges(
|
|||
&& photo == newPhoto
|
||||
&& document == newDocument
|
||||
&& collage.items == newCollage.items
|
||||
&& (!iv == !newIv)
|
||||
&& duration == newDuration
|
||||
&& author == resultAuthor
|
||||
&& hasLargeMedia == (newHasLargeMedia ? 1 : 0)
|
||||
|
@ -296,6 +301,7 @@ bool WebPageData::applyChanges(
|
|||
photo = newPhoto;
|
||||
document = newDocument;
|
||||
collage = std::move(newCollage);
|
||||
iv = std::move(newIv);
|
||||
duration = newDuration;
|
||||
author = resultAuthor;
|
||||
pendingTill = newPendingTill;
|
||||
|
|
|
@ -17,6 +17,10 @@ namespace Data {
|
|||
class Session;
|
||||
} // namespace Data
|
||||
|
||||
namespace Iv {
|
||||
class Data;
|
||||
} // namespace Iv
|
||||
|
||||
enum class WebPageType : uint8 {
|
||||
None,
|
||||
|
||||
|
@ -64,6 +68,7 @@ struct WebPageCollage {
|
|||
|
||||
struct WebPageData {
|
||||
WebPageData(not_null<Data::Session*> owner, const WebPageId &id);
|
||||
~WebPageData();
|
||||
|
||||
[[nodiscard]] Data::Session &owner() const;
|
||||
[[nodiscard]] Main::Session &session() const;
|
||||
|
@ -79,6 +84,7 @@ struct WebPageData {
|
|||
PhotoData *newPhoto,
|
||||
DocumentData *newDocument,
|
||||
WebPageCollage &&newCollage,
|
||||
std::unique_ptr<Iv::Data> newIv,
|
||||
int newDuration,
|
||||
const QString &newAuthor,
|
||||
bool newHasLargeMedia,
|
||||
|
@ -105,6 +111,7 @@ struct WebPageData {
|
|||
PhotoData *photo = nullptr;
|
||||
DocumentData *document = nullptr;
|
||||
WebPageCollage collage;
|
||||
std::unique_ptr<Iv::Data> iv;
|
||||
int duration = 0;
|
||||
TimeId pendingTill = 0;
|
||||
uint32 version : 30 = 0;
|
||||
|
|
|
@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "history/history.h"
|
||||
#include "history/history_item_components.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 "ui/effects/ripple_animation.h"
|
||||
#include "ui/painter.h"
|
||||
|
@ -18,6 +20,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/window_session_controller.h"
|
||||
#include "styles/style_chat.h"
|
||||
|
||||
#include "core/application.h"
|
||||
#include "iv/iv_instance.h"
|
||||
|
||||
namespace HistoryView {
|
||||
namespace {
|
||||
|
||||
|
|
|
@ -7,6 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
*/
|
||||
#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/ui_integration.h"
|
||||
#include "data/data_file_click_handler.h"
|
||||
|
@ -82,7 +86,29 @@ constexpr auto kMaxOriginalEntryLines = 8192;
|
|||
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) {
|
||||
if (webpage->iv) {
|
||||
return u"Instant View"_q;
|
||||
}
|
||||
const auto type = webpage->type;
|
||||
return Ui::Text::Upper((type == WebPageType::Theme)
|
||||
? tr::lng_view_button_theme(tr::now)
|
||||
|
@ -118,7 +144,8 @@ constexpr auto kMaxOriginalEntryLines = 8192;
|
|||
|
||||
[[nodiscard]] bool HasButton(not_null<WebPageData*> webpage) {
|
||||
const auto type = webpage->type;
|
||||
return (type == WebPageType::Message)
|
||||
return webpage->iv
|
||||
|| (type == WebPageType::Message)
|
||||
|| (type == WebPageType::Group)
|
||||
|| (type == WebPageType::Channel)
|
||||
|| (type == WebPageType::ChannelBoost)
|
||||
|
@ -245,7 +272,7 @@ QSize WebPage::countOptimalSize() {
|
|||
}
|
||||
return true;
|
||||
}();
|
||||
_openl = (previewOfHiddenUrl
|
||||
_openl = _data->iv ? IvClickHandler(_data) : (previewOfHiddenUrl
|
||||
|| UrlClickHandler::IsSuspicious(_data->url))
|
||||
? std::make_shared<HiddenUrlClickHandler>(_data->url)
|
||||
: 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