diff --git a/Telegram/Resources/iv_html/page.css b/Telegram/Resources/iv_html/page.css
index 58ac7a6d1..856922333 100644
--- a/Telegram/Resources/iv_html/page.css
+++ b/Telegram/Resources/iv_html/page.css
@@ -598,70 +598,22 @@ figure.slideshow-wrap {
position: relative;
}
figure.slideshow {
- position: relative;
+ position: absolute;
+ top: 0px;
white-space: nowrap;
width: 100%;
background: #000;
overflow: hidden;
}
-figure.slideshow > figure {
+figure.slideshow a {
+ transition: margin 200ms ease-in-out;
+}
+figure.slideshow .photo-wrap,
+figure.slideshow .video-wrap {
position: static !important;
display: inline-block;
- width: 100%;
+ margin: 0;
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;
@@ -672,8 +624,8 @@ figure.slideshow > figure figcaption a:hover {
z-index: 3;
}
.slideshow-buttons > fieldset {
- padding: 0 10px 20px;
- margin: 0 0 -20px;
+ padding: 0;
+ margin: 0;
border: none;
line-height: 0;
overflow: hidden;
@@ -702,6 +654,54 @@ figure.slideshow > figure figcaption a:hover {
.slideshow-buttons input:checked ~ i {
opacity: 1;
}
+.slideshow-next,
+.slideshow-prev {
+ position: absolute;
+ z-index: 4;
+ top: 0;
+ width: 25%;
+ max-width: 128px;
+ height: 100%;
+ cursor: pointer;
+ transition: opacity 200ms ease-in-out;
+ user-select: none;
+ opacity: 0.6;
+}
+.slideshow-next {
+ right: 0;
+ background: linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%);
+}
+.slideshow-prev {
+ left: 0;
+ background: linear-gradient(to left, rgba(0,0,0,0) 0%, rgba(0,0,0,0.6) 100%);
+}
+.slideshow-next:hover {
+ opacity: 1;
+}
+.slideshow-prev:hover {
+ opacity: 1;
+}
+.slideshow-prev svg,
+.slideshow-next svg {
+ fill: none;
+ top: calc(50% - 12px);
+ position: absolute;
+ z-index: 5;
+ width: 24px;
+ height: 24px;
+ pointer-events: none;
+}
+.slideshow-prev svg {
+ left: calc(min(50% - 12px, 20px));
+}
+.slideshow-next svg {
+ right: calc(min(50% - 12px, 20px));
+}
+.slideshow-prev path,
+.slideshow-next path {
+ stroke-width: 1.4;
+ stroke: #fff;
+}
figure.collage-wrap {
margin: 0px 12px;
diff --git a/Telegram/Resources/iv_html/page.js b/Telegram/Resources/iv_html/page.js
index 181a11498..dca833fee 100644
--- a/Telegram/Resources/iv_html/page.js
+++ b/Telegram/Resources/iv_html/page.js
@@ -106,14 +106,16 @@ var IV = {
}
}
},
- slideshowSlide: function(el, next) {
+ slideshowSlide: function(el, delta) {
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;
+ if (delta) {
+ var form = el.parentNode.firstChild;
+ var s = form.s;
+ const next = +s.value + delta;
+ s.value = (next == s.length) ? 0 : (next == -1) ? (s.length - 1) : next;
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) + '%';
+ form.nextSibling.firstChild.style[marginProp] = (-100 * s.value) + '%';
} else {
el.form.nextSibling.firstChild.style[marginProp] = (-100 * el.value) + '%';
}
diff --git a/Telegram/SourceFiles/iv/iv_prepare.cpp b/Telegram/SourceFiles/iv/iv_prepare.cpp
index ebfbadd23..a9186997f 100644
--- a/Telegram/SourceFiles/iv/iv_prepare.cpp
+++ b/Telegram/SourceFiles/iv/iv_prepare.cpp
@@ -69,6 +69,9 @@ private:
const QVector &list,
const std::vector &dimensions,
int offset = 0);
+ [[nodiscard]] QByteArray slideshow(
+ const QVector &list,
+ QSize dimensions);
[[nodiscard]] QByteArray block(const MTPDpageBlockUnsupported &data);
[[nodiscard]] QByteArray block(const MTPDpageBlockTitle &data);
@@ -87,11 +90,13 @@ private:
[[nodiscard]] QByteArray block(
const MTPDpageBlockPhoto &data,
const Ui::GroupMediaLayout &layout = {},
- QSize outer = {});
+ QSize outer = {},
+ int slideshowIndex = -1);
[[nodiscard]] QByteArray block(
const MTPDpageBlockVideo &data,
const Ui::GroupMediaLayout &layout = {},
- QSize outer = {});
+ QSize outer = {},
+ int slideshowIndex = -1);
[[nodiscard]] QByteArray block(const MTPDpageBlockCover &data);
[[nodiscard]] QByteArray block(const MTPDpageBlockEmbed &data);
[[nodiscard]] QByteArray block(const MTPDpageBlockEmbedPost &data);
@@ -151,6 +156,8 @@ private:
[[nodiscard]] std::vector computeCollageDimensions(
const QVector &items);
+ [[nodiscard]] QSize computeSlideshowDimensions(
+ const QVector &items);
const Options _options;
@@ -158,9 +165,6 @@ private:
Prepared _result;
- bool _rtl = false;
- bool _captionAsTitle = false;
- bool _captionWrapped = false;
base::flat_map _photosById;
base::flat_map _documentsById;
@@ -186,6 +190,17 @@ private:
return voids.contains(name);
}
+[[nodiscard]] QByteArray ArrowSvg(bool left) {
+ const auto rotate = QByteArray(left ? "180" : "0");
+ return R"(
+)";
+}
+
Parser::Parser(const Source &source, const Options &options)
: _options(options) {
process(source);
@@ -292,6 +307,56 @@ QByteArray Parser::collage(
return wrapped;
}
+QByteArray Parser::slideshow(
+ const QVector &list,
+ QSize dimensions) {
+ auto result = QByteArray();
+ for (auto i = 0, count = int(list.size()); i != count; ++i) {
+ list[i].match([&](const MTPDpageBlockPhoto &data) {
+ result += block(data, {}, dimensions);
+ }, [&](const MTPDpageBlockVideo &data) {
+ result += block(data, {}, dimensions);
+ }, [](const auto &) {
+ Unexpected("Block type in collage layout.");
+ });
+ }
+
+ auto inputs = QByteArrayList();
+ for (auto i = 0; i != int(list.size()); ++i) {
+ auto attributes = Attributes{
+ { "type", "radio" },
+ { "name", "s" },
+ { "value", Number(i) },
+ { "onchange", "return IV.slideshowSlide(this);" },
+ };
+ if (!i) {
+ attributes.push_back({ "checked", std::nullopt });
+ }
+ inputs.append(tag("label", tag("input", attributes, tag("i"))));
+ }
+ const auto form = tag(
+ "form",
+ { { "class", "slideshow-buttons" } },
+ tag("fieldset", inputs.join(QByteArray())));
+ const auto navigation = tag("a", {
+ { "class", "slideshow-prev" },
+ { "onclick", "IV.slideshowSlide(this, -1);" },
+ }, ArrowSvg(true)) + tag("a", {
+ { "class", "slideshow-next" },
+ { "onclick", "IV.slideshowSlide(this, 1);" },
+ }, ArrowSvg(false));
+ auto wrapStyle = "padding-top: calc(min("
+ + Percent(dimensions.height() / float64(dimensions.width()))
+ + "%, 480px));";
+ result = form + tag("figure", {
+ { "class", "slideshow" },
+ }, result) + navigation;
+ return tag("figure", {
+ { "class", "slideshow-wrap" },
+ { "style", wrapStyle },
+ }, result);
+}
+
QByteArray Parser::block(const MTPDpageBlockUnsupported &data) {
return "Unsupported."_q;
}
@@ -375,8 +440,10 @@ QByteArray Parser::block(const MTPDpageBlockPullquote &data) {
QByteArray Parser::block(
const MTPDpageBlockPhoto &data,
const Ui::GroupMediaLayout &layout,
- QSize outer) {
+ QSize outer,
+ int slideshowIndex) {
const auto collage = !layout.geometry.isEmpty();
+ const auto slideshow = !collage && !outer.isEmpty();
const auto photo = photoById(data.vphoto_id().v);
if (!photo.id) {
return "Photo not found.";
@@ -390,7 +457,7 @@ QByteArray Parser::block(
+ "top: " + Percent(layout.geometry.y() * hcoef) + "%; "
+ "width: " + Percent(layout.geometry.width() * wcoef) + "%; "
+ "height: " + Percent(layout.geometry.height() * hcoef) + "%";
- } else if (photo.width) {
+ } else if (!slideshow && photo.width) {
wrapStyle += "max-width:" + Number(photo.width) + "px";
}
const auto dimension = collage
@@ -420,16 +487,6 @@ QByteArray Parser::block(
{ "class", "photo-wrap" },
{ "style", wrapStyle }
};
- if (_captionAsTitle) {
- const auto caption = plain(data.vcaption().data().vtext());
- const auto credit = plain(data.vcaption().data().vtext());
- if (!caption.isEmpty() || !credit.isEmpty()) {
- const auto title = (!caption.isEmpty() && !credit.isEmpty())
- ? (caption + " / " + credit)
- : (caption + credit);
- attributes.push_back({ "title", title });
- }
- }
auto result = tag("div", attributes, inner);
const auto href = data.vurl()
@@ -439,20 +496,19 @@ QByteArray Parser::block(
result = tag("a", {
{ "href", href },
{ "data-context", data.vurl() ? QByteArray() : "viewer-photo" + id },
- }, result);
- if (!_captionAsTitle) {
- result += caption(data.vcaption());
- }
+ }, result) + caption(data.vcaption());
return result;
}
QByteArray Parser::block(
const MTPDpageBlockVideo &data,
const Ui::GroupMediaLayout &layout,
- QSize outer) {
+ QSize outer,
+ int slideshowIndex) {
const auto collage = !layout.geometry.isEmpty();
const auto collageSmall = collage
&& (layout.geometry.width() < outer.width());
+ const auto slideshow = !collage && !outer.isEmpty();
const auto video = documentById(data.vvideo_id().v);
if (!video.id) {
return "Video not found.";
@@ -512,16 +568,6 @@ QByteArray Parser::block(
{ "class", "video-wrap" },
{ "style", wrapStyle },
};
- if (_captionAsTitle) {
- const auto caption = plain(data.vcaption().data().vtext());
- const auto credit = plain(data.vcaption().data().vtext());
- if (!caption.isEmpty() || !credit.isEmpty()) {
- const auto title = (!caption.isEmpty() && !credit.isEmpty())
- ? (caption + " / " + credit)
- : (caption + credit);
- attributes.push_back({ "title", title });
- }
- }
auto result = tag("div", attributes, inner);
if (data.is_autoplay() || collageSmall) {
const auto id = Number(video.id);
@@ -531,9 +577,7 @@ QByteArray Parser::block(
{ "data-context", "viewer-video" + id },
}, result);
}
- if (!_captionAsTitle) {
- result += caption(data.vcaption());
- }
+ result += caption(data.vcaption());
return result;
}
@@ -644,32 +688,12 @@ QByteArray Parser::block(const MTPDpageBlockCollage &data) {
}
QByteArray Parser::block(const MTPDpageBlockSlideshow &data) {
- auto inputs = QByteArrayList();
- auto i = 0;
- for (auto i = 0; i != int(data.vitems().v.size()); ++i) {
- auto attributes = Attributes{
- { "type", "radio" },
- { "name", "s" },
- { "value", Number(i) },
- { "onchange", "return IV.slideshowSlide(this);" },
- };
- if (!i) {
- attributes.push_back({ "checked", std::nullopt });
- }
- inputs.append(tag("label", tag("input", attributes, tag("i"))));
+ const auto &items = data.vitems().v;
+ const auto dimensions = computeSlideshowDimensions(items);
+ if (dimensions.isEmpty()) {
+ return list(data.vitems());
}
- const auto form = tag(
- "form",
- { { "class", "slideshow-buttons" } },
- tag("fieldset", inputs.join(QByteArray())));
- auto inner = form + tag("figure", {
- { "class", "slideshow" },
- { "onclick", "return IV.slideshowSlide(this, 1);" },
- }, list(data.vitems()));
- auto result = tag(
- "figure",
- { { "class", "slideshow-wrap" } },
- inner);
+ const auto result = slideshow(items, dimensions);
return tag("figure", result + caption(data.vcaption()));
}
@@ -909,7 +933,7 @@ QByteArray Parser::tag(
list.push_back(' ' + name + (value ? "=\"" + *value + "\"" : ""));
}
const auto serialized = list.join(QByteArray());
- return IsVoidElement(name)
+ return (IsVoidElement(name) && body.isEmpty())
? ('<' + name + serialized + " />")
: ('<' + name + serialized + '>' + body + "" + name + '>');
}
@@ -1024,9 +1048,6 @@ QByteArray Parser::plain(const MTPRichText &text) {
QByteArray Parser::caption(const MTPPageCaption &caption) {
auto text = rich(caption.data().vtext());
const auto credit = rich(caption.data().vcredit());
- if (_captionWrapped && !text.isEmpty()) {
- text = tag("span", text);
- }
if (!credit.isEmpty()) {
text += tag("cite", credit);
} else if (text.isEmpty()) {
@@ -1170,12 +1191,11 @@ QByteArray Parser::resource(QByteArray id) {
std::vector Parser::computeCollageDimensions(
const QVector &items) {
- auto result = std::vector(items.size());
if (items.size() < 2) {
return {};
}
+ auto result = std::vector(items.size());
for (auto i = 0, count = int(items.size()); i != count; ++i) {
- auto size = QSize();
items[i].match([&](const MTPDpageBlockPhoto &data) {
const auto photo = photoById(data.vphoto_id().v);
if (photo.id && photo.width > 0 && photo.height > 0) {
@@ -1194,6 +1214,35 @@ std::vector Parser::computeCollageDimensions(
return result;
}
+QSize Parser::computeSlideshowDimensions(
+ const QVector &items) {
+ if (items.size() < 2) {
+ return {};
+ }
+ auto result = QSize();
+ for (const auto &item : items) {
+ auto size = QSize();
+ item.match([&](const MTPDpageBlockPhoto &data) {
+ const auto photo = photoById(data.vphoto_id().v);
+ if (photo.id && photo.width > 0 && photo.height > 0) {
+ size = QSize(photo.width, photo.height);
+ }
+ }, [&](const MTPDpageBlockVideo &data) {
+ const auto document = documentById(data.vvideo_id().v);
+ if (document.id && document.width > 0 && document.height > 0) {
+ size = QSize(document.width, document.height);
+ }
+ }, [](const auto &) {});
+ if (size.isEmpty()) {
+ return {};
+ } else if (result.height() * size.width()
+ < result.width() * size.height()) {
+ result = size;
+ }
+ }
+ return result;
+}
+
} // namespace
Prepared Prepare(const Source &source, const Options &options) {