diff --git a/ui/integration.cpp b/ui/integration.cpp index 999140b..28ffb7c 100644 --- a/ui/integration.cpp +++ b/ui/integration.cpp @@ -139,4 +139,8 @@ QString Integration::phraseFormattingMonospace() { return "Monospace"; } +QString Integration::phraseFormattingSpoiler() { + return "Spoiler"; +} + } // namespace Ui diff --git a/ui/integration.h b/ui/integration.h index cd7704b..c981689 100644 --- a/ui/integration.h +++ b/ui/integration.h @@ -70,6 +70,7 @@ public: [[nodiscard]] virtual QString phraseFormattingUnderline(); [[nodiscard]] virtual QString phraseFormattingStrikeOut(); [[nodiscard]] virtual QString phraseFormattingMonospace(); + [[nodiscard]] virtual QString phraseFormattingSpoiler(); }; diff --git a/ui/text/text_entity.cpp b/ui/text/text_entity.cpp index 00cfcf3..edb97f4 100644 --- a/ui/text/text_entity.cpp +++ b/ui/text/text_entity.cpp @@ -70,6 +70,10 @@ QString SeparatorsMono() { return Separators(QString::fromUtf8("*~/")); } +QString SeparatorsSpoiler() { + return Separators(QString::fromUtf8("|*~/")); +} + QString ExpressionHashtag() { return QString::fromUtf8("(^|[") + ExpressionSeparators(QString::fromUtf8("`\\*/")) + QString::fromUtf8("])#[\\w]{2,64}([\\W]|$)"); } @@ -1271,6 +1275,14 @@ QString MarkdownPreBadAfter() { return QString::fromLatin1("`"); } +QString MarkdownSpoilerGoodBefore() { + return SeparatorsSpoiler(); +} + +QString MarkdownSpoilerBadAfter() { + return QString::fromLatin1("|"); +} + bool IsValidProtocol(const QString &protocol) { static const auto list = CreateValidProtocols(); return list.contains(base::crc32(protocol.constData(), protocol.size() * sizeof(QChar))); @@ -2087,6 +2099,7 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) { EntityType::Italic, EntityType::Underline, EntityType::StrikeOut, + EntityType::Spoiler, EntityType::Code, EntityType::Pre, }; @@ -2193,6 +2206,8 @@ EntitiesInText ConvertTextTagsToEntities(const TextWithTags::Tags &tags) { result.set(EntityType::Code); } else if (single == Ui::InputField::kTagPre) { result.set(EntityType::Pre); + } else if (single == Ui::InputField::kTagSpoiler) { + result.set(EntityType::Spoiler); } else { result.link = single.toString(); } @@ -2281,6 +2296,7 @@ TextWithTags::Tags ConvertEntitiesToTextTags( break; case EntityType::Code: push(Ui::InputField::kTagCode); break; // #TODO entities case EntityType::Pre: push(Ui::InputField::kTagPre); break; + case EntityType::Spoiler: push(Ui::InputField::kTagSpoiler); break; } } if (!toRemove.empty()) { diff --git a/ui/text/text_entity.h b/ui/text/text_entity.h index 52ee5ce..915e62e 100644 --- a/ui/text/text_entity.h +++ b/ui/text/text_entity.h @@ -291,6 +291,8 @@ QString MarkdownCodeGoodBefore(); QString MarkdownCodeBadAfter(); QString MarkdownPreGoodBefore(); QString MarkdownPreBadAfter(); +QString MarkdownSpoilerGoodBefore(); +QString MarkdownSpoilerBadAfter(); // Text preprocess. QString Clean(const QString &text); diff --git a/ui/widgets/input_fields.cpp b/ui/widgets/input_fields.cpp index 98cb7f1..24f2eab 100644 --- a/ui/widgets/input_fields.cpp +++ b/ui/widgets/input_fields.cpp @@ -44,6 +44,7 @@ const auto &kTagUnderline = InputField::kTagUnderline; const auto &kTagStrikeOut = InputField::kTagStrikeOut; const auto &kTagCode = InputField::kTagCode; const auto &kTagPre = InputField::kTagPre; +const auto &kTagSpoiler = InputField::kTagSpoiler; const auto kTagCheckLinkMeta = QString("^:/:/:^"); const auto kNewlineChars = QString("\r\n") + QChar(0xfdd0) // QTextBeginningOfFrame @@ -230,6 +231,7 @@ constexpr auto kTagItalicIndex = 1; constexpr auto kTagStrikeOutIndex = 2; constexpr auto kTagCodeIndex = 3; constexpr auto kTagPreIndex = 4; +constexpr auto kTagSpoilerIndex = 5; constexpr auto kInvalidPosition = std::numeric_limits::max() / 2; class TagSearchItem { @@ -373,6 +375,13 @@ const std::vector &TagStartExpressions() { TextUtilities::MarkdownPreBadAfter(), TextUtilities::MarkdownPreGoodBefore() }, + { + kTagSpoiler, + TextUtilities::MarkdownSpoilerGoodBefore(), + TextUtilities::MarkdownSpoilerBadAfter(), + TextUtilities::MarkdownSpoilerBadAfter(), + TextUtilities::MarkdownSpoilerGoodBefore() + }, }; return cached; } @@ -385,6 +394,7 @@ const std::map &TagIndices() { { kTagStrikeOut, kTagStrikeOutIndex }, { kTagCode, kTagCodeIndex }, { kTagPre, kTagPreIndex }, + { kTagSpoiler, kTagSpoilerIndex }, }; return cached; } @@ -702,6 +712,7 @@ QTextCharFormat PrepareTagFormat( auto result = QTextCharFormat(); auto font = st.font; auto color = std::optional(); + auto bg = std::optional(); const auto applyOne = [&](QStringView tag) { if (IsValidMarkdownLink(tag)) { color = st::defaultTextPalette.linkFg; @@ -716,6 +727,8 @@ QTextCharFormat PrepareTagFormat( } else if (tag == kTagCode || tag == kTagPre) { color = st::defaultTextPalette.monoFg; font = font->monospace(); + } else if (tag == kTagSpoiler) { + bg = st::defaultTextPalette.spoilerActiveBg; } }; for (const auto &tag : TextUtilities::SplitTags(tag)) { @@ -724,6 +737,9 @@ QTextCharFormat PrepareTagFormat( result.setFont(font); result.setForeground(color.value_or(st.textFg)); result.setProperty(kTagProperty, tag); + if (bg) { + result.setBackground(*bg); + } return result; } @@ -842,6 +858,7 @@ 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("||"); class InputField::Inner final : public QTextEdit { public: @@ -2819,6 +2836,8 @@ bool InputField::handleMarkdownKey(QKeyEvent *e) { toggleSelectionMarkdown(kTagStrikeOut); } else if (matches(kMonospaceSequence)) { toggleSelectionMarkdown(kTagCode); + } else if (matches(kSpoilerSequence)) { + toggleSelectionMarkdown(kTagSpoiler); } else if (matches(kClearFormatSequence)) { clearSelectionMarkdown(); } else if (matches(kEditLinkSequence) && _editLinkCallback) { @@ -3578,6 +3597,7 @@ void InputField::addMarkdownActions( addtag(integration.phraseFormattingUnderline(), QKeySequence::Underline, kTagUnderline); addtag(integration.phraseFormattingStrikeOut(), kStrikeOutSequence, kTagStrikeOut); addtag(integration.phraseFormattingMonospace(), kMonospaceSequence, kTagCode); + addtag(integration.phraseFormattingSpoiler(), kSpoilerSequence, kTagSpoiler); if (_editLinkCallback) { submenu->addSeparator(); diff --git a/ui/widgets/input_fields.h b/ui/widgets/input_fields.h index 5638c59..846f18b 100644 --- a/ui/widgets/input_fields.h +++ b/ui/widgets/input_fields.h @@ -26,6 +26,7 @@ const auto kClearFormatSequence = QKeySequence("ctrl+shift+n"); const auto kStrikeOutSequence = QKeySequence("ctrl+shift+x"); const auto kMonospaceSequence = QKeySequence("ctrl+shift+m"); const auto kEditLinkSequence = QKeySequence("ctrl+k"); +const auto kSpoilerSequence = QKeySequence("ctrl+shift+p"); class PopupMenu; @@ -171,6 +172,7 @@ public: static const QString kTagStrikeOut; static const QString kTagCode; static const QString kTagPre; + static const QString kTagSpoiler; InputField( QWidget *parent,