mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-04-01 00:08:02 +00:00
parent
cca46448fe
commit
07d8dafa5e
@ -1259,8 +1259,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_menu_formatting_bold" = "Bold";
|
||||
"lng_menu_formatting_italic" = "Italic";
|
||||
"lng_menu_formatting_monospace" = "Monospace";
|
||||
//"lng_menu_formatting_link" = "Create link";
|
||||
"lng_menu_formatting_link_create" = "Create link";
|
||||
"lng_menu_formatting_link_edit" = "Edit link";
|
||||
"lng_menu_formatting_clear" = "Plain text";
|
||||
"lng_formatting_link_create_title" = "Create link";
|
||||
"lng_formatting_link_edit_title" = "Create link";
|
||||
"lng_formatting_link_text" = "Text";
|
||||
"lng_formatting_link_url" = "URL";
|
||||
"lng_formatting_link_create" = "Create";
|
||||
|
||||
"lng_full_name" = "{first_name} {last_name}";
|
||||
|
||||
|
@ -782,3 +782,5 @@ proxyDropdownUpPosition: point(-2px, 20px);
|
||||
|
||||
proxyAboutPadding: margins(22px, 7px, 22px, 14px);
|
||||
proxyAboutSponsorPadding: margins(22px, 7px, 22px, 0px);
|
||||
|
||||
markdownLinkFieldPadding: margins(22px, 0px, 22px, 10px);
|
||||
|
@ -25,8 +25,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
EditCaptionBox::EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<HistoryItem*> item)
|
||||
: _msgId(item->fullId()) {
|
||||
: _controller(controller)
|
||||
, _msgId(item->fullId()) {
|
||||
Expects(item->media() != nullptr);
|
||||
Expects(item->media()->allowsEditCaption());
|
||||
|
||||
@ -146,6 +148,8 @@ EditCaptionBox::EditCaptionBox(
|
||||
_field->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
_field->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
|
||||
_field->setMarkdownReplacesEnabled(rpl::single(true));
|
||||
_field->setEditLinkCallback(
|
||||
DefaultEditLinkCallback(_controller, _field));
|
||||
}
|
||||
|
||||
void EditCaptionBox::prepareGifPreview(DocumentData *document) {
|
||||
|
@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "boxes/abstract_box.h"
|
||||
|
||||
namespace Window {
|
||||
class Controller;
|
||||
} // namespace Window
|
||||
|
||||
namespace Data {
|
||||
class Media;
|
||||
} // namespace Data
|
||||
@ -19,7 +23,10 @@ class InputField;
|
||||
|
||||
class EditCaptionBox : public BoxContent, public RPCSender {
|
||||
public:
|
||||
EditCaptionBox(QWidget*, not_null<HistoryItem*> item);
|
||||
EditCaptionBox(
|
||||
QWidget*,
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<HistoryItem*> item);
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
@ -41,6 +48,7 @@ private:
|
||||
|
||||
int errorTopSkip() const;
|
||||
|
||||
not_null<Window::Controller*> _controller;
|
||||
FullMsgId _msgId;
|
||||
bool _animated = false;
|
||||
bool _photo = false;
|
||||
|
@ -1319,10 +1319,12 @@ void SendFilesBox::AlbumPreview::mouseReleaseEvent(QMouseEvent *e) {
|
||||
|
||||
SendFilesBox::SendFilesBox(
|
||||
QWidget*,
|
||||
not_null<Window::Controller*> controller,
|
||||
Storage::PreparedList &&list,
|
||||
const TextWithTags &caption,
|
||||
CompressConfirm compressed)
|
||||
: _list(std::move(list))
|
||||
: _controller(controller)
|
||||
, _list(std::move(list))
|
||||
, _compressConfirmInitial(compressed)
|
||||
, _compressConfirm(compressed)
|
||||
, _caption(
|
||||
@ -1579,6 +1581,8 @@ void SendFilesBox::setupCaption() {
|
||||
_caption->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
_caption->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
|
||||
_caption->setMarkdownReplacesEnabled(rpl::single(true));
|
||||
_caption->setEditLinkCallback(
|
||||
DefaultEditLinkCallback(_controller, _caption));
|
||||
}
|
||||
|
||||
void SendFilesBox::captionResized() {
|
||||
|
@ -12,6 +12,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "storage/localimageloader.h"
|
||||
#include "storage/storage_media_prepare.h"
|
||||
|
||||
namespace Window {
|
||||
class Controller;
|
||||
} // namespace Window
|
||||
|
||||
namespace Ui {
|
||||
template <typename Enum>
|
||||
class Radioenum;
|
||||
@ -32,6 +36,7 @@ class SendFilesBox : public BoxContent {
|
||||
public:
|
||||
SendFilesBox(
|
||||
QWidget*,
|
||||
not_null<Window::Controller*> controller,
|
||||
Storage::PreparedList &&list,
|
||||
const TextWithTags &caption,
|
||||
CompressConfirm compressed);
|
||||
@ -88,6 +93,8 @@ private:
|
||||
bool canAddUrls(const QList<QUrl> &urls) const;
|
||||
bool addFiles(not_null<const QMimeData*> data);
|
||||
|
||||
not_null<Window::Controller*> _controller;
|
||||
|
||||
QString _titleText;
|
||||
int _titleHeight = 0;
|
||||
|
||||
|
@ -399,11 +399,11 @@ QString SuggestionsController::getEmojiQuery() {
|
||||
}
|
||||
|
||||
auto cursor = _field->textCursor();
|
||||
auto position = _field->textCursor().position();
|
||||
if (cursor.anchor() != position) {
|
||||
if (cursor.hasSelection()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
auto position = cursor.position();
|
||||
auto findTextPart = [this, &position] {
|
||||
auto document = _field->document();
|
||||
auto block = document->findBlock(position);
|
||||
|
@ -9,14 +9,26 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "history/history_widget.h"
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "boxes/abstract_box.h"
|
||||
#include "ui/wrap/vertical_layout.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "mainwindow.h"
|
||||
#include "auth_session.h"
|
||||
#include "styles/style_boxes.h"
|
||||
#include "styles/style_history.h"
|
||||
|
||||
namespace {
|
||||
|
||||
using EditLinkAction = Ui::InputField::EditLinkAction;
|
||||
using EditLinkSelection = Ui::InputField::EditLinkSelection;
|
||||
|
||||
constexpr auto kParseLinksTimeout = TimeMs(1000);
|
||||
const auto kMentionTagStart = qstr("mention://user.");
|
||||
|
||||
bool IsMentionLink(const QString &link) {
|
||||
return link.startsWith(kMentionTagStart);
|
||||
}
|
||||
|
||||
// For mention tags save and validate userId, ignore tags for different userId.
|
||||
class FieldTagMimeProcessor : public Ui::InputField::TagMimeProcessor {
|
||||
@ -26,7 +38,7 @@ public:
|
||||
}
|
||||
|
||||
QString tagFromMimeTag(const QString &mimeTag) override {
|
||||
if (mimeTag.startsWith(qstr("mention://"))) {
|
||||
if (IsMentionLink(mimeTag)) {
|
||||
auto match = QRegularExpression(":(\\d+)$").match(mimeTag);
|
||||
if (!match.hasMatch()
|
||||
|| match.capturedRef(1).toInt() != Auth().userId()) {
|
||||
@ -39,15 +51,167 @@ public:
|
||||
|
||||
};
|
||||
|
||||
class EditLinkBox : public BoxContent {
|
||||
public:
|
||||
EditLinkBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &link,
|
||||
base::lambda<void(QString, QString)> callback);
|
||||
|
||||
void setInnerFocus() override;
|
||||
|
||||
protected:
|
||||
void prepare() override;
|
||||
|
||||
private:
|
||||
QString _startText;
|
||||
QString _startLink;
|
||||
base::lambda<void(QString, QString)> _callback;
|
||||
base::lambda<void()> _setInnerFocus;
|
||||
|
||||
};
|
||||
|
||||
QRegularExpression RegExpProtocol() {
|
||||
static const auto result = QRegularExpression("^([a-zA-Z]+)://");
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsGoodProtocol(const QString &protocol) {
|
||||
const auto equals = [&](QLatin1String string) {
|
||||
return protocol.compare(string, Qt::CaseInsensitive) == 0;
|
||||
};
|
||||
return equals(qstr("http"))
|
||||
|| equals(qstr("https"))
|
||||
|| equals(qstr("tg"));
|
||||
}
|
||||
|
||||
QString NormalizeUrl(const QString &value) {
|
||||
const auto trimmed = value.trimmed();
|
||||
if (trimmed.isEmpty()) {
|
||||
return QString();
|
||||
}
|
||||
const auto match = TextUtilities::RegExpDomainExplicit().match(trimmed);
|
||||
if (!match.hasMatch()) {
|
||||
const auto domain = TextUtilities::RegExpDomain().match(trimmed);
|
||||
if (!domain.hasMatch() || domain.capturedStart() != 0) {
|
||||
return QString();
|
||||
}
|
||||
return qstr("http://") + trimmed;
|
||||
} else if (match.capturedStart() != 0) {
|
||||
return QString();
|
||||
}
|
||||
const auto protocolMatch = RegExpProtocol().match(trimmed);
|
||||
Assert(protocolMatch.hasMatch());
|
||||
return IsGoodProtocol(protocolMatch.captured(1)) ? trimmed : QString();
|
||||
}
|
||||
|
||||
//bool ValidateUrl(const QString &value) {
|
||||
// const auto match = TextUtilities::RegExpDomain().match(value);
|
||||
// if (!match.hasMatch() || match.capturedStart() != 0) {
|
||||
// return false;
|
||||
// }
|
||||
// const auto protocolMatch = RegExpProtocol().match(value);
|
||||
// return protocolMatch.hasMatch()
|
||||
// && IsGoodProtocol(protocolMatch.captured(1));
|
||||
//}
|
||||
|
||||
EditLinkBox::EditLinkBox(
|
||||
QWidget*,
|
||||
const QString &text,
|
||||
const QString &link,
|
||||
base::lambda<void(QString, QString)> callback)
|
||||
: _startText(text)
|
||||
, _startLink(link)
|
||||
, _callback(std::move(callback)) {
|
||||
Expects(_callback != nullptr);
|
||||
}
|
||||
|
||||
void EditLinkBox::setInnerFocus() {
|
||||
Expects(_setInnerFocus != nullptr);
|
||||
|
||||
_setInnerFocus();
|
||||
}
|
||||
|
||||
void EditLinkBox::prepare() {
|
||||
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
|
||||
|
||||
const auto text = content->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
content,
|
||||
st::defaultInputField,
|
||||
langFactory(lng_formatting_link_text),
|
||||
_startText),
|
||||
st::markdownLinkFieldPadding);
|
||||
text->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
text->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
|
||||
|
||||
const auto url = content->add(
|
||||
object_ptr<Ui::InputField>(
|
||||
content,
|
||||
st::defaultInputField,
|
||||
langFactory(lng_formatting_link_url),
|
||||
_startLink.trimmed()),
|
||||
st::markdownLinkFieldPadding);
|
||||
|
||||
const auto submit = [=] {
|
||||
const auto linkText = text->getLastText();
|
||||
const auto linkUrl = NormalizeUrl(url->getLastText());
|
||||
if (linkText.isEmpty()) {
|
||||
text->showError();
|
||||
return;
|
||||
} else if (linkUrl.isEmpty()) {
|
||||
url->showError();
|
||||
return;
|
||||
}
|
||||
const auto weak = make_weak(this);
|
||||
_callback(linkText, linkUrl);
|
||||
if (weak) {
|
||||
closeBox();
|
||||
}
|
||||
};
|
||||
|
||||
connect(text, &Ui::InputField::submitted, [=] {
|
||||
url->setFocusFast();
|
||||
});
|
||||
connect(url, &Ui::InputField::submitted, [=] {
|
||||
if (text->getLastText().isEmpty()) {
|
||||
text->setFocusFast();
|
||||
} else {
|
||||
submit();
|
||||
}
|
||||
});
|
||||
|
||||
setTitle(langFactory(lng_formatting_link_create_title));
|
||||
|
||||
addButton(langFactory(lng_formatting_link_create), submit);
|
||||
addButton(langFactory(lng_cancel), [=] { closeBox(); });
|
||||
|
||||
content->resizeToWidth(st::boxWidth);
|
||||
content->moveToLeft(0, 0);
|
||||
setDimensions(st::boxWidth, content->height());
|
||||
|
||||
_setInnerFocus = [=] {
|
||||
(_startText.isEmpty() ? text : url)->setFocusFast();
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QString ConvertTagToMimeTag(const QString &tagId) {
|
||||
if (tagId.startsWith(qstr("mention://"))) {
|
||||
if (IsMentionLink(tagId)) {
|
||||
return tagId + ':' + QString::number(Auth().userId());
|
||||
}
|
||||
return tagId;
|
||||
}
|
||||
|
||||
QString PrepareMentionTag(not_null<UserData*> user) {
|
||||
return kMentionTagStart
|
||||
+ QString::number(user->bareId())
|
||||
+ '.'
|
||||
+ QString::number(user->accessHash());
|
||||
}
|
||||
|
||||
EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
||||
EntitiesInText result;
|
||||
if (tags.isEmpty()) {
|
||||
@ -55,7 +219,6 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
||||
}
|
||||
|
||||
result.reserve(tags.size());
|
||||
auto mentionStart = qstr("mention://user.");
|
||||
for (const auto &tag : tags) {
|
||||
const auto push = [&](
|
||||
EntityInTextType type,
|
||||
@ -63,8 +226,8 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
||||
result.push_back(
|
||||
EntityInText(type, tag.offset, tag.length, data));
|
||||
};
|
||||
if (tag.id.startsWith(mentionStart)) {
|
||||
if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(mentionStart.size()))) {
|
||||
if (IsMentionLink(tag.id)) {
|
||||
if (auto match = qthelp::regex_match("^(\\d+\\.\\d+)(/|$)", tag.id.midRef(kMentionTagStart.size()))) {
|
||||
push(EntityInTextMentionName, match->captured(1));
|
||||
}
|
||||
} else if (tag.id == Ui::InputField::kTagBold) {
|
||||
@ -75,6 +238,8 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
||||
push(EntityInTextCode);
|
||||
} else if (tag.id == Ui::InputField::kTagPre) {
|
||||
push(EntityInTextPre);
|
||||
} else /*if (ValidateUrl(tag.id)) */{ // We validate when we insert.
|
||||
push(EntityInTextCustomUrl, tag.id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@ -95,7 +260,14 @@ TextWithTags::Tags ConvertEntitiesToTextTags(const EntitiesInText &entities) {
|
||||
case EntityInTextMentionName: {
|
||||
auto match = QRegularExpression("^(\\d+\\.\\d+)$").match(entity.data());
|
||||
if (match.hasMatch()) {
|
||||
push(qstr("mention://user.") + entity.data());
|
||||
push(kMentionTagStart + entity.data());
|
||||
}
|
||||
} break;
|
||||
case EntityInTextCustomUrl: {
|
||||
const auto url = entity.data();
|
||||
if (Ui::InputField::IsValidMarkdownLink(url)
|
||||
&& !IsMentionLink(url)) {
|
||||
push(url);
|
||||
}
|
||||
} break;
|
||||
case EntityInTextBold: push(Ui::InputField::kTagBold); break;
|
||||
@ -135,7 +307,38 @@ void SetClipboardWithEntities(
|
||||
}
|
||||
}
|
||||
|
||||
void InitMessageField(not_null<Ui::InputField*> field) {
|
||||
base::lambda<bool(
|
||||
Ui::InputField::EditLinkSelection selection,
|
||||
QString text,
|
||||
QString link,
|
||||
EditLinkAction action)> DefaultEditLinkCallback(
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<Ui::InputField*> field) {
|
||||
const auto weak = make_weak(field);
|
||||
return [=](
|
||||
EditLinkSelection selection,
|
||||
QString text,
|
||||
QString link,
|
||||
EditLinkAction action) {
|
||||
if (action == EditLinkAction::Check) {
|
||||
return Ui::InputField::IsValidMarkdownLink(link)
|
||||
&& !IsMentionLink(link);
|
||||
}
|
||||
Ui::show(Box<EditLinkBox>(text, link, [=](
|
||||
const QString &text,
|
||||
const QString &link) {
|
||||
if (const auto strong = weak.data()) {
|
||||
strong->commitMarkdownLinkEdit(selection, text, link);
|
||||
}
|
||||
}), LayerOption::KeepOther);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
void InitMessageField(
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<Ui::InputField*> field) {
|
||||
field->setMinHeight(st::historySendSize.height() - 2 * st::historySendPadding);
|
||||
field->setMaxHeight(st::historyComposeFieldMaxHeight);
|
||||
|
||||
@ -148,6 +351,8 @@ void InitMessageField(not_null<Ui::InputField*> field) {
|
||||
field->setInstantReplaces(Ui::InstantReplaces::Default());
|
||||
field->setInstantReplacesEnabled(Global::ReplaceEmojiValue());
|
||||
field->setMarkdownReplacesEnabled(rpl::single(true));
|
||||
field->setEditLinkCallback(
|
||||
DefaultEditLinkCallback(controller, field));
|
||||
}
|
||||
|
||||
bool HasSendText(not_null<const Ui::InputField*> field) {
|
||||
@ -237,11 +442,11 @@ AutocompleteQuery ParseMentionHashtagBotCommandQuery(
|
||||
auto result = AutocompleteQuery();
|
||||
|
||||
const auto cursor = field->textCursor();
|
||||
const auto position = cursor.position();
|
||||
if (cursor.anchor() != position) {
|
||||
if (cursor.hasSelection()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto position = cursor.position();
|
||||
const auto document = field->document();
|
||||
const auto block = document->findBlock(position);
|
||||
for (auto item = block.begin(); !item.atEnd(); ++item) {
|
||||
@ -363,13 +568,43 @@ const rpl::variable<QStringList> &MessageLinksParser::list() const {
|
||||
}
|
||||
|
||||
void MessageLinksParser::parse() {
|
||||
const auto &text = _field->getTextWithTags().text;
|
||||
const auto &textWithTags = _field->getTextWithTags();
|
||||
const auto &text = textWithTags.text;
|
||||
const auto &tags = textWithTags.tags;
|
||||
if (text.isEmpty()) {
|
||||
_list = QStringList();
|
||||
return;
|
||||
}
|
||||
|
||||
auto ranges = QVector<LinkRange>();
|
||||
|
||||
const auto tagsBegin = tags.begin();
|
||||
const auto tagsEnd = tags.end();
|
||||
auto tag = tagsBegin;
|
||||
const auto processTag = [&] {
|
||||
Expects(tag != tagsEnd);
|
||||
|
||||
if (Ui::InputField::IsValidMarkdownLink(tag->id)
|
||||
&& !IsMentionLink(tag->id)) {
|
||||
ranges.push_back({ tag->offset, tag->length, tag->id });
|
||||
}
|
||||
++tag;
|
||||
};
|
||||
const auto processTagsBefore = [&](int offset) {
|
||||
while (tag != tagsEnd && tag->offset + tag->length <= offset) {
|
||||
processTag();
|
||||
}
|
||||
};
|
||||
const auto hasTagsIntersection = [&](int till) {
|
||||
if (tag == tagsEnd || tag->offset >= till) {
|
||||
return false;
|
||||
}
|
||||
while (tag != tagsEnd && tag->offset < till) {
|
||||
processTag();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const auto len = text.size();
|
||||
const QChar *start = text.unicode(), *end = start + text.size();
|
||||
for (auto offset = 0, matchOffset = offset; offset < len;) {
|
||||
@ -429,7 +664,15 @@ void MessageLinksParser::parse() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ranges.push_back({ domainOffset, static_cast<int>(p - start - domainOffset) });
|
||||
const auto range = LinkRange {
|
||||
domainOffset,
|
||||
static_cast<int>(p - start - domainOffset),
|
||||
QString()
|
||||
};
|
||||
processTagsBefore(domainOffset);
|
||||
if (!hasTagsIntersection(range.start + range.length)) {
|
||||
ranges.push_back(range);
|
||||
}
|
||||
offset = matchOffset = p - start;
|
||||
}
|
||||
|
||||
@ -441,13 +684,17 @@ void MessageLinksParser::apply(
|
||||
const QVector<LinkRange> &ranges) {
|
||||
const auto count = int(ranges.size());
|
||||
const auto current = _list.current();
|
||||
const auto computeLink = [&](const LinkRange &range) {
|
||||
return range.custom.isEmpty()
|
||||
? text.midRef(range.start, range.length)
|
||||
: range.custom.midRef(0);
|
||||
};
|
||||
const auto changed = [&] {
|
||||
if (current.size() != count) {
|
||||
return true;
|
||||
}
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
const auto &range = ranges[i];
|
||||
if (text.midRef(range.start, range.length) != current[i]) {
|
||||
if (computeLink(ranges[i]) != current[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -459,7 +706,7 @@ void MessageLinksParser::apply(
|
||||
auto parsed = QStringList();
|
||||
parsed.reserve(count);
|
||||
for (const auto &range : ranges) {
|
||||
parsed.push_back(text.mid(range.start, range.length));
|
||||
parsed.push_back(computeLink(range).toString());
|
||||
}
|
||||
_list = std::move(parsed);
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ class Controller;
|
||||
} // namespace Window
|
||||
|
||||
QString ConvertTagToMimeTag(const QString &tagId);
|
||||
QString PrepareMentionTag(not_null<UserData*> user);
|
||||
|
||||
EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags);
|
||||
TextWithTags::Tags ConvertEntitiesToTextTags(
|
||||
@ -26,7 +27,16 @@ void SetClipboardWithEntities(
|
||||
const TextWithEntities &forClipboard,
|
||||
QClipboard::Mode mode = QClipboard::Clipboard);
|
||||
|
||||
void InitMessageField(not_null<Ui::InputField*> field);
|
||||
base::lambda<bool(
|
||||
Ui::InputField::EditLinkSelection selection,
|
||||
QString text,
|
||||
QString link,
|
||||
Ui::InputField::EditLinkAction action)> DefaultEditLinkCallback(
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<Ui::InputField*> field);
|
||||
void InitMessageField(
|
||||
not_null<Window::Controller*> controller,
|
||||
not_null<Ui::InputField*> field);
|
||||
bool HasSendText(not_null<const Ui::InputField*> field);
|
||||
|
||||
struct InlineBotQuery {
|
||||
@ -71,9 +81,12 @@ private:
|
||||
struct LinkRange {
|
||||
int start;
|
||||
int length;
|
||||
QString custom;
|
||||
};
|
||||
friend inline bool operator==(const LinkRange &a, const LinkRange &b) {
|
||||
return (a.start == b.start) && (a.length == b.length);
|
||||
return (a.start == b.start)
|
||||
&& (a.length == b.length)
|
||||
&& (a.custom == b.custom);
|
||||
}
|
||||
friend inline bool operator!=(const LinkRange &a, const LinkRange &b) {
|
||||
return !(a == b);
|
||||
|
@ -513,7 +513,7 @@ HistoryWidget::HistoryWidget(
|
||||
_historyDown->installEventFilter(this);
|
||||
_unreadMentions->installEventFilter(this);
|
||||
|
||||
InitMessageField(_field);
|
||||
InitMessageField(controller, _field);
|
||||
_fieldAutocomplete->hide();
|
||||
connect(_fieldAutocomplete, SIGNAL(mentionChosen(UserData*,FieldAutocomplete::ChooseMethod)), this, SLOT(onMentionInsert(UserData*)));
|
||||
connect(_fieldAutocomplete, SIGNAL(hashtagChosen(QString,FieldAutocomplete::ChooseMethod)), this, SLOT(onHashtagOrBotCommandInsert(QString,FieldAutocomplete::ChooseMethod)));
|
||||
@ -997,10 +997,7 @@ void HistoryWidget::onMentionInsert(UserData *user) {
|
||||
if (replacement.isEmpty()) {
|
||||
replacement = App::peerName(user);
|
||||
}
|
||||
entityTag = qsl("mention://user.")
|
||||
+ QString::number(user->bareId())
|
||||
+ '.'
|
||||
+ QString::number(user->accessHash());
|
||||
entityTag = PrepareMentionTag(user);
|
||||
} else {
|
||||
replacement = '@' + user->username;
|
||||
}
|
||||
@ -1172,8 +1169,8 @@ void HistoryWidget::onDraftSaveDelayed() {
|
||||
if (!_peer || !(_textUpdateEvents & TextUpdateEvent::SaveDraft)) {
|
||||
return;
|
||||
}
|
||||
if (!_field->textCursor().anchor()
|
||||
&& !_field->textCursor().position()
|
||||
if (!_field->textCursor().position()
|
||||
&& !_field->textCursor().anchor()
|
||||
&& !_field->scrollTop().current()) {
|
||||
if (!Local::hasDraftCursors(_peer->id)) {
|
||||
return;
|
||||
@ -4168,6 +4165,7 @@ bool HistoryWidget::confirmSendingFiles(
|
||||
const auto anchor = cursor.anchor();
|
||||
const auto text = _field->getTextWithTags();
|
||||
auto box = Box<SendFilesBox>(
|
||||
controller(),
|
||||
std::move(list),
|
||||
text,
|
||||
boxCompressConfirm);
|
||||
@ -5846,7 +5844,7 @@ void HistoryWidget::editMessage(FullMsgId itemId) {
|
||||
void HistoryWidget::editMessage(not_null<HistoryItem*> item) {
|
||||
if (const auto media = item->media()) {
|
||||
if (media->allowsEditCaption()) {
|
||||
Ui::show(Box<EditCaptionBox>(item));
|
||||
Ui::show(Box<EditCaptionBox>(controller(), item));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -1534,7 +1534,8 @@ MTPVector<MTPMessageEntity> EntitiesToMTP(const EntitiesInText &entities, Conver
|
||||
&& entity.type() != EntityInTextItalic
|
||||
&& entity.type() != EntityInTextCode
|
||||
&& entity.type() != EntityInTextPre
|
||||
&& entity.type() != EntityInTextMentionName) {
|
||||
&& entity.type() != EntityInTextMentionName
|
||||
&& entity.type() != EntityInTextCustomUrl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ const auto kNewlineChars = QString("\r\n")
|
||||
+ QChar(QChar::LineSeparator);
|
||||
const auto kClearFormatSequence = QKeySequence("ctrl+shift+n");
|
||||
const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
|
||||
const auto kEditLinkSequence = QKeySequence("ctrl+k");
|
||||
|
||||
bool IsNewline(QChar ch) {
|
||||
return (kNewlineChars.indexOf(ch) >= 0);
|
||||
@ -84,36 +85,21 @@ public:
|
||||
}
|
||||
|
||||
if (!_currentTagId.isEmpty()) {
|
||||
const auto randomPartPosition = _currentTagId.lastIndexOf('/');
|
||||
const auto tagId = _currentTagId.midRef(
|
||||
0,
|
||||
(randomPartPosition > 0
|
||||
? randomPartPosition
|
||||
: _currentTagId.size()));
|
||||
|
||||
bool tagChanged = true;
|
||||
if (_currentTag < _tags.size()) {
|
||||
auto &alreadyTag = _tags[_currentTag];
|
||||
if (alreadyTag.offset == _currentStart &&
|
||||
alreadyTag.length == currentPosition - _currentStart &&
|
||||
alreadyTag.id == tagId) {
|
||||
tagChanged = false;
|
||||
}
|
||||
}
|
||||
if (tagChanged) {
|
||||
_changed = true;
|
||||
const auto tag = TextWithTags::Tag {
|
||||
_currentStart,
|
||||
currentPosition - _currentStart,
|
||||
tagId.toString()
|
||||
};
|
||||
if (_currentTag < _tags.size()) {
|
||||
_tags[_currentTag] = tag;
|
||||
} else {
|
||||
const auto tag = TextWithTags::Tag {
|
||||
_currentStart,
|
||||
currentPosition - _currentStart,
|
||||
_currentTagId
|
||||
};
|
||||
if (tag.length > 0) {
|
||||
if (_currentTag >= _tags.size()) {
|
||||
_changed = true;
|
||||
_tags.push_back(tag);
|
||||
} else if (_tags[_currentTag] != tag) {
|
||||
_changed = true;
|
||||
_tags[_currentTag] = tag;
|
||||
}
|
||||
++_currentTag;
|
||||
}
|
||||
++_currentTag;
|
||||
}
|
||||
_currentTagId = randomTagId;
|
||||
_currentStart = currentPosition;
|
||||
@ -523,12 +509,15 @@ style::font AdjustFont(
|
||||
: font;
|
||||
}
|
||||
|
||||
bool IsValidMarkdownLink(const QString &link) {
|
||||
return (link.indexOf('.') >= 0) || (link.indexOf(':') >= 0);
|
||||
}
|
||||
|
||||
QTextCharFormat PrepareTagFormat(
|
||||
const style::InputField &st,
|
||||
QString tag) {
|
||||
auto result = QTextCharFormat();
|
||||
if (tag.indexOf(':') >= 0) {
|
||||
tag += '/' + QString::number(rand_value<uint32>());
|
||||
if (IsValidMarkdownLink(tag)) {
|
||||
result.setForeground(st::defaultTextPalette.linkFg);
|
||||
result.setFont(st.font);
|
||||
} else if (tag == kTagBold) {
|
||||
@ -1614,7 +1603,6 @@ QString InputField::getTextPart(
|
||||
end = possibleLength;
|
||||
}
|
||||
|
||||
bool tillFragmentEnd = full;
|
||||
for (auto block = from; block != till;) {
|
||||
for (auto item = block.begin(); !item.atEnd(); ++item) {
|
||||
const auto fragment = item.fragment();
|
||||
@ -1628,7 +1616,6 @@ QString InputField::getTextPart(
|
||||
: (fragmentPosition + fragment.length());
|
||||
const auto format = fragment.charFormat();
|
||||
if (!full) {
|
||||
tillFragmentEnd = (fragmentEnd <= end);
|
||||
if (fragmentPosition == end) {
|
||||
tagAccumulator.feed(
|
||||
format.property(kTagProperty).toString(),
|
||||
@ -1662,7 +1649,7 @@ QString InputField::getTextPart(
|
||||
return result;
|
||||
}();
|
||||
|
||||
if (full || fragmentPosition >= start) {
|
||||
if (full || !text.isEmpty()) {
|
||||
lastTag = format.property(kTagProperty).toString();
|
||||
tagAccumulator.feed(lastTag, result.size());
|
||||
possibleTagAccumulator.feed(text, lastTag);
|
||||
@ -1700,9 +1687,7 @@ QString InputField::getTextPart(
|
||||
}
|
||||
}
|
||||
|
||||
if (tillFragmentEnd) {
|
||||
tagAccumulator.feed(QString(), result.size());
|
||||
}
|
||||
tagAccumulator.feed(QString(), result.size());
|
||||
tagAccumulator.finish();
|
||||
possibleTagAccumulator.finish();
|
||||
|
||||
@ -2301,6 +2286,13 @@ void InputField::keyPressEventInner(QKeyEvent *e) {
|
||||
}
|
||||
}
|
||||
|
||||
TextWithTags InputField::getTextWithTagsSelected() const {
|
||||
const auto cursor = textCursor();
|
||||
const auto start = cursor.selectionStart();
|
||||
const auto end = cursor.selectionEnd();
|
||||
return (end > start) ? getTextWithTagsPart(start, end) : TextWithTags();
|
||||
}
|
||||
|
||||
bool InputField::handleMarkdownKey(QKeyEvent *e) {
|
||||
if (!_markdownEnabled) {
|
||||
return false;
|
||||
@ -2319,12 +2311,152 @@ bool InputField::handleMarkdownKey(QKeyEvent *e) {
|
||||
toggleSelectionMarkdown(kTagCode);
|
||||
} else if (matches(kClearFormatSequence)) {
|
||||
clearSelectionMarkdown();
|
||||
} else if (matches(kEditLinkSequence) && _editLinkCallback) {
|
||||
const auto cursor = textCursor();
|
||||
editMarkdownLink({
|
||||
cursor.selectionStart(),
|
||||
cursor.selectionEnd()
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto InputField::selectionEditLinkData(EditLinkSelection selection) const
|
||||
-> EditLinkData {
|
||||
Expects(_editLinkCallback != nullptr);
|
||||
|
||||
const auto position = (selection.from == selection.till
|
||||
&& selection.from > 0)
|
||||
? (selection.from - 1)
|
||||
: selection.from;
|
||||
const auto link = [&] {
|
||||
return (position != selection.till)
|
||||
? GetFullSimpleTextTag(
|
||||
getTextWithTagsPart(position, selection.till))
|
||||
: QString();
|
||||
}();
|
||||
const auto simple = EditLinkData {
|
||||
selection.from,
|
||||
selection.till,
|
||||
QString()
|
||||
};
|
||||
if (!_editLinkCallback(selection, {}, link, EditLinkAction::Check)) {
|
||||
return simple;
|
||||
}
|
||||
Assert(!link.isEmpty());
|
||||
|
||||
struct State {
|
||||
QTextBlock block;
|
||||
QTextBlock::iterator i;
|
||||
};
|
||||
const auto document = _inner->document();
|
||||
const auto skipInvalid = [&](State &state) {
|
||||
if (state.block == document->end()) {
|
||||
return false;
|
||||
}
|
||||
while (state.i.atEnd()) {
|
||||
state.block = state.block.next();
|
||||
if (state.block == document->end()) {
|
||||
return false;
|
||||
}
|
||||
state.i = state.block.begin();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const auto moveToNext = [&](State &state) {
|
||||
Expects(state.block != document->end());
|
||||
Expects(!state.i.atEnd());
|
||||
|
||||
++state.i;
|
||||
};
|
||||
const auto moveToPrevious = [&](State &state) {
|
||||
Expects(state.block != document->end());
|
||||
Expects(!state.i.atEnd());
|
||||
|
||||
while (state.i == state.block.begin()) {
|
||||
if (state.block == document->begin()) {
|
||||
state.block = document->end();
|
||||
return false;
|
||||
}
|
||||
state.block = state.block.previous();
|
||||
state.i = state.block.end();
|
||||
}
|
||||
--state.i;
|
||||
return true;
|
||||
};
|
||||
const auto stateTag = [&](const State &state) {
|
||||
const auto format = state.i.fragment().charFormat();
|
||||
return format.property(kTagProperty).toString();
|
||||
};
|
||||
const auto stateStart = [&](const State &state) {
|
||||
return state.i.fragment().position();
|
||||
};
|
||||
const auto stateEnd = [&](const State &state) {
|
||||
const auto fragment = state.i.fragment();
|
||||
return fragment.position() + fragment.length();
|
||||
};
|
||||
auto state = State{ document->findBlock(position) };
|
||||
if (state.block != document->end()) {
|
||||
state.i = state.block.begin();
|
||||
}
|
||||
for (; skipInvalid(state); moveToNext(state)) {
|
||||
const auto fragmentStart = stateStart(state);
|
||||
const auto fragmentEnd = stateEnd(state);
|
||||
if (fragmentEnd <= position) {
|
||||
continue;
|
||||
} else if (fragmentStart >= selection.till) {
|
||||
break;
|
||||
}
|
||||
if (stateTag(state) == link) {
|
||||
auto start = fragmentStart;
|
||||
auto finish = fragmentEnd;
|
||||
auto copy = state;
|
||||
while (moveToPrevious(copy) && (stateTag(copy) == link)) {
|
||||
start = stateStart(copy);
|
||||
}
|
||||
while (skipInvalid(state) && (stateTag(state) == link)) {
|
||||
finish = stateEnd(state);
|
||||
moveToNext(state);
|
||||
}
|
||||
return { start, finish, link };
|
||||
}
|
||||
}
|
||||
return simple;
|
||||
}
|
||||
|
||||
auto InputField::editLinkSelection(QContextMenuEvent *e) const
|
||||
-> EditLinkSelection {
|
||||
const auto cursor = textCursor();
|
||||
if (!cursor.hasSelection() && e->reason() == QContextMenuEvent::Mouse) {
|
||||
const auto clickCursor = _inner->cursorForPosition(
|
||||
_inner->viewport()->mapFromGlobal(e->globalPos()));
|
||||
if (!clickCursor.isNull() && !clickCursor.hasSelection()) {
|
||||
return {
|
||||
clickCursor.position(),
|
||||
clickCursor.position()
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
cursor.selectionStart(),
|
||||
cursor.selectionEnd()
|
||||
};
|
||||
}
|
||||
|
||||
void InputField::editMarkdownLink(EditLinkSelection selection) {
|
||||
if (!_editLinkCallback) {
|
||||
return;
|
||||
}
|
||||
const auto data = selectionEditLinkData(selection);
|
||||
_editLinkCallback(
|
||||
selection,
|
||||
getTextWithTagsPart(data.from, data.till).text,
|
||||
data.link,
|
||||
EditLinkAction::Edit);
|
||||
}
|
||||
|
||||
void InputField::inputMethodEventInner(QInputMethodEvent *e) {
|
||||
const auto preedit = e->preeditString();
|
||||
if (_lastPreEditText != preedit) {
|
||||
@ -2425,7 +2557,7 @@ void InputField::applyInstantReplace(
|
||||
const auto length = int(what.size());
|
||||
const auto cursor = textCursor();
|
||||
const auto position = cursor.position();
|
||||
if (cursor.anchor() != position) {
|
||||
if (cursor.hasSelection()) {
|
||||
return;
|
||||
} else if (position < length) {
|
||||
return;
|
||||
@ -2546,19 +2678,19 @@ bool InputField::commitMarkdownReplacement(
|
||||
+ outer.mid(innerLeft, innerLength).toString()
|
||||
+ (newlineright ? "\n" : "");
|
||||
|
||||
// Trim inserted tag, so that all spaces and newlines are left outside.
|
||||
// Trim inserted tag, so that all newlines are left outside.
|
||||
_insertedTags.clear();
|
||||
auto tagFrom = newlineleft ? 1 : 0;
|
||||
auto tagTill = insert.size() - (newlineright ? 1 : 0);
|
||||
for (; tagFrom != tagTill; ++tagFrom) {
|
||||
const auto ch = insert.at(tagFrom);
|
||||
if (!IsNewline(ch) && !chIsSpace(ch)) {
|
||||
if (!IsNewline(ch)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (; tagTill != tagFrom; --tagTill) {
|
||||
const auto ch = insert.at(tagTill - 1);
|
||||
if (!IsNewline(ch) && !chIsSpace(ch)) {
|
||||
if (!IsNewline(ch)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -2588,18 +2720,49 @@ bool InputField::commitMarkdownReplacement(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputField::IsValidMarkdownLink(const QString &link) {
|
||||
return ::Ui::IsValidMarkdownLink(link);
|
||||
}
|
||||
|
||||
void InputField::commitMarkdownLinkEdit(
|
||||
EditLinkSelection selection,
|
||||
const QString &text,
|
||||
const QString &link) {
|
||||
if (text.isEmpty()
|
||||
|| !IsValidMarkdownLink(link)
|
||||
|| !_editLinkCallback) {
|
||||
return;
|
||||
}
|
||||
_insertedTags.clear();
|
||||
_insertedTags.push_back({ 0, text.size(), link });
|
||||
|
||||
auto cursor = textCursor();
|
||||
const auto editData = selectionEditLinkData(selection);
|
||||
cursor.setPosition(editData.from);
|
||||
cursor.setPosition(editData.till, QTextCursor::KeepAnchor);
|
||||
auto format = _defaultCharFormat;
|
||||
_insertedTagsAreFromMime = false;
|
||||
cursor.insertText(
|
||||
(editData.from == editData.till) ? (text + QChar(' ')) : text,
|
||||
_defaultCharFormat);
|
||||
_insertedTags.clear();
|
||||
|
||||
_reverseMarkdownReplacement = false;
|
||||
cursor.setCharFormat(_defaultCharFormat);
|
||||
_inner->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
void InputField::toggleSelectionMarkdown(const QString &tag) {
|
||||
_reverseMarkdownReplacement = false;
|
||||
const auto cursor = textCursor();
|
||||
const auto anchor = cursor.anchor();
|
||||
const auto position = cursor.position();
|
||||
const auto from = std::min(anchor, position);
|
||||
const auto till = std::max(anchor, position);
|
||||
const auto from = cursor.selectionStart();
|
||||
const auto till = cursor.selectionEnd();
|
||||
if (from == till) {
|
||||
return;
|
||||
}
|
||||
if (tag.isEmpty()
|
||||
|| GetFullSimpleTextTag(getTextWithTagsPart(from, till)) == tag) {
|
||||
|| GetFullSimpleTextTag(getTextWithTagsSelected()) == tag) {
|
||||
RemoveDocumentTags(_st, document(), from, till);
|
||||
return;
|
||||
}
|
||||
@ -2626,7 +2789,7 @@ void InputField::toggleSelectionMarkdown(const QString &tag) {
|
||||
}();
|
||||
commitMarkdownReplacement(from, till, commitTag);
|
||||
auto restorePosition = textCursor();
|
||||
restorePosition.setPosition(anchor);
|
||||
restorePosition.setPosition((position == till) ? from : till);
|
||||
restorePosition.setPosition(position, QTextCursor::KeepAnchor);
|
||||
setTextCursor(restorePosition);
|
||||
}
|
||||
@ -2638,7 +2801,7 @@ void InputField::clearSelectionMarkdown() {
|
||||
bool InputField::revertFormatReplace() {
|
||||
const auto cursor = textCursor();
|
||||
const auto position = cursor.position();
|
||||
if (position <= 0 || cursor.anchor() != position) {
|
||||
if (position <= 0 || cursor.hasSelection()) {
|
||||
return false;
|
||||
}
|
||||
const auto inside = position - 1;
|
||||
@ -2726,13 +2889,15 @@ bool InputField::revertFormatReplace() {
|
||||
|
||||
void InputField::contextMenuEventInner(QContextMenuEvent *e) {
|
||||
if (const auto menu = _inner->createStandardContextMenu()) {
|
||||
addMarkdownActions(menu);
|
||||
addMarkdownActions(menu, e);
|
||||
_contextMenu = base::make_unique_q<Ui::PopupMenu>(nullptr, menu);
|
||||
_contextMenu->popup(e->globalPos());
|
||||
}
|
||||
}
|
||||
|
||||
void InputField::addMarkdownActions(not_null<QMenu*> menu) {
|
||||
void InputField::addMarkdownActions(
|
||||
not_null<QMenu*> menu,
|
||||
QContextMenuEvent *e) {
|
||||
if (!_markdownEnabled) {
|
||||
return;
|
||||
}
|
||||
@ -2742,17 +2907,16 @@ void InputField::addMarkdownActions(not_null<QMenu*> menu) {
|
||||
const auto submenu = new QMenu(menu);
|
||||
formatting->setMenu(submenu);
|
||||
|
||||
const auto cursor = textCursor();
|
||||
const auto from = std::min(cursor.anchor(), cursor.position());
|
||||
const auto till = std::max(cursor.anchor(), cursor.position());
|
||||
const auto textWithTags = getTextWithTagsPart(from, till);
|
||||
const auto textWithTags = getTextWithTagsSelected();
|
||||
const auto &text = textWithTags.text;
|
||||
const auto &tags = textWithTags.tags;
|
||||
formatting->setDisabled(text.isEmpty());
|
||||
if (text.isEmpty()) {
|
||||
const auto hasText = !text.isEmpty();
|
||||
const auto hasTags = !tags.isEmpty();
|
||||
const auto disabled = (!_editLinkCallback && !hasText);
|
||||
formatting->setDisabled(disabled);
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
const auto hasTags = !textWithTags.tags.isEmpty();
|
||||
const auto fullTag = GetFullSimpleTextTag(textWithTags);
|
||||
const auto add = [&](
|
||||
LangKey key,
|
||||
@ -2773,27 +2937,35 @@ void InputField::addMarkdownActions(not_null<QMenu*> menu) {
|
||||
const QString &tag) {
|
||||
const auto disabled = (fullTag == tag)
|
||||
|| (fullTag == kTagPre && tag == kTagCode);
|
||||
add(key, sequence, (fullTag == tag), [=] {
|
||||
add(key, sequence, (!hasText || fullTag == tag), [=] {
|
||||
toggleSelectionMarkdown(tag);
|
||||
});
|
||||
};
|
||||
//const auto addlink = [&] {
|
||||
// add(lng_menu_formatting_link, QKeySequence("ctrl+k"), false, [=] {
|
||||
// createMarkdownLink();
|
||||
// });
|
||||
//};
|
||||
const auto addlink = [&] {
|
||||
const auto selection = editLinkSelection(e);
|
||||
const auto data = selectionEditLinkData(selection);
|
||||
const auto key = data.link.isEmpty()
|
||||
? lng_menu_formatting_link_create
|
||||
: lng_menu_formatting_link_edit;
|
||||
add(key, kEditLinkSequence, false, [=] {
|
||||
editMarkdownLink(selection);
|
||||
});
|
||||
};
|
||||
const auto addclear = [&] {
|
||||
add(lng_menu_formatting_clear, kClearFormatSequence, !hasTags, [=] {
|
||||
const auto disabled = !hasText || !hasTags;
|
||||
add(lng_menu_formatting_clear, kClearFormatSequence, disabled, [=] {
|
||||
clearSelectionMarkdown();
|
||||
});
|
||||
};
|
||||
|
||||
addtag(lng_menu_formatting_bold, QKeySequence::Bold, kTagBold);
|
||||
addtag(lng_menu_formatting_italic, QKeySequence::Italic, kTagItalic);
|
||||
|
||||
addtag(lng_menu_formatting_monospace, kMonospaceSequence, kTagCode);
|
||||
|
||||
//submenu->addSeparator();
|
||||
//addlink();
|
||||
if (_editLinkCallback) {
|
||||
submenu->addSeparator();
|
||||
addlink();
|
||||
}
|
||||
|
||||
submenu->addSeparator();
|
||||
addclear();
|
||||
@ -2853,7 +3025,7 @@ void InputField::insertFromMimeDataInner(const QMimeData *source) {
|
||||
_insertedTags.clear();
|
||||
}
|
||||
auto cursor = textCursor();
|
||||
_realInsertPosition = qMin(cursor.position(), cursor.anchor());
|
||||
_realInsertPosition = cursor.selectionStart();
|
||||
_realCharsAdded = text.size();
|
||||
_inner->QTextEdit::insertFromMimeData(source);
|
||||
if (!_inDrop) {
|
||||
@ -2899,6 +3071,15 @@ void InputField::setPlaceholder(
|
||||
refreshPlaceholder();
|
||||
}
|
||||
|
||||
void InputField::setEditLinkCallback(
|
||||
base::lambda<bool(
|
||||
EditLinkSelection selection,
|
||||
QString text,
|
||||
QString link,
|
||||
EditLinkAction action)> callback) {
|
||||
_editLinkCallback = std::move(callback);
|
||||
}
|
||||
|
||||
void InputField::showError() {
|
||||
setErrorShown(true);
|
||||
if (!hasFocus()) {
|
||||
|
@ -186,6 +186,21 @@ public:
|
||||
};
|
||||
void setTagMimeProcessor(std::unique_ptr<TagMimeProcessor> &&processor);
|
||||
|
||||
struct EditLinkSelection {
|
||||
int from = 0;
|
||||
int till = 0;
|
||||
};
|
||||
enum class EditLinkAction {
|
||||
Check,
|
||||
Edit,
|
||||
};
|
||||
void setEditLinkCallback(
|
||||
base::lambda<bool(
|
||||
EditLinkSelection selection,
|
||||
QString text,
|
||||
QString link,
|
||||
EditLinkAction action)> callback);
|
||||
|
||||
void setAdditionalMargin(int margin);
|
||||
|
||||
void setInstantReplaces(const InstantReplaces &replaces);
|
||||
@ -201,8 +216,13 @@ public:
|
||||
int till,
|
||||
const QString &tag,
|
||||
const QString &edge = QString());
|
||||
void commitMarkdownLinkEdit(
|
||||
EditLinkSelection selection,
|
||||
const QString &text,
|
||||
const QString &link);
|
||||
void toggleSelectionMarkdown(const QString &tag);
|
||||
void clearSelectionMarkdown();
|
||||
static bool IsValidMarkdownLink(const QString &link);
|
||||
|
||||
const QString &getLastText() const {
|
||||
return _lastTextWithTags.text;
|
||||
@ -322,6 +342,7 @@ private:
|
||||
QMimeData *createMimeDataFromSelectionInner() const;
|
||||
bool canInsertFromMimeDataInner(const QMimeData *source) const;
|
||||
void insertFromMimeDataInner(const QMimeData *source);
|
||||
TextWithTags getTextWithTagsSelected() const;
|
||||
|
||||
// "start" and "end" are in coordinates of text where emoji are replaced
|
||||
// by ObjectReplacementCharacter. If "end" = -1 means get text till the end.
|
||||
@ -345,7 +366,7 @@ private:
|
||||
|
||||
bool processMarkdownReplaces(const QString &appended);
|
||||
bool processMarkdownReplace(const QString &tag);
|
||||
void addMarkdownActions(not_null<QMenu*> menu);
|
||||
void addMarkdownActions(not_null<QMenu*> menu, QContextMenuEvent *e);
|
||||
void addMarkdownMenuAction(
|
||||
not_null<QMenu*> menu,
|
||||
not_null<QAction*> action);
|
||||
@ -357,6 +378,15 @@ private:
|
||||
void processInstantReplaces(const QString &appended);
|
||||
void applyInstantReplace(const QString &what, const QString &with);
|
||||
|
||||
struct EditLinkData {
|
||||
int from = 0;
|
||||
int till = 0;
|
||||
QString link;
|
||||
};
|
||||
EditLinkData selectionEditLinkData(EditLinkSelection selection) const;
|
||||
EditLinkSelection editLinkSelection(QContextMenuEvent *e) const;
|
||||
void editMarkdownLink(EditLinkSelection selection);
|
||||
|
||||
bool revertFormatReplace();
|
||||
|
||||
const style::InputField &_st;
|
||||
@ -373,6 +403,11 @@ private:
|
||||
TextWithTags _lastTextWithTags;
|
||||
std::vector<PossibleTag> _textAreaPossibleTags;
|
||||
QString _lastPreEditText;
|
||||
base::lambda<bool(
|
||||
EditLinkSelection selection,
|
||||
QString text,
|
||||
QString link,
|
||||
EditLinkAction action)> _editLinkCallback;
|
||||
|
||||
// Tags list which we should apply while setText() call or insert from mime data.
|
||||
TagList _insertedTags;
|
||||
|
Loading…
Reference in New Issue
Block a user