Support limited formatting input in factcheck.

This commit is contained in:
John Preston 2024-05-24 11:23:27 +04:00
parent a3ef36f9f7
commit 97a5e0c6ea
16 changed files with 151 additions and 106 deletions

View File

@ -1044,7 +1044,7 @@ not_null<Ui::InputField*> CreatePollBox::setupSolution(
solution->setInstantReplaces(Ui::InstantReplaces::Default());
solution->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
solution->setMarkdownReplacesEnabled(rpl::single(true));
solution->setMarkdownReplacesEnabled(true);
solution->setEditLinkCallback(
DefaultEditLinkCallback(_controller->uiShow(), solution));
solution->customTab(true);

View File

@ -60,60 +60,43 @@ constexpr auto kTypesDuration = 4 * crl::time(1000);
// For mention / custom emoji tags save and validate selfId,
// ignore tags for different users.
class FieldTagMimeProcessor final {
public:
FieldTagMimeProcessor(
not_null<Main::Session*> _session,
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji);
QString operator()(QStringView mimeTag);
private:
const not_null<Main::Session*> _session;
const Fn<bool(not_null<DocumentData*>)> _allowPremiumEmoji;
};
FieldTagMimeProcessor::FieldTagMimeProcessor(
not_null<Main::Session*> session,
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji)
: _session(session)
, _allowPremiumEmoji(allowPremiumEmoji) {
}
QString FieldTagMimeProcessor::operator()(QStringView mimeTag) {
const auto id = _session->userId().bare;
auto all = TextUtilities::SplitTags(mimeTag);
auto premiumSkipped = (DocumentData*)nullptr;
for (auto i = all.begin(); i != all.end();) {
const auto tag = *i;
if (TextUtilities::IsMentionLink(tag)
&& TextUtilities::MentionNameDataToFields(tag).selfId != id) {
i = all.erase(i);
continue;
} else if (Ui::InputField::IsCustomEmojiLink(tag)) {
const auto data = Ui::InputField::CustomEmojiEntityData(tag);
const auto emoji = Data::ParseCustomEmojiData(data);
if (!emoji) {
[[nodiscard]] Fn<QString(QStringView)> FieldTagMimeProcessor(
not_null<Main::Session*> session,
Fn<bool(not_null<DocumentData*>)> allowPremiumEmoji) {
return [=](QStringView mimeTag) {
const auto id = session->userId().bare;
auto all = TextUtilities::SplitTags(mimeTag);
auto premiumSkipped = (DocumentData*)nullptr;
for (auto i = all.begin(); i != all.end();) {
const auto tag = *i;
if (TextUtilities::IsMentionLink(tag)
&& TextUtilities::MentionNameDataToFields(tag).selfId != id) {
i = all.erase(i);
continue;
} else if (!_session->premium()) {
const auto document = _session->data().document(emoji);
if (document->isPremiumEmoji()) {
if (!_allowPremiumEmoji
|| premiumSkipped
|| !_session->premiumPossible()
|| !_allowPremiumEmoji(document)) {
premiumSkipped = document;
i = all.erase(i);
continue;
} else if (Ui::InputField::IsCustomEmojiLink(tag)) {
const auto data = Ui::InputField::CustomEmojiEntityData(tag);
const auto emoji = Data::ParseCustomEmojiData(data);
if (!emoji) {
i = all.erase(i);
continue;
} else if (!session->premium()) {
const auto document = session->data().document(emoji);
if (document->isPremiumEmoji()) {
if (!allowPremiumEmoji
|| premiumSkipped
|| !session->premiumPossible()
|| !allowPremiumEmoji(document)) {
premiumSkipped = document;
i = all.erase(i);
continue;
}
}
}
}
++i;
}
++i;
}
return TextUtilities::JoinTag(all);
return TextUtilities::JoinTag(all);
};
}
//bool ValidateUrl(const QString &value) {
@ -352,7 +335,7 @@ void InitMessageFieldHandlers(
field->setInstantReplaces(Ui::InstantReplaces::Default());
field->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
field->setMarkdownReplacesEnabled(rpl::single(true));
field->setMarkdownReplacesEnabled(true);
if (show) {
field->setEditLinkCallback(
DefaultEditLinkCallback(show, field, fieldStyle));
@ -360,6 +343,42 @@ void InitMessageFieldHandlers(
}
}
Fn<void(not_null<Ui::InputField*>)> FactcheckFieldIniter(
std::shared_ptr<Main::SessionShow> show) {
Expects(show != nullptr);
return [=](not_null<Ui::InputField*> field) {
field->setTagMimeProcessor([](QStringView mimeTag) {
using Field = Ui::InputField;
auto all = TextUtilities::SplitTags(mimeTag);
for (auto i = all.begin(); i != all.end();) {
const auto tag = *i;
if (tag != Field::kTagBold
&& tag != Field::kTagItalic
&& (!Field::IsValidMarkdownLink(mimeTag)
|| TextUtilities::IsMentionLink(mimeTag))) {
i = all.erase(i);
continue;
}
++i;
}
return TextUtilities::JoinTag(all);
});
field->setInstantReplaces(Ui::InstantReplaces::Default());
field->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
field->setMarkdownReplacesEnabled(rpl::single(
Ui::MarkdownEnabledState{
Ui::MarkdownEnabled{
{ Ui::InputField::kTagBold, Ui::InputField::kTagItalic }
}
}
));
field->setEditLinkCallback(DefaultEditLinkCallback(show, field));
InitSpellchecker(show, field);
};
}
void InitMessageFieldHandlers(
not_null<Window::SessionController*> controller,
not_null<Ui::InputField*> field,

View File

@ -77,6 +77,9 @@ void InitSpellchecker(
not_null<Ui::InputField*> field,
bool skipDictionariesManager = false);
[[nodiscard]] Fn<void(not_null<Ui::InputField*>)> FactcheckFieldIniter(
std::shared_ptr<Main::SessionShow> show);
bool HasSendText(not_null<const Ui::InputField*> field);
void InitMessageFieldFade(

View File

@ -199,18 +199,20 @@ void Factchecks::save(
void Factchecks::save(
FullMsgId itemId,
TextWithEntities was,
const TextWithEntities &was,
TextWithEntities text,
std::shared_ptr<Ui::Show> show) {
const auto done = [=](QString error) {
const auto wasEmpty = was.empty();
const auto textEmpty = text.empty();
save(itemId, std::move(text), [=](QString error) {
show->showToast(!error.isEmpty()
? error
: was.empty()
: wasEmpty
? tr::lng_factcheck_remove_done(tr::now)
: text.empty()
: textEmpty
? tr::lng_factcheck_add_done(tr::now)
: tr::lng_factcheck_edit_done(tr::now));
};
});
}
} // namespace Data

View File

@ -45,7 +45,7 @@ public:
Fn<void(QString)> done);
void save(
FullMsgId itemId,
TextWithEntities was,
const TextWithEntities &was,
TextWithEntities text,
std::shared_ptr<Ui::Show> show);

View File

@ -2184,7 +2184,7 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
TextWithEntities result) {
const auto show = controller->uiShow();
session->factchecks().save(itemId, text, result, show);
}));
}, FactcheckFieldIniter(controller->uiShow())));
}, &st::menuIconFactcheck);
}
const auto pinItem = (item->canPin() && item->isPinned())

View File

@ -69,6 +69,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_file_origin.h"
#include "data/data_message_reactions.h"
#include "data/stickers/data_custom_emoji.h"
#include "chat_helpers/message_field.h" // FactcheckFieldIniter.
#include "core/file_utilities.h"
#include "core/click_handler_types.h"
#include "base/platform/base_platform_info.h"
@ -736,7 +737,7 @@ void AddFactcheckAction(
TextWithEntities result) {
const auto show = controller->uiShow();
session->factchecks().save(itemId, text, result, show);
}));
}, FactcheckFieldIniter(controller->uiShow())));
}, &st::menuIconFactcheck);
}

View File

@ -448,7 +448,7 @@ void MainWindow::updateGlobalMenuHook() {
auto canSelectAll = false;
const auto mimeData = QGuiApplication::clipboard()->mimeData();
const auto clipboardHasText = mimeData ? mimeData->hasText() : false;
auto markdownEnabled = false;
auto markdownState = Ui::MarkdownEnabledState();
if (const auto edit = qobject_cast<QLineEdit*>(focused)) {
canCut = canCopy = canDelete = edit->hasSelectedText();
canSelectAll = !edit->text().isEmpty();
@ -464,7 +464,7 @@ void MainWindow::updateGlobalMenuHook() {
if (canCopy) {
if (const auto inputField = dynamic_cast<Ui::InputField*>(
focused->parentWidget())) {
markdownEnabled = inputField->isMarkdownEnabled();
markdownState = inputField->markdownEnabledState();
}
}
} else if (const auto list = dynamic_cast<HistoryInner*>(focused)) {
@ -489,13 +489,19 @@ void MainWindow::updateGlobalMenuHook() {
ForceDisabled(psNewGroup, inactive || support);
ForceDisabled(psNewChannel, inactive || support);
ForceDisabled(psBold, !markdownEnabled);
ForceDisabled(psItalic, !markdownEnabled);
ForceDisabled(psUnderline, !markdownEnabled);
ForceDisabled(psStrikeOut, !markdownEnabled);
ForceDisabled(psBlockquote, !markdownEnabled);
ForceDisabled(psMonospace, !markdownEnabled);
ForceDisabled(psClearFormat, !markdownEnabled);
const auto diabled = [=](const QString &tag) {
return !markdownState.enabledForTag(tag);
};
using Field = Ui::InputField;
ForceDisabled(psBold, diabled(Field::kTagBold));
ForceDisabled(psItalic, diabled(Field::kTagItalic));
ForceDisabled(psUnderline, diabled(Field::kTagUnderline));
ForceDisabled(psStrikeOut, diabled(Field::kTagStrikeOut));
ForceDisabled(psBlockquote, diabled(Field::kTagBlockquote));
ForceDisabled(
psMonospace,
diabled(Field::kTagPre) || diabled(Field::kTagCode));
ForceDisabled(psClearFormat, markdownState.disabled());
}
bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {

View File

@ -64,8 +64,6 @@ private:
base::Timer _hideAfterFullScreenTimer;
rpl::variable<bool> _canApplyMarkdown;
QMenuBar psMainMenu;
QAction *psLogout = nullptr;
QAction *psUndo = nullptr;

View File

@ -92,10 +92,11 @@ public:
void setNativeWindow(NSWindow *window, NSView *view);
void initTouchBar(
NSWindow *window,
not_null<Window::Controller*> controller,
rpl::producer<bool> canApplyMarkdown);
not_null<Window::Controller*> controller);
void setWindowBadge(const QString &str);
void setMarkdownEnabledState(Ui::MarkdownEnabledState state);
bool clipboardHasText();
~Private();
@ -103,6 +104,8 @@ private:
not_null<MainWindow*> _public;
friend class MainWindow;
rpl::variable<Ui::MarkdownEnabledState> _markdownState;
NSWindow * __weak _nativeWindow = nil;
NSView * __weak _nativeView = nil;
@ -229,8 +232,7 @@ void MainWindow::Private::setNativeWindow(NSWindow *window, NSView *view) {
void MainWindow::Private::initTouchBar(
NSWindow *window,
not_null<Window::Controller*> controller,
rpl::producer<bool> canApplyMarkdown) {
not_null<Window::Controller*> controller) {
if (!IsMac10_13OrGreater()) {
return;
}
@ -240,12 +242,17 @@ void MainWindow::Private::initTouchBar(
[window
performSelectorOnMainThread:@selector(setTouchBar:)
withObject:[[[RootTouchBar alloc]
init:std::move(canApplyMarkdown)
init:_markdownState.value()
controller:controller
domain:(&Core::App().domain())] autorelease]
waitUntilDone:true];
}
void MainWindow::Private::setMarkdownEnabledState(
Ui::MarkdownEnabledState state) {
_markdownState = state;
}
bool MainWindow::Private::clipboardHasText() {
auto currentChangeCount = static_cast<int>([_generalPasteboard changeCount]);
if (_generalPasteboardChangeCount != currentChangeCount) {
@ -289,10 +296,7 @@ void MainWindow::initHook() {
if (auto view = reinterpret_cast<NSView*>(winId())) {
if (auto window = [view window]) {
_private->setNativeWindow(window, view);
_private->initTouchBar(
window,
&controller(),
_canApplyMarkdown.changes());
_private->initTouchBar(window, &controller());
}
}
}
@ -558,7 +562,7 @@ void MainWindow::updateGlobalMenuHook() {
auto focused = QApplication::focusWidget();
bool canUndo = false, canRedo = false, canCut = false, canCopy = false, canPaste = false, canDelete = false, canSelectAll = false;
auto clipboardHasText = _private->clipboardHasText();
auto canApplyMarkdown = false;
auto markdownState = Ui::MarkdownEnabledState();
if (auto edit = qobject_cast<QLineEdit*>(focused)) {
canCut = canCopy = canDelete = edit->hasSelectedText();
canSelectAll = !edit->text().isEmpty();
@ -574,7 +578,7 @@ void MainWindow::updateGlobalMenuHook() {
if (canCopy) {
if (const auto inputField = dynamic_cast<Ui::InputField*>(
focused->parentWidget())) {
canApplyMarkdown = inputField->isMarkdownEnabled();
markdownState = inputField->markdownEnabledState();
}
}
} else if (auto list = dynamic_cast<HistoryInner*>(focused)) {
@ -582,7 +586,7 @@ void MainWindow::updateGlobalMenuHook() {
canDelete = list->canDeleteSelected();
}
_canApplyMarkdown = canApplyMarkdown;
_private->setMarkdownEnabledState(markdownState);
updateIsActive();
const auto logged = (sessionController() != nullptr);
@ -603,13 +607,19 @@ void MainWindow::updateGlobalMenuHook() {
ForceDisabled(psNewChannel, inactive || support);
ForceDisabled(psShowTelegram, isActive());
ForceDisabled(psBold, !canApplyMarkdown);
ForceDisabled(psItalic, !canApplyMarkdown);
ForceDisabled(psUnderline, !canApplyMarkdown);
ForceDisabled(psStrikeOut, !canApplyMarkdown);
ForceDisabled(psBlockquote, !canApplyMarkdown);
ForceDisabled(psMonospace, !canApplyMarkdown);
ForceDisabled(psClearFormat, !canApplyMarkdown);
const auto diabled = [=](const QString &tag) {
return !markdownState.enabledForTag(tag);
};
using Field = Ui::InputField;
ForceDisabled(psBold, diabled(Field::kTagBold));
ForceDisabled(psItalic, diabled(Field::kTagItalic));
ForceDisabled(psUnderline, diabled(Field::kTagUnderline));
ForceDisabled(psStrikeOut, diabled(Field::kTagStrikeOut));
ForceDisabled(psBlockquote, diabled(Field::kTagBlockquote));
ForceDisabled(
psMonospace,
diabled(Field::kTagPre) || diabled(Field::kTagCode));
ForceDisabled(psClearFormat, markdownState.disabled());
}
bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {

View File

@ -17,9 +17,13 @@ namespace Window {
class Controller;
} // namespace Window
namespace Ui {
struct MarkdownEnabledState;
} // namespace Ui
API_AVAILABLE(macos(10.12.2))
@interface RootTouchBar : NSTouchBar<NSTouchBarDelegate>
- (id)init:(rpl::producer<bool>)canApplyMarkdown
- (id)init:(rpl::producer<Ui::MarkdownEnabledState>)markdownState
controller:(not_null<Window::Controller*>)controller
domain:(not_null<Main::Domain*>)domain;
@end

View File

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/mac/touchbar/mac_touchbar_audio.h"
#include "platform/mac/touchbar/mac_touchbar_common.h"
#include "platform/mac/touchbar/mac_touchbar_main.h"
#include "ui/widgets/fields/input_field.h"
#include "window/window_controller.h"
#include "window/window_session_controller.h"
@ -57,13 +58,12 @@ const auto kAudioItemIdentifier = @"touchbarAudio";
Main::Session *_session;
Window::Controller *_controller;
bool _canApplyMarkdownLast;
rpl::event_stream<bool> _canApplyMarkdown;
rpl::variable<Ui::MarkdownEnabledState> _markdownState;
rpl::event_stream<> _touchBarSwitches;
rpl::lifetime _lifetime;
}
- (id)init:(rpl::producer<bool>)canApplyMarkdown
- (id)init:(rpl::producer<Ui::MarkdownEnabledState>)markdownState
controller:(not_null<Window::Controller*>)controller
domain:(not_null<Main::Domain*>)domain {
self = [super init];
@ -75,10 +75,7 @@ const auto kAudioItemIdentifier = @"touchbarAudio";
self.defaultItemIdentifiers = @[];
});
_controller = controller;
_canApplyMarkdownLast = false;
std::move(
canApplyMarkdown
) | rpl::start_to_stream(_canApplyMarkdown, _lifetime);
_markdownState = std::move(markdownState);
auto sessionChanges = domain->activeSessionChanges(
) | rpl::map([=](Main::Session *session) {
@ -140,8 +137,7 @@ const auto kAudioItemIdentifier = @"touchbarAudio";
init:_controller
touchBarSwitches:_touchBarSwitches.events()] autorelease];
rpl::combine(
_canApplyMarkdown.events_starting_with_copy(
_canApplyMarkdownLast),
_markdownState.value(),
_controller->sessionController()->activeChatValue(
) | rpl::map([](Dialogs::Key k) {
const auto topic = k.topic();
@ -153,16 +149,15 @@ const auto kAudioItemIdentifier = @"touchbarAudio";
: (peer && Data::CanSendAnyOf(peer, rights));
}) | rpl::distinct_until_changed()
) | rpl::start_with_next([=](
bool canApplyMarkdown,
Ui::MarkdownEnabledState state,
bool hasActiveChat) {
_canApplyMarkdownLast = canApplyMarkdown;
item.groupTouchBar.defaultItemIdentifiers = @[
kPinnedPanelItemIdentifier,
canApplyMarkdown
(!state.disabled()
? kPopoverInputItemIdentifier
: hasActiveChat
? kPopoverPickerItemIdentifier
: @""];
: @"")];
}, [item lifetime]);
return [item autorelease];

View File

@ -88,7 +88,7 @@ EditInfoBox::EditInfoBox(
_field->setInstantReplaces(Ui::InstantReplaces::Default());
_field->setInstantReplacesEnabled(
Core::App().settings().replaceEmojiValue());
_field->setMarkdownReplacesEnabled(rpl::single(true));
_field->setMarkdownReplacesEnabled(true);
_field->setEditLinkCallback(
DefaultEditLinkCallback(controller->uiShow(), _field));
}

View File

@ -16,7 +16,8 @@ void EditFactcheckBox(
not_null<Ui::GenericBox*> box,
TextWithEntities current,
int limit,
Fn<void(TextWithEntities)> save) {
Fn<void(TextWithEntities)> save,
Fn<void(not_null<Ui::InputField*>)> initField) {
box->setTitle(tr::lng_factcheck_title());
const auto field = box->addRow(object_ptr<Ui::InputField>(
@ -29,6 +30,7 @@ void EditFactcheckBox(
TextUtilities::ConvertEntitiesToTextTags(current.entities)
}));
AddLengthLimitLabel(field, limit);
initField(field);
enum class State {
Initial,

View File

@ -9,8 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/layers/generic_box.h"
namespace Ui {
class InputField;
} // namespace Ui
void EditFactcheckBox(
not_null<Ui::GenericBox*> box,
TextWithEntities current,
int limit,
Fn<void(TextWithEntities)> save);
Fn<void(TextWithEntities)> save,
Fn<void(not_null<Ui::InputField*>)> initField);

@ -1 +1 @@
Subproject commit e7c598affe724322577ef46d9a07d1dcac3c617b
Subproject commit 0835adcc2d3cb1f2cf0d3630f6d95485864621f9