mirror of
https://github.com/telegramdesktop/tdesktop
synced 2024-12-25 16:02:52 +00:00
Add some javascript handlers to HTML export.
This commit is contained in:
parent
a99ae76ad4
commit
aaa1245430
@ -245,6 +245,14 @@ a.block_link:hover {
|
||||
.history {
|
||||
padding: 16px 0;
|
||||
}
|
||||
.message {
|
||||
margin: 0 -10px;
|
||||
transition: background-color 2.0s ease;
|
||||
}
|
||||
div.selected {
|
||||
background-color: rgba(242,246,250,255);
|
||||
transition: background-color 0.5s ease;
|
||||
}
|
||||
.service {
|
||||
padding: 10px 24px;
|
||||
}
|
||||
@ -264,7 +272,7 @@ a.block_link:hover {
|
||||
font-size: 16px;
|
||||
}
|
||||
.default {
|
||||
padding: 10px 0 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
.default.joined {
|
||||
padding-top: 0;
|
||||
@ -379,6 +387,26 @@ a.block_link:hover {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.toast_container {
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
opacity: 0;
|
||||
transition: opacity 3.0s ease;
|
||||
}
|
||||
.toast_body {
|
||||
margin: 0 -50%;
|
||||
float: left;
|
||||
border-radius: 15px;
|
||||
padding: 10px 20px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: #ffffff;
|
||||
}
|
||||
div.toast_shown {
|
||||
opacity: 1;
|
||||
transition: opacity 0.4s ease;
|
||||
}
|
||||
|
||||
.section.calls {
|
||||
background-image: url(../images/section_calls.png);
|
||||
}
|
||||
|
189
Telegram/Resources/export_html/js/script.js
Normal file
189
Telegram/Resources/export_html/js/script.js
Normal file
@ -0,0 +1,189 @@
|
||||
"use strict";
|
||||
|
||||
window.AllowBackFromHistory = false;
|
||||
function CheckLocation() {
|
||||
var start = "#go_to_message";
|
||||
var hash = location.hash;
|
||||
if (hash.substr(0, start.length) == start) {
|
||||
var messageId = parseInt(hash.substr(start.length));
|
||||
if (messageId) {
|
||||
GoToMessage(messageId);
|
||||
}
|
||||
} else if (hash == "#allow_back") {
|
||||
window.AllowBackFromHistory = true;
|
||||
}
|
||||
}
|
||||
|
||||
function ShowToast(text) {
|
||||
var container = document.createElement("div");
|
||||
container.className = "toast_container";
|
||||
var inner = container.appendChild(document.createElement("div"));
|
||||
inner.className = "toast_body";
|
||||
inner.appendChild(document.createTextNode(text));
|
||||
var appended = document.body.appendChild(container);
|
||||
setTimeout(function () {
|
||||
AddClass(appended, "toast_shown");
|
||||
setTimeout(function () {
|
||||
RemoveClass(appended, "toast_shown");
|
||||
setTimeout(function () {
|
||||
document.body.removeChild(appended);
|
||||
}, 3000);
|
||||
}, 3000);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function ShowHashtag(tag) {
|
||||
ShowToast("This is a hashtag '#" + tag + "' link.");
|
||||
return false;
|
||||
}
|
||||
|
||||
function ShowCashtag(tag) {
|
||||
ShowToast("This is a cashtag '$" + tag + "' link.");
|
||||
return false;
|
||||
}
|
||||
|
||||
function ShowBotCommand(command) {
|
||||
ShowToast("This is a bot command '/" + command + "' link.");
|
||||
return false;
|
||||
}
|
||||
|
||||
function ShowMentionName() {
|
||||
ShowToast("This is a link to a user mentioned by name.");
|
||||
return false;
|
||||
}
|
||||
|
||||
function AddClass(element, name) {
|
||||
var current = element.className;
|
||||
var expression = new RegExp('(^|\\s)' + name + '(\\s|$)', 'g');
|
||||
if (expression.test(current)) {
|
||||
return;
|
||||
}
|
||||
element.className = current + ' ' + name;
|
||||
}
|
||||
|
||||
function RemoveClass(element, name) {
|
||||
var current = element.className;
|
||||
var expression = new RegExp('(^|\\s)' + name + '(\\s|$)', '');
|
||||
var match = expression.exec(current);
|
||||
while ((match = expression.exec(current)) != null) {
|
||||
if (match[1].length > 0 && match[2].length > 0) {
|
||||
current = current.substr(0, match.index + match[1].length)
|
||||
+ current.substr(match.index + match[0].length);
|
||||
} else {
|
||||
current = current.substr(0, match.index)
|
||||
+ current.substr(match.index + match[0].length);
|
||||
}
|
||||
}
|
||||
element.className = current;
|
||||
}
|
||||
|
||||
function EaseOutQuad(t) {
|
||||
return t * t;
|
||||
}
|
||||
|
||||
function EaseInOutQuad(t) {
|
||||
return (t < 0.5) ? (2 * t * t) : ((4 - 2 * t) * t - 1);
|
||||
}
|
||||
|
||||
function ScrollHeight() {
|
||||
if ("innerHeight" in window) {
|
||||
return window.innerHeight;
|
||||
} else if (document.documentElement) {
|
||||
return document.documentElement.clientHeight;
|
||||
}
|
||||
return document.body.clientHeight;
|
||||
}
|
||||
|
||||
function ScrollTo(top, callback) {
|
||||
var html = document.documentElement;
|
||||
var current = html.scrollTop;
|
||||
var delta = top - current;
|
||||
var finish = function () {
|
||||
html.scrollTop = top;
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
if (!window.performance.now || delta == 0) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
var transition = EaseOutQuad;
|
||||
var max = 300;
|
||||
if (delta < -max) {
|
||||
current = top + max;
|
||||
delta = -max;
|
||||
} else if (delta > max) {
|
||||
current = top - max;
|
||||
delta = max;
|
||||
} else {
|
||||
transition = EaseInOutQuad;
|
||||
}
|
||||
var duration = 150;
|
||||
var interval = 7;
|
||||
var time = window.performance.now();
|
||||
var animate = function () {
|
||||
var now = window.performance.now();
|
||||
if (now >= time + duration) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
var dt = (now - time) / duration;
|
||||
html.scrollTop = Math.round(current + delta * transition(dt));
|
||||
setTimeout(animate, interval);
|
||||
};
|
||||
setTimeout(animate, interval);
|
||||
}
|
||||
|
||||
function ScrollToElement(element, callback) {
|
||||
var header = document.getElementsByClassName("page_header")[0];
|
||||
var headerHeight = header.offsetHeight;
|
||||
var html = document.documentElement;
|
||||
var scrollHeight = ScrollHeight();
|
||||
var available = scrollHeight - headerHeight;
|
||||
var padding = 10;
|
||||
var top = element.offsetTop;
|
||||
var height = element.offsetHeight;
|
||||
var desired = top
|
||||
- Math.max((available - height) / 2, padding)
|
||||
- headerHeight;
|
||||
var scrollTopMax = html.offsetHeight - scrollHeight;
|
||||
ScrollTo(Math.min(desired, scrollTopMax), callback);
|
||||
}
|
||||
|
||||
function GoToMessage(messageId) {
|
||||
var element = document.getElementById("message" + messageId);
|
||||
if (element) {
|
||||
var hash = "#go_to_message" + messageId;
|
||||
if (location.hash != hash) {
|
||||
location.hash = hash;
|
||||
}
|
||||
ScrollToElement(element, function () {
|
||||
AddClass(element, "selected");
|
||||
setTimeout(function () {
|
||||
RemoveClass(element, "selected");
|
||||
}, 1000);
|
||||
});
|
||||
} else {
|
||||
ShowToast("This message was not exported. Maybe it was deleted.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function GoBack(anchor) {
|
||||
if (!window.AllowBackFromHistory) {
|
||||
return true;
|
||||
}
|
||||
history.back();
|
||||
if (!anchor || !anchor.getAttribute) {
|
||||
return true;
|
||||
}
|
||||
var destination = anchor.getAttribute("href");
|
||||
if (!destination) {
|
||||
return true;
|
||||
}
|
||||
setTimeout(function () {
|
||||
location.href = destination;
|
||||
}, 100);
|
||||
return false;
|
||||
}
|
@ -39,6 +39,7 @@
|
||||
<file alias="images/section_sessions@2x.png">../export_html/images/section_sessions@2x.png</file>
|
||||
<file alias="images/section_web.png">../export_html/images/section_web.png</file>
|
||||
<file alias="images/section_web@2x.png">../export_html/images/section_web@2x.png</file>
|
||||
<file alias="js/script.js">../export_html/js/script.js</file>
|
||||
</qresource>
|
||||
<qresource prefix="/gui">
|
||||
<file alias="fonts/OpenSans-Regular.ttf">../fonts/OpenSans-Regular.ttf</file>
|
||||
|
@ -972,6 +972,13 @@ void ApiWrap::cancelExportFast() {
|
||||
}
|
||||
|
||||
void ApiWrap::requestSinglePeerDialog() {
|
||||
const auto isChannelType = [](Data::DialogInfo::Type type) {
|
||||
using Type = Data::DialogInfo::Type;
|
||||
return (type == Type::PrivateSupergroup)
|
||||
|| (type == Type::PublicSupergroup)
|
||||
|| (type == Type::PrivateChannel)
|
||||
|| (type == Type::PublicChannel);
|
||||
};
|
||||
auto doneSinglePeer = [=](const auto &result) {
|
||||
auto info = Data::ParseDialogsInfo(_settings->singlePeer, result);
|
||||
|
||||
@ -980,6 +987,9 @@ void ApiWrap::requestSinglePeerDialog() {
|
||||
|
||||
const auto last = _dialogsProcess->splitIndexPlusOne - 1;
|
||||
for (auto &info : _dialogsProcess->info.chats) {
|
||||
if (isChannelType(info.type)) {
|
||||
continue;
|
||||
}
|
||||
for (auto i = last; i != 0; --i) {
|
||||
info.splits.push_back(i - 1);
|
||||
info.messagesCountPerSplit.push_back(0);
|
||||
|
@ -253,12 +253,14 @@ QByteArray FormatText(
|
||||
+ internalLinksDomain.toUtf8()
|
||||
+ text.mid(1)
|
||||
+ "\">" + text + "</a>";
|
||||
case Type::Hashtag: return "<a href=\"#hash-"
|
||||
+ text.mid(1)
|
||||
+ "\">" + text + "</a>";
|
||||
case Type::BotCommand: return "<a href=\"#command-"
|
||||
+ text.mid(1)
|
||||
+ "\">" + text + "</a>";
|
||||
case Type::Hashtag: return "<a href=\"\" "
|
||||
"onclick=\"return ShowHashtag("
|
||||
+ SerializeString('"' + text.mid(1) + '"')
|
||||
+ ")\">" + text + "</a>";
|
||||
case Type::BotCommand: return "<a href=\"\" "
|
||||
"onclick=\"return ShowBotCommand("
|
||||
+ SerializeString('"' + text.mid(1) + '"')
|
||||
+ ")\">" + text + "</a>";
|
||||
case Type::Url: return "<a href=\""
|
||||
+ text
|
||||
+ "\">" + text + "</a>";
|
||||
@ -272,15 +274,15 @@ QByteArray FormatText(
|
||||
case Type::TextUrl: return "<a href=\""
|
||||
+ SerializeString(part.additional)
|
||||
+ "\">" + text + "</a>";
|
||||
case Type::MentionName: return "<a href=\"#mention-"
|
||||
+ part.additional
|
||||
+ "\">" + text + "</a>";
|
||||
case Type::MentionName: return "<a href=\"\" "
|
||||
"onclick=\"return ShowMentionName()\">" + text + "</a>";
|
||||
case Type::Phone: return "<a href=\"tel:"
|
||||
+ text
|
||||
+ "\">" + text + "</a>";
|
||||
case Type::Cashtag: return "<a href=\"#cash-"
|
||||
+ text.mid(1)
|
||||
+ "\">" + text + "</a>";
|
||||
case Type::Cashtag: return "<a href=\"\" "
|
||||
"onclick=\"return ShowCashtag("
|
||||
+ SerializeString('"' + text.mid(1) + '"')
|
||||
+ ")\">" + text + "</a>";
|
||||
}
|
||||
Unexpected("Type in text entities serialization.");
|
||||
}) | ranges::to_vector);
|
||||
@ -506,6 +508,7 @@ struct HtmlWriter::MessageInfo {
|
||||
Service,
|
||||
Default,
|
||||
};
|
||||
int32 id = 0;
|
||||
Type type = Type::Service;
|
||||
int32 fromId = 0;
|
||||
TimeId date = 0;
|
||||
@ -566,7 +569,8 @@ public:
|
||||
const Data::DialogInfo &dialog,
|
||||
const QString &basePath,
|
||||
const PeersMap &peers,
|
||||
const QString &internalLinksDomain);
|
||||
const QString &internalLinksDomain,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink);
|
||||
|
||||
[[nodiscard]] Result writeBlock(const QByteArray &block);
|
||||
|
||||
@ -794,7 +798,7 @@ QByteArray HtmlWriter::Wrap::pushGenericListEntry(
|
||||
? pushDiv("entry clearfix")
|
||||
: pushTag("a", {
|
||||
{ "class", "entry block_link clearfix" },
|
||||
{ "href", relativePath(link).toUtf8() },
|
||||
{ "href", relativePath(link).toUtf8() + "#allow_back" },
|
||||
});
|
||||
result.append(pushDiv("pull_left userpic_wrap"));
|
||||
result.append(pushUserpic(userpic));
|
||||
@ -850,7 +854,8 @@ QByteArray HtmlWriter::Wrap::pushHeader(
|
||||
? pushDiv("content")
|
||||
: pushTag("a", {
|
||||
{ "class", "content block_link" },
|
||||
{ "href", relativePath(path).toUtf8() }
|
||||
{ "href", relativePath(path).toUtf8() },
|
||||
{ "onclick", "return GoBack(this)"},
|
||||
}));
|
||||
result.append(pushDiv("text bold"));
|
||||
result.append(SerializeString(header));
|
||||
@ -867,7 +872,7 @@ QByteArray HtmlWriter::Wrap::pushSection(
|
||||
const QString &link) {
|
||||
auto result = pushTag("a", {
|
||||
{ "class", "section block_link " + type },
|
||||
{ "href", link.toUtf8() },
|
||||
{ "href", link.toUtf8() + "#allow_back" },
|
||||
});
|
||||
result.append(pushDiv("counter details"));
|
||||
result.append(Data::NumberToString(count));
|
||||
@ -924,16 +929,18 @@ QByteArray HtmlWriter::Wrap::pushServiceMessage(
|
||||
}
|
||||
|
||||
auto HtmlWriter::Wrap::pushMessage(
|
||||
const Data::Message &message,
|
||||
const MessageInfo *previous,
|
||||
const Data::DialogInfo &dialog,
|
||||
const QString &basePath,
|
||||
const PeersMap &peers,
|
||||
const QString &internalLinksDomain
|
||||
const Data::Message &message,
|
||||
const MessageInfo *previous,
|
||||
const Data::DialogInfo &dialog,
|
||||
const QString &basePath,
|
||||
const PeersMap &peers,
|
||||
const QString &internalLinksDomain,
|
||||
Fn<QByteArray(int messageId, QByteArray text)> wrapMessageLink
|
||||
) -> std::pair<MessageInfo, QByteArray> {
|
||||
using namespace Data;
|
||||
|
||||
auto info = MessageInfo();
|
||||
info.id = message.id;
|
||||
info.fromId = message.fromId;
|
||||
info.date = message.date;
|
||||
info.forwardedFromId = message.forwardedFromId;
|
||||
@ -948,10 +955,7 @@ auto HtmlWriter::Wrap::pushMessage(
|
||||
}
|
||||
|
||||
const auto wrapReplyToLink = [&](const QByteArray &text) {
|
||||
return "<a href=\"#message"
|
||||
+ NumberToString(message.replyToMsgId)
|
||||
+ "\">"
|
||||
+ text + "</a>";
|
||||
return wrapMessageLink(message.replyToMsgId, text);
|
||||
};
|
||||
|
||||
const auto serviceFrom = peers.wrapUserName(message.fromId);
|
||||
@ -1706,8 +1710,15 @@ QByteArray HtmlWriter::Wrap::composeStart() {
|
||||
{ "rel", "stylesheet" },
|
||||
{ "empty", "" }
|
||||
}));
|
||||
result.append(_context.pushTag("script", {
|
||||
{ "src", _base + "js/script.js" },
|
||||
{ "type", "text/javascript" },
|
||||
}));
|
||||
result.append(_context.popTag());
|
||||
result.append(popTag());
|
||||
result.append(pushTag("body"));
|
||||
result.append(pushTag("body", {
|
||||
{ "onload", "CheckLocation();" }
|
||||
}));
|
||||
result.append(pushDiv("page_wrap"));
|
||||
return result;
|
||||
}
|
||||
@ -1758,6 +1769,7 @@ Result HtmlWriter::start(
|
||||
"images/section_photos.png",
|
||||
"images/section_sessions.png",
|
||||
"images/section_web.png",
|
||||
"js/script.js",
|
||||
};
|
||||
for (const auto path : files) {
|
||||
const auto name = QString(path);
|
||||
@ -2239,6 +2251,7 @@ Result HtmlWriter::writeDialogStart(const Data::DialogInfo &data) {
|
||||
_messagesCount = 0;
|
||||
_dateMessageId = 0;
|
||||
_lastMessageInfo = nullptr;
|
||||
_lastMessageIdsPerFile.clear();
|
||||
_dialog = data;
|
||||
return Result::Success();
|
||||
}
|
||||
@ -2247,11 +2260,32 @@ Result HtmlWriter::writeDialogSlice(const Data::MessagesSlice &data) {
|
||||
Expects(_chat != nullptr);
|
||||
Expects(!data.list.empty());
|
||||
|
||||
const auto messageLinkWrapper = [&](int messageId, QByteArray text) {
|
||||
return wrapMessageLink(messageId, text);
|
||||
};
|
||||
auto oldIndex = (_messagesCount / kMessagesInFile);
|
||||
auto previous = _lastMessageInfo.get();
|
||||
auto saved = base::optional<MessageInfo>();
|
||||
auto block = QByteArray();
|
||||
for (const auto &message : data.list) {
|
||||
const auto newIndex = (_messagesCount / kMessagesInFile);
|
||||
if (oldIndex != newIndex) {
|
||||
if (const auto result = _chat->writeBlock(block); !result) {
|
||||
return result;
|
||||
} else if (const auto next = switchToNextChatFile(newIndex)) {
|
||||
Assert(saved.has_value() || _lastMessageInfo != nullptr);
|
||||
_lastMessageIdsPerFile.push_back(saved
|
||||
? saved->id
|
||||
: _lastMessageInfo->id);
|
||||
block = QByteArray();
|
||||
_lastMessageInfo = nullptr;
|
||||
previous = nullptr;
|
||||
saved = base::none;
|
||||
oldIndex = newIndex;
|
||||
} else {
|
||||
return next;
|
||||
}
|
||||
}
|
||||
if (_chatFileEmpty) {
|
||||
if (const auto result = writeDialogOpening(oldIndex); !result) {
|
||||
return result;
|
||||
@ -2272,27 +2306,13 @@ Result HtmlWriter::writeDialogSlice(const Data::MessagesSlice &data) {
|
||||
_dialog,
|
||||
_settings.path,
|
||||
data.peers,
|
||||
_environment.internalLinksDomain);
|
||||
_environment.internalLinksDomain,
|
||||
messageLinkWrapper);
|
||||
block.append(content);
|
||||
|
||||
++_messagesCount;
|
||||
const auto newIndex = (_messagesCount / kMessagesInFile);
|
||||
if (oldIndex != newIndex) {
|
||||
if (const auto result = _chat->writeBlock(block); !result) {
|
||||
return result;
|
||||
} else if (const auto next = switchToNextChatFile(newIndex)) {
|
||||
block = QByteArray();
|
||||
_lastMessageInfo = nullptr;
|
||||
previous = nullptr;
|
||||
saved = base::none;
|
||||
oldIndex = newIndex;
|
||||
} else {
|
||||
return next;
|
||||
}
|
||||
} else {
|
||||
saved = info;
|
||||
previous = &*saved;
|
||||
}
|
||||
saved = info;
|
||||
previous = &*saved;
|
||||
}
|
||||
if (saved) {
|
||||
_lastMessageInfo = std::make_unique<MessageInfo>(*saved);
|
||||
@ -2474,7 +2494,9 @@ void HtmlWriter::pushSection(
|
||||
Result HtmlWriter::writeSections() {
|
||||
Expects(_summary != nullptr);
|
||||
|
||||
if (!_haveSections) {
|
||||
if (_savedSections.empty()) {
|
||||
return Result::Success();
|
||||
} else if (!_haveSections) {
|
||||
auto block = _summary->pushDiv(
|
||||
_summaryNeedDivider ? "sections with_divider" : "sections");
|
||||
if (const auto result = _summary->writeBlock(block); !result) {
|
||||
@ -2498,6 +2520,29 @@ Result HtmlWriter::writeSections() {
|
||||
return _summary->writeBlock(block);
|
||||
}
|
||||
|
||||
QByteArray HtmlWriter::wrapMessageLink(int messageId, QByteArray text) {
|
||||
const auto finishedCount = _lastMessageIdsPerFile.size();
|
||||
const auto it = ranges::find_if(_lastMessageIdsPerFile, [&](int maxMessageId) {
|
||||
return messageId <= maxMessageId;
|
||||
});
|
||||
if (it == end(_lastMessageIdsPerFile)) {
|
||||
return "<a href=\"#go_to_message"
|
||||
+ Data::NumberToString(messageId)
|
||||
+ "\" onclick=\"return GoToMessage("
|
||||
+ Data::NumberToString(messageId)
|
||||
+ ")\">"
|
||||
+ text + "</a>";
|
||||
} else {
|
||||
const auto index = it - begin(_lastMessageIdsPerFile);
|
||||
return "<a href=\"" + messagesFile(index).toUtf8()
|
||||
+ "#go_to_message"
|
||||
+ Data::NumberToString(messageId)
|
||||
+ "\">"
|
||||
+ text + "</a>";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Result HtmlWriter::switchToNextChatFile(int index) {
|
||||
Expects(_chat != nullptr);
|
||||
|
||||
@ -2525,6 +2570,9 @@ Result HtmlWriter::finish() {
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
if (const auto result = writeSections(); !result) {
|
||||
return result;
|
||||
}
|
||||
auto block = QByteArray();
|
||||
if (_haveSections) {
|
||||
block.append(_summary->popTag());
|
||||
|
@ -128,6 +128,10 @@ private:
|
||||
|
||||
[[nodiscard]] QString userpicsFilePath() const;
|
||||
|
||||
[[nodiscard]] QByteArray wrapMessageLink(
|
||||
int messageId,
|
||||
QByteArray text);
|
||||
|
||||
Settings _settings;
|
||||
Environment _environment;
|
||||
Stats *_stats = nullptr;
|
||||
@ -154,6 +158,7 @@ private:
|
||||
int _dateMessageId = 0;
|
||||
std::unique_ptr<Wrap> _chats;
|
||||
std::unique_ptr<Wrap> _chat;
|
||||
std::vector<int> _lastMessageIdsPerFile;
|
||||
bool _chatFileEmpty = false;
|
||||
|
||||
};
|
||||
|
@ -110,6 +110,7 @@
|
||||
'<!@(<(list_sources_command) <(qt_moc_list_sources_arg))',
|
||||
'telegram_sources.txt',
|
||||
'<(res_loc)/export_html/css/style.css',
|
||||
'<(res_loc)/export_html/js/script.js',
|
||||
'<(res_loc)/export_html/images/back.png',
|
||||
'<(res_loc)/export_html/images/back@2x.png',
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user