Partially (italic+colored) support blockquotes.
This commit is contained in:
parent
68b89a6ba9
commit
c317f2a353
12 changed files with 134 additions and 58 deletions
|
|
@ -148,6 +148,10 @@ QString Integration::phraseFormattingStrikeOut() {
|
|||
return "Strike-through";
|
||||
}
|
||||
|
||||
QString Integration::phraseFormattingBlockquote() {
|
||||
return "Quote";
|
||||
}
|
||||
|
||||
QString Integration::phraseFormattingMonospace() {
|
||||
return "Monospace";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ public:
|
|||
[[nodiscard]] virtual QString phraseFormattingItalic();
|
||||
[[nodiscard]] virtual QString phraseFormattingUnderline();
|
||||
[[nodiscard]] virtual QString phraseFormattingStrikeOut();
|
||||
[[nodiscard]] virtual QString phraseFormattingBlockquote();
|
||||
[[nodiscard]] virtual QString phraseFormattingMonospace();
|
||||
[[nodiscard]] virtual QString phraseFormattingSpoiler();
|
||||
[[nodiscard]] virtual QString phraseButtonOk();
|
||||
|
|
|
|||
|
|
@ -1006,6 +1006,7 @@ TextForMimeData String::toText(
|
|||
{ TextBlockFlag::StrikeOut, EntityType::StrikeOut },
|
||||
{ TextBlockFlag::Code, EntityType::Code }, // #TODO entities
|
||||
{ TextBlockFlag::Pre, EntityType::Pre },
|
||||
{ TextBlockFlag::Blockquote, EntityType::Blockquote },
|
||||
} : std::vector<MarkdownTagTracker>();
|
||||
const auto flagsChangeCallback = [&](int32 oldFlags, int32 newFlags) {
|
||||
if (!composeEntities) {
|
||||
|
|
|
|||
|
|
@ -392,7 +392,9 @@ style::font WithFlags(
|
|||
|| (fontFlags & FontSemibold)) {
|
||||
result = result->semibold();
|
||||
}
|
||||
if ((flags & TextBlockFlag::Italic) || (fontFlags & FontItalic)) {
|
||||
if ((flags & TextBlockFlag::Italic)
|
||||
|| (fontFlags & FontItalic)
|
||||
|| (flags & TextBlockFlag::Blockquote)) {
|
||||
result = result->italic();
|
||||
}
|
||||
if ((flags & TextBlockFlag::Underline) || (fontFlags & FontUnderline)) {
|
||||
|
|
|
|||
|
|
@ -30,15 +30,16 @@ enum class TextBlockType : uint16 {
|
|||
};
|
||||
|
||||
enum class TextBlockFlag : uint16 {
|
||||
Bold = 0x001,
|
||||
Italic = 0x002,
|
||||
Underline = 0x004,
|
||||
StrikeOut = 0x008,
|
||||
Tilde = 0x010, // Tilde fix in OpenSans.
|
||||
Semibold = 0x020,
|
||||
Code = 0x040,
|
||||
Pre = 0x080,
|
||||
Spoiler = 0x100,
|
||||
Bold = 0x001,
|
||||
Italic = 0x002,
|
||||
Underline = 0x004,
|
||||
StrikeOut = 0x008,
|
||||
Tilde = 0x010, // Tilde fix in OpenSans.
|
||||
Semibold = 0x020,
|
||||
Code = 0x040,
|
||||
Pre = 0x080,
|
||||
Spoiler = 0x100,
|
||||
Blockquote = 0x200,
|
||||
};
|
||||
inline constexpr bool is_flag_type(TextBlockFlag) { return true; }
|
||||
using TextBlockFlags = base::flags<TextBlockFlag>;
|
||||
|
|
|
|||
|
|
@ -1438,7 +1438,9 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
|
|||
if (s > half) {
|
||||
bool inEntity = (currentEntity < entityCount) && (ch > start + left.entities[currentEntity].offset()) && (ch < start + left.entities[currentEntity].offset() + left.entities[currentEntity].length());
|
||||
EntityType entityType = (currentEntity < entityCount) ? left.entities[currentEntity].type() : EntityType::Invalid;
|
||||
bool canBreakEntity = (entityType == EntityType::Pre || entityType == EntityType::Code); // #TODO entities
|
||||
bool canBreakEntity = (entityType == EntityType::Pre)
|
||||
|| (entityType == EntityType::Blockquote)
|
||||
|| (entityType == EntityType::Code); // #TODO entities
|
||||
int32 noEntityLevel = inEntity ? 0 : 1;
|
||||
|
||||
auto markGoodAsLevel = [&](int newLevel) {
|
||||
|
|
@ -1464,9 +1466,15 @@ bool CutPart(TextWithEntities &sending, TextWithEntities &left, int32 limit) {
|
|||
}
|
||||
} else if (ch + 1 < end && IsNewline(*(ch + 1))) {
|
||||
markGoodAsLevel(15);
|
||||
} else if (currentEntity < entityCount && ch + 1 == start + left.entities[currentEntity].offset() && left.entities[currentEntity].type() == EntityType::Pre) {
|
||||
} else if (currentEntity < entityCount
|
||||
&& ch + 1 == start + left.entities[currentEntity].offset()
|
||||
&& (left.entities[currentEntity].type() == EntityType::Pre
|
||||
|| left.entities[currentEntity].type() == EntityType::Blockquote)) {
|
||||
markGoodAsLevel(14);
|
||||
} else if (currentEntity > 0 && ch == start + left.entities[currentEntity - 1].offset() + left.entities[currentEntity - 1].length() && left.entities[currentEntity - 1].type() == EntityType::Pre) {
|
||||
} else if (currentEntity > 0
|
||||
&& ch == start + left.entities[currentEntity - 1].offset() + left.entities[currentEntity - 1].length()
|
||||
&& (left.entities[currentEntity - 1].type() == EntityType::Pre
|
||||
|| left.entities[currentEntity - 1].type() == EntityType::Blockquote)) {
|
||||
markGoodAsLevel(14);
|
||||
} else {
|
||||
markGoodAsLevel(13);
|
||||
|
|
@ -2029,6 +2037,7 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
|||
EntityType::Spoiler,
|
||||
EntityType::Code,
|
||||
EntityType::Pre,
|
||||
EntityType::Blockquote,
|
||||
};
|
||||
struct State {
|
||||
QString link;
|
||||
|
|
@ -2146,6 +2155,8 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) {
|
|||
&& single.startsWith(Tags::kTagPre)) {
|
||||
result.set(EntityType::Pre);
|
||||
result.language = single.mid(languageStart).toString();
|
||||
} else if (single == Tags::kTagBlockquote) {
|
||||
result.set(EntityType::Blockquote);
|
||||
} else if (single == Tags::kTagSpoiler) {
|
||||
result.set(EntityType::Spoiler);
|
||||
} else {
|
||||
|
|
@ -2253,6 +2264,9 @@ TextWithTags::Tags ConvertEntitiesToTextTags(
|
|||
}
|
||||
push(Ui::InputField::kTagPre);
|
||||
} break;
|
||||
case EntityType::Blockquote:
|
||||
push(Ui::InputField::kTagBlockquote);
|
||||
break;
|
||||
case EntityType::Spoiler: push(Ui::InputField::kTagSpoiler); break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ enum class EntityType : uchar {
|
|||
StrikeOut,
|
||||
Code, // inline
|
||||
Pre, // block
|
||||
Blockquote,
|
||||
Spoiler,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ constexpr auto kMaxDiacAfterSymbol = 2;
|
|||
|| type == EntityType::Colorized
|
||||
|| type == EntityType::Spoiler
|
||||
|| type == EntityType::Code
|
||||
|| type == EntityType::Pre))) {
|
||||
|| type == EntityType::Pre
|
||||
|| type == EntityType::Blockquote))) {
|
||||
continue;
|
||||
}
|
||||
result.entities.push_back(preparsed.at(i));
|
||||
|
|
@ -226,6 +227,17 @@ void Parser::createNewlineBlock() {
|
|||
createBlock();
|
||||
}
|
||||
|
||||
void Parser::ensureAtNewline() {
|
||||
const auto lastType = _t->_blocks.empty()
|
||||
? TextBlockType::Newline
|
||||
: _t->_blocks.back()->type();
|
||||
if (lastType != TextBlockType::Newline) {
|
||||
auto saved = base::take(_customEmojiData);
|
||||
createNewlineBlock();
|
||||
_customEmojiData = base::take(saved);
|
||||
}
|
||||
}
|
||||
|
||||
void Parser::finishEntities() {
|
||||
while (!_startedEntities.empty()
|
||||
&& (_ptr >= _startedEntities.begin()->first || _ptr >= _end)) {
|
||||
|
|
@ -239,9 +251,13 @@ void Parser::finishEntities() {
|
|||
if (_flags & (*flags)) {
|
||||
createBlock();
|
||||
_flags &= ~(*flags);
|
||||
if (((*flags) & TextBlockFlag::Pre)
|
||||
&& !_t->_blocks.empty()
|
||||
&& _t->_blocks.back()->type() != TextBlockType::Newline) {
|
||||
const auto lastType = _t->_blocks.empty()
|
||||
? TextBlockType::Newline
|
||||
: _t->_blocks.back()->type();
|
||||
if ((lastType != TextBlockType::Newline)
|
||||
&& ((*flags)
|
||||
& (TextBlockFlag::Pre
|
||||
| TextBlockFlag::Blockquote))) {
|
||||
_newlineAwaited = true;
|
||||
}
|
||||
if (IsMono(*flags)) {
|
||||
|
|
@ -320,11 +336,7 @@ bool Parser::checkEntities() {
|
|||
} else {
|
||||
flags = TextBlockFlag::Pre;
|
||||
createBlock();
|
||||
if (!_t->_blocks.empty()
|
||||
&& _t->_blocks.back()->type() != TextBlockType::Newline
|
||||
&& _customEmojiData.isEmpty()) {
|
||||
createNewlineBlock();
|
||||
}
|
||||
ensureAtNewline();
|
||||
}
|
||||
const auto text = QString(entityBegin, entityLength);
|
||||
|
||||
|
|
@ -338,6 +350,10 @@ bool Parser::checkEntities() {
|
|||
_monos.push_back({ .text = text, .type = entityType });
|
||||
monoIndex = _monos.size();
|
||||
}
|
||||
} else if (entityType == EntityType::Blockquote) {
|
||||
flags = TextBlockFlag::Blockquote;
|
||||
createBlock();
|
||||
ensureAtNewline();
|
||||
} else if (entityType == EntityType::Url
|
||||
|| entityType == EntityType::Email
|
||||
|| entityType == EntityType::Mention
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ private:
|
|||
void blockCreated();
|
||||
void createBlock(int32 skipBack = 0);
|
||||
void createNewlineBlock();
|
||||
void ensureAtNewline();
|
||||
|
||||
// Returns true if at least one entity was parsed in the current position.
|
||||
bool checkEntities();
|
||||
|
|
|
|||
|
|
@ -2028,7 +2028,7 @@ void Renderer::applyBlockProperties(const AbstractBlock *block) {
|
|||
_currentPen = &_originalPen;
|
||||
_currentPenSelected = &_originalPenSelected;
|
||||
}
|
||||
} else if (isMono) {
|
||||
} else if (isMono || (flags & TextBlockFlag::Blockquote)) {
|
||||
_currentPen = &_palette->monoFg->p;
|
||||
_currentPenSelected = &_palette->selectMonoFg->p;
|
||||
} else if (block->linkIndex()) {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ const auto &kTagUnderline = InputField::kTagUnderline;
|
|||
const auto &kTagStrikeOut = InputField::kTagStrikeOut;
|
||||
const auto &kTagCode = InputField::kTagCode;
|
||||
const auto &kTagPre = InputField::kTagPre;
|
||||
const auto &kTagBlockquote = InputField::kTagBlockquote;
|
||||
const auto &kTagSpoiler = InputField::kTagSpoiler;
|
||||
const auto &kCustomEmojiFormat = InputField::kCustomEmojiFormat;
|
||||
const auto kTagCheckLinkMeta = u"^:/:/:^"_q;
|
||||
|
|
@ -754,6 +755,9 @@ QTextCharFormat PrepareTagFormat(
|
|||
font = font->underline();
|
||||
} else if (tag == kTagStrikeOut) {
|
||||
font = font->strikeout();
|
||||
} else if (tag == kTagBlockquote) {
|
||||
color = st::defaultTextPalette.monoFg;
|
||||
font = font->italic();
|
||||
} else if (tag == kTagCode || IsTagPre(tag)) {
|
||||
color = st::defaultTextPalette.monoFg;
|
||||
font = font->monospace();
|
||||
|
|
@ -931,13 +935,14 @@ struct FormattingAction {
|
|||
|
||||
// kTagUnderline is not used for Markdown.
|
||||
|
||||
const QString InputField::kTagBold = QStringLiteral("**");
|
||||
const QString InputField::kTagItalic = QStringLiteral("__");
|
||||
const QString InputField::kTagUnderline = QStringLiteral("^^");
|
||||
const QString InputField::kTagStrikeOut = QStringLiteral("~~");
|
||||
const QString InputField::kTagCode = QStringLiteral("`");
|
||||
const QString InputField::kTagPre = QStringLiteral("```");
|
||||
const QString InputField::kTagSpoiler = QStringLiteral("||");
|
||||
const QString InputField::kTagBold = u"**"_q;
|
||||
const QString InputField::kTagItalic = u"__"_q;
|
||||
const QString InputField::kTagUnderline = u"^^"_q;
|
||||
const QString InputField::kTagStrikeOut = u"~~"_q;
|
||||
const QString InputField::kTagCode = u"`"_q;
|
||||
const QString InputField::kTagPre = u"```"_q;
|
||||
const QString InputField::kTagSpoiler = u"||"_q;
|
||||
const QString InputField::kTagBlockquote = u">"_q;
|
||||
const QString InputField::kCustomEmojiTagStart = u"custom-emoji://"_q;
|
||||
const int InputField::kCustomEmojiFormat
|
||||
= QTextFormat::UserObject + 1;
|
||||
|
|
@ -2903,6 +2908,14 @@ bool InputField::handleMarkdownKey(QKeyEvent *e) {
|
|||
const auto events = QKeySequence(searchKey);
|
||||
return sequence.matches(events) == QKeySequence::ExactMatch;
|
||||
};
|
||||
const auto matchesCtrlShiftDot = [&] {
|
||||
// We can't match ctrl+shift+. with QKeySequence because
|
||||
// shift+. gives us '>' and ctrl+shift+> is not the same.
|
||||
// So we check by nativeVirtualKey instead.
|
||||
return e->modifiers().testFlag(Qt::ControlModifier)
|
||||
&& e->modifiers().testFlag(Qt::ShiftModifier)
|
||||
&& (e->nativeVirtualKey() == 190);
|
||||
};
|
||||
if (e == QKeySequence::Bold) {
|
||||
toggleSelectionMarkdown(kTagBold);
|
||||
} else if (e == QKeySequence::Italic) {
|
||||
|
|
@ -2913,6 +2926,8 @@ bool InputField::handleMarkdownKey(QKeyEvent *e) {
|
|||
toggleSelectionMarkdown(kTagStrikeOut);
|
||||
} else if (matches(kMonospaceSequence)) {
|
||||
toggleSelectionMarkdown(kTagCode);
|
||||
} else if (matches(kBlockquoteSequence) || matchesCtrlShiftDot()) {
|
||||
toggleSelectionMarkdown(kTagBlockquote);
|
||||
} else if (matches(kSpoilerSequence)) {
|
||||
toggleSelectionMarkdown(kTagSpoiler);
|
||||
} else if (matches(kClearFormatSequence)) {
|
||||
|
|
@ -3518,9 +3533,9 @@ void InputField::commitMarkdownLinkEdit(
|
|||
void InputField::toggleSelectionMarkdown(const QString &tag) {
|
||||
_reverseMarkdownReplacement = false;
|
||||
const auto cursor = textCursor();
|
||||
const auto position = cursor.position();
|
||||
const auto from = cursor.selectionStart();
|
||||
const auto till = cursor.selectionEnd();
|
||||
auto position = cursor.position();
|
||||
auto from = cursor.selectionStart();
|
||||
auto till = cursor.selectionEnd();
|
||||
if (from == till) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -3529,33 +3544,50 @@ void InputField::toggleSelectionMarkdown(const QString &tag) {
|
|||
} else if (HasFullTextTag(getTextWithTagsSelected(), tag)) {
|
||||
removeMarkdownTag(from, till, tag);
|
||||
} else {
|
||||
const auto useTag = [&] {
|
||||
if (tag != kTagCode) {
|
||||
return tag;
|
||||
const auto leftForBlock = [&] {
|
||||
if (!from) {
|
||||
return true;
|
||||
}
|
||||
const auto leftForBlock = [&] {
|
||||
if (!from) {
|
||||
return true;
|
||||
}
|
||||
const auto text = getTextWithTagsPart(
|
||||
from - 1,
|
||||
from + 1
|
||||
).text;
|
||||
return text.isEmpty()
|
||||
|| IsNewline(text[0])
|
||||
|| IsNewline(text[text.size() - 1]);
|
||||
}();
|
||||
const auto rightForBlock = [&] {
|
||||
const auto text = getTextWithTagsPart(
|
||||
till - 1,
|
||||
till + 1
|
||||
).text;
|
||||
return text.isEmpty()
|
||||
|| IsNewline(text[0])
|
||||
|| IsNewline(text[text.size() - 1]);
|
||||
}();
|
||||
return (leftForBlock && rightForBlock) ? kTagPre : kTagCode;
|
||||
const auto text = getTextWithTagsPart(
|
||||
from - 1,
|
||||
from + 1
|
||||
).text;
|
||||
return text.isEmpty()
|
||||
|| IsNewline(text[0])
|
||||
|| IsNewline(text[text.size() - 1]);
|
||||
}();
|
||||
const auto rightForBlock = [&] {
|
||||
const auto text = getTextWithTagsPart(
|
||||
till - 1,
|
||||
till + 1
|
||||
).text;
|
||||
return text.isEmpty()
|
||||
|| IsNewline(text[0])
|
||||
|| IsNewline(text[text.size() - 1]);
|
||||
}();
|
||||
|
||||
const auto useTag = (tag != kTagCode)
|
||||
? tag
|
||||
: (leftForBlock && rightForBlock)
|
||||
? kTagPre
|
||||
: kTagCode;
|
||||
if (tag == kTagBlockquote) {
|
||||
QTextCursor(document()).beginEditBlock();
|
||||
if (!leftForBlock) {
|
||||
auto copy = textCursor();
|
||||
copy.setPosition(from);
|
||||
copy.insertText(u"\n"_q);
|
||||
++position;
|
||||
++from;
|
||||
++till;
|
||||
}
|
||||
if (!rightForBlock) {
|
||||
auto copy = textCursor();
|
||||
copy.setPosition(till);
|
||||
copy.insertText(u"\n"_q);
|
||||
}
|
||||
QTextCursor(document()).endEditBlock();
|
||||
}
|
||||
addMarkdownTag(from, till, useTag);
|
||||
}
|
||||
auto restorePosition = textCursor();
|
||||
|
|
@ -3738,6 +3770,7 @@ void InputField::addMarkdownActions(
|
|||
addtag(integration.phraseFormattingItalic(), QKeySequence::Italic, kTagItalic);
|
||||
addtag(integration.phraseFormattingUnderline(), QKeySequence::Underline, kTagUnderline);
|
||||
addtag(integration.phraseFormattingStrikeOut(), kStrikeOutSequence, kTagStrikeOut);
|
||||
addtag(integration.phraseFormattingBlockquote(), kBlockquoteSequence, kTagBlockquote);
|
||||
addtag(integration.phraseFormattingMonospace(), kMonospaceSequence, kTagCode);
|
||||
addtag(integration.phraseFormattingSpoiler(), kSpoilerSequence, kTagSpoiler);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ namespace Ui {
|
|||
|
||||
const auto kClearFormatSequence = QKeySequence("ctrl+shift+n");
|
||||
const auto kStrikeOutSequence = QKeySequence("ctrl+shift+x");
|
||||
const auto kBlockquoteSequence = QKeySequence("ctrl+shift+.");
|
||||
const auto kMonospaceSequence = QKeySequence("ctrl+shift+m");
|
||||
const auto kEditLinkSequence = QKeySequence("ctrl+k");
|
||||
const auto kSpoilerSequence = QKeySequence("ctrl+shift+p");
|
||||
|
|
@ -133,6 +134,7 @@ public:
|
|||
static const QString kTagCode;
|
||||
static const QString kTagPre;
|
||||
static const QString kTagSpoiler;
|
||||
static const QString kTagBlockquote;
|
||||
static const QString kCustomEmojiTagStart;
|
||||
static const int kCustomEmojiFormat;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue