Files
AyuGramDesktop/Telegram/SourceFiles/iv/editor/iv_editor_state.h
T
2026-07-14 13:12:13 +04:00

980 lines
34 KiB
C++

/*
This file is part of Telegram Desktop,
the official desktop application for the Telegram messaging service.
For license and copyright information please follow this link:
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
#include "iv/editor/iv_editor_clipboard.h"
#include "iv/iv_rich_page.h"
#include "iv/markdown/iv_markdown_prepare.h"
#include <memory>
#include <optional>
#include <vector>
namespace Iv::Editor {
class State final {
public:
enum class FieldMode : uchar {
Rich,
Raw,
};
enum class ApplyResult {
Failed,
Unchanged,
Changed,
};
enum class PreparedMutationKind {
None,
LeafOnly,
FullRebuild,
};
enum class InsertBlockType : uchar {
Heading,
Blockquote,
Code,
Math,
Footer,
Divider,
Anchor,
OrderedList,
BulletList,
TaskList,
Pullquote,
Photo,
Video,
Audio,
Details,
Table,
Map,
};
struct InsertAction {
InsertBlockType type;
int headingLevel = 1;
double latitude = 0.;
double longitude = 0.;
};
enum class BlockContainerKind : uchar {
Root,
BlockChildren,
ListItemChildren,
};
struct BlockContainerStep {
BlockContainerKind kind = BlockContainerKind::BlockChildren;
int blockIndex = -1;
int listItemIndex = -1;
friend inline bool operator==(
const BlockContainerStep &a,
const BlockContainerStep &b) {
return (a.kind == b.kind)
&& (a.blockIndex == b.blockIndex)
&& (a.listItemIndex == b.listItemIndex);
}
};
struct BlockContainerPath {
std::vector<BlockContainerStep> steps;
friend inline bool operator==(
const BlockContainerPath &a,
const BlockContainerPath &b) {
return (a.steps == b.steps);
}
};
struct BlockPath {
BlockContainerPath container;
int index = -1;
friend inline bool operator==(
const BlockPath &a,
const BlockPath &b) {
return (a.container == b.container)
&& (a.index == b.index);
}
};
struct ReplaceTarget {
BlockPath path;
RichPage::BlockKind kind = RichPage::BlockKind::Unsupported;
uint64 mediaId = 0;
int itemIndex = -1;
};
enum class LeafKind : uchar {
BlockText,
BlockCaption,
ListItemText,
TableCellText,
MathFormula,
};
struct LeafPath {
LeafKind kind = LeafKind::BlockText;
BlockPath block;
int listItemIndex = -1;
int tableRowIndex = -1;
int tableCellIndex = -1;
friend inline bool operator==(
const LeafPath &a,
const LeafPath &b) {
return (a.kind == b.kind)
&& (a.block == b.block)
&& (a.listItemIndex == b.listItemIndex)
&& (a.tableRowIndex == b.tableRowIndex)
&& (a.tableCellIndex == b.tableCellIndex);
}
};
struct Snapshot {
RichPage richPage;
std::optional<LeafPath> activeLeaf;
std::optional<LeafPath> temporaryDownParagraph;
};
struct InsertionAnchor {
BlockContainerPath container;
int blockIndex = -1;
};
enum class RemovalKind : uchar {
Block,
ListItem,
TableCell,
};
struct RemovalTarget {
RemovalKind kind = RemovalKind::Block;
BlockPath block;
int listItemIndex = -1;
int tableRowIndex = -1;
int tableCellIndex = -1;
friend inline bool operator==(
const RemovalTarget &a,
const RemovalTarget &b) {
return (a.kind == b.kind)
&& (a.block == b.block)
&& (a.listItemIndex == b.listItemIndex)
&& (a.tableRowIndex == b.tableRowIndex)
&& (a.tableCellIndex == b.tableCellIndex);
}
};
struct TextNodeDescriptor {
LeafPath leaf;
InsertionAnchor insertionAnchor;
RemovalTarget removalTarget;
FieldMode mode = FieldMode::Rich;
};
struct BoundaryTarget {
enum class Action : uchar {
None,
Text,
StructuralSelection,
RemoveActiveOwner,
};
Action action = Action::None;
int textOrdinal = -1;
Markdown::PreparedEditSelection structuralSelection;
};
struct TableSelectionInfo {
bool valid = false;
bool allHeader = false;
bool allAlignLeft = false;
bool allAlignCenter = false;
bool allAlignRight = false;
bool allAlignTop = false;
bool allAlignMiddle = false;
bool allAlignBottom = false;
bool singleCell = false;
bool canSplitCell = false;
bool canUniteCells = false;
bool canDeleteRows = false;
bool canDeleteColumns = false;
bool canDeleteTable = false;
int selectedRows = 0;
int selectedColumns = 0;
bool bordered = false;
bool striped = false;
};
struct ListSelectionInfo {
bool valid = false;
bool taskList = false;
bool wholeList = false;
bool singleItem = false;
bool reversed = false;
bool allOrderedDecimal = false;
bool allOrderedLowerAlpha = false;
bool allOrderedUpperAlpha = false;
bool allOrderedLowerRoman = false;
bool allOrderedUpperRoman = false;
int selectedItems = 0;
RichPage::ListKind listKind = RichPage::ListKind::Bullet;
};
enum class ListStyle : uchar {
Ordered,
Bullet,
Task,
};
State();
State(
std::shared_ptr<RichPage> richPage,
std::shared_ptr<Markdown::MediaRuntime> mediaRuntime,
RichMessageLimits limits = {});
[[nodiscard]] const RichPage &richPage() const;
[[nodiscard]] bool articleEmpty() const;
[[nodiscard]] const Markdown::MarkdownArticleContent &prepared() const;
[[nodiscard]] const std::vector<TextNodeDescriptor> &textNodes() const;
[[nodiscard]] Snapshot snapshot() const;
void restoreSnapshot(Snapshot snapshot);
[[nodiscard]] std::optional<LeafPath> activeLeafPath() const;
[[nodiscard]] int textOrdinalForLeafPath(const LeafPath &path) const;
[[nodiscard]] int textOrdinalForLeaf(
const Markdown::PreparedEditLeafSource &source) const;
[[nodiscard]] auto preparedLeafSourceForOrdinal(int ordinal) const
-> std::optional<Markdown::PreparedEditLeafSource>;
[[nodiscard]] PreparedMutationKind lastPreparedMutationKind() const;
[[nodiscard]] auto activePreparedLeafSource() const
-> std::optional<Markdown::PreparedEditLeafSource>;
[[nodiscard]] int textNodeCount() const;
[[nodiscard]] int activeTextOrdinal() const;
[[nodiscard]] bool setActiveTextByOrdinal(int ordinal);
[[nodiscard]] TextWithEntities activeText() const;
[[nodiscard]] ApplyResult applyActiveText(TextWithEntities text);
[[nodiscard]] FieldMode activeFieldMode() const;
[[nodiscard]] QString activeRawText() const;
[[nodiscard]] QString activePlaceholderText() const;
[[nodiscard]] ApplyResult applyActiveRawText(QString text);
[[nodiscard]] std::optional<RichMessageLimitError> lastLimitError() const;
[[nodiscard]] std::optional<QString> codeBlockLanguage(int ordinal) const;
[[nodiscard]] bool setCodeBlockLanguage(int ordinal, QString language);
[[nodiscard]] int activeTextLength() const;
[[nodiscard]] std::optional<int> previousEditableOrdinal() const;
[[nodiscard]] std::optional<int> nextEditableOrdinal() const;
[[nodiscard]] std::optional<int> firstTableCellOrdinalFromActiveTitle() const;
[[nodiscard]] BoundaryTarget activeBoundaryTarget(bool forward) const;
[[nodiscard]] std::vector<BoundaryTarget> boundarySteps(
bool forward) const;
[[nodiscard]] bool isActiveTopLevelParagraph() const;
[[nodiscard]] bool isActiveTopLevelParagraphOrHeading() const;
[[nodiscard]] bool activeSurfaceAllowsSeparateLineFormula() const;
[[nodiscard]] bool activeLeafUsesQuoteCaptionColor() const;
[[nodiscard]] bool activeLeafUsesQuotePlaceholderColor() const;
[[nodiscard]] bool activeBlockBodyCanEscape() const;
[[nodiscard]] std::optional<int> moveActiveSpecialBlockDown();
[[nodiscard]] std::optional<int> escapeActiveBlockBody();
[[nodiscard]] BoundaryTarget removeTemporaryDownParagraphAndMove();
enum class EnterPosition : uchar {
End,
Beginning,
Middle,
};
struct ActiveEnterContext {
EnterPosition position = EnterPosition::End;
TextWithEntities head;
TextWithEntities tail;
};
[[nodiscard]] std::optional<int> submitActiveSingleLineField(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveHeadingEnter(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveFooterEnter(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveListEnter(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveParagraphEnter(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveQuoteEnter(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> removeActiveOwnerAndSelectAdjacent(
bool forward);
[[nodiscard]] std::optional<int> removeStructuralSelection(
const Markdown::PreparedEditSelection &selection,
bool forward);
[[nodiscard]] bool toggleTaskState(
const Markdown::PreparedEditListItemSource &source);
[[nodiscard]] bool toggleDetailsOpen(
const Markdown::PreparedEditBlockSource &source);
[[nodiscard]] ListSelectionInfo listSelectionInfo(
const Markdown::PreparedEditListItemRange &range) const;
[[nodiscard]] std::optional<Markdown::PreparedEditListItemRange>
listContextRangeForSelection(
const Markdown::PreparedEditSelection &selection,
const Markdown::PreparedEditListItemSource &source) const;
[[nodiscard]] bool setListStyle(
const Markdown::PreparedEditListItemRange &range,
ListStyle style);
[[nodiscard]] bool setListOrderedType(
const Markdown::PreparedEditListItemRange &range,
Markdown::PreparedOrderedListType type);
[[nodiscard]] bool setListOrderedReversed(
const Markdown::PreparedEditListItemRange &range,
bool reversed);
[[nodiscard]] bool setListItemOrderedType(
const Markdown::PreparedEditListItemRange &range,
std::optional<Markdown::PreparedOrderedListType> type);
[[nodiscard]] TableSelectionInfo tableSelectionInfo(
const Markdown::PreparedEditTableCellRange &range) const;
[[nodiscard]] std::optional<Markdown::PreparedEditTableCellRange>
tableContextRangeForSelection(
const Markdown::PreparedEditSelection &selection,
const Markdown::PreparedEditTableCellSource &source) const;
[[nodiscard]] std::optional<BlockPath> convertBlockPath(
const Markdown::PreparedEditBlockPath &path) const;
[[nodiscard]] std::optional<BlockPath> convertBlockPath(
const Markdown::PreparedEditBlockSource &source) const;
[[nodiscard]] bool canRemoveStructuralSelection(
const Markdown::PreparedEditSelection &selection) const;
[[nodiscard]] auto structuredClipboardDataForSelection(
const Markdown::PreparedEditSelection &selection) const
-> std::optional<ClipboardData>;
[[nodiscard]] std::shared_ptr<const RichPage> richPageForTableSelection(
const Markdown::PreparedEditSelection &selection) const;
[[nodiscard]] bool insertPreparedBlocksAfterTableSelection(
const Markdown::PreparedEditSelection &selection,
std::vector<RichPage::Block> blocks);
enum class TableInPlaceApplyResult : uchar {
Applied,
Unchanged,
StructureMismatch,
Failed,
};
[[nodiscard]] TableInPlaceApplyResult replaceTableSelectionCellsInPlace(
const Markdown::PreparedEditSelection &selection,
const RichPage &page);
[[nodiscard]] bool addTableRow(
const Markdown::PreparedEditTableCellRange &range,
bool after);
[[nodiscard]] bool addTableColumn(
const Markdown::PreparedEditTableCellRange &range,
bool after);
[[nodiscard]] bool setTableHeader(
const Markdown::PreparedEditTableCellRange &range,
bool header);
[[nodiscard]] bool setTableAlignment(
const Markdown::PreparedEditTableCellRange &range,
RichPage::TableAlignment alignment);
[[nodiscard]] bool setTableVerticalAlignment(
const Markdown::PreparedEditTableCellRange &range,
RichPage::TableVerticalAlignment alignment);
[[nodiscard]] bool splitTableCell(
const Markdown::PreparedEditTableCellRange &range);
[[nodiscard]] bool uniteTableCells(
const Markdown::PreparedEditTableCellRange &range);
[[nodiscard]] bool removeTableRows(
const Markdown::PreparedEditTableCellRange &range);
[[nodiscard]] bool removeTableColumns(
const Markdown::PreparedEditTableCellRange &range);
[[nodiscard]] bool removeTable(
const Markdown::PreparedEditTableCellRange &range);
[[nodiscard]] bool setTableBordered(
const Markdown::PreparedEditTableCellRange &range,
bool bordered);
[[nodiscard]] bool setTableStriped(
const Markdown::PreparedEditTableCellRange &range,
bool striped);
[[nodiscard]] std::optional<int> ensureTrailingParagraphActive();
// Inserts an empty paragraph at the very start of the top-level blocks
// list, so content can always be added above a non-trivial first block
// (a table, a list, ...). Returns the ordinal of the inserted paragraph
// when focusInserted, otherwise the new ordinal of the still-active leaf.
[[nodiscard]] std::optional<int> insertLeadingParagraphActive(
bool focusInserted);
void resyncAfterExternalRichPageMutation();
void insertHeading1AfterActive();
void insertBlockquoteAfterActive();
struct ActiveTextInsertContext {
TextWithEntities before;
TextWithEntities selected;
TextWithEntities after;
};
struct ActiveTextBlockActionResult {
ApplyResult result = ApplyResult::Failed;
std::optional<LeafPath> destinationLeaf;
int selectionFrom = 0;
int selectionTo = 0;
};
struct DisplayMathEditResult {
ApplyResult result = ApplyResult::Failed;
std::optional<LeafPath> inlineLeaf;
int selectionFrom = 0;
int selectionTo = 0;
};
struct ParagraphBoundaryJoinResult {
ApplyResult result = ApplyResult::Failed;
std::optional<LeafPath> destinationLeaf;
int selectionFrom = 0;
int selectionTo = 0;
};
struct StructuralSelectionDropResult {
ApplyResult result = ApplyResult::Failed;
BoundaryTarget destination;
};
[[nodiscard]] DisplayMathEditResult editActiveDisplayMath(
QString source,
bool separateLine);
[[nodiscard]] ParagraphBoundaryJoinResult joinActiveParagraphBoundary(
bool forward);
[[nodiscard]] bool insertBlockAfterActive(
InsertAction action,
std::optional<ActiveTextInsertContext> context = std::nullopt);
[[nodiscard]] ActiveTextBlockActionResult applyActiveTextBlockAction(
InsertAction action,
ActiveTextInsertContext context);
[[nodiscard]] ActiveTextBlockActionResult
replaceActiveTextSelectionWithText(
TextWithEntities text,
ActiveTextInsertContext context);
[[nodiscard]] bool insertPreparedBlockAfterActive(RichPage::Block block);
[[nodiscard]] bool insertPreparedBlocksAfterActive(
std::vector<RichPage::Block> blocks,
std::optional<ActiveTextInsertContext> context = std::nullopt);
[[nodiscard]] bool pasteClipboardListItemsAfterActive(
const ClipboardListItemsData &data,
std::optional<ActiveTextInsertContext> context = std::nullopt);
[[nodiscard]] bool replaceStructuralSelectionWithBlock(
const Markdown::PreparedEditSelection &selection,
InsertAction action,
std::optional<ActiveTextInsertContext> context = std::nullopt,
BoundaryTarget *destination = nullptr);
[[nodiscard]] bool toggleCodeBlockForStructuralSelection(
const Markdown::PreparedEditSelection &selection);
[[nodiscard]] bool replaceStructuralSelectionWithPreparedBlocks(
const Markdown::PreparedEditSelection &selection,
std::vector<RichPage::Block> blocks,
std::optional<ActiveTextInsertContext> context = std::nullopt);
[[nodiscard]] bool replaceStructuralSelectionWithClipboardListItems(
const Markdown::PreparedEditSelection &selection,
const ClipboardListItemsData &data,
std::optional<ActiveTextInsertContext> context = std::nullopt);
[[nodiscard]] StructuralSelectionDropResult
moveStructuralSelectionToDropTarget(
const Markdown::PreparedEditSelection &selection,
const Markdown::PreparedEditDropTarget &target);
[[nodiscard]] bool insertPreparedBlocksAtDropTarget(
std::vector<RichPage::Block> blocks,
const Markdown::PreparedEditBlockDropTarget &target);
enum class TextFormattingAction : uchar {
Bold,
Italic,
Underline,
StrikeOut,
Spoiler,
PlainText,
};
struct TextNodeSpan {
LeafPath leaf;
int from = 0;
int till = 0;
};
struct TextSelectionDropResult {
ApplyResult result = ApplyResult::Failed;
std::optional<LeafPath> destinationLeaf;
int selectionFrom = 0;
int selectionTo = 0;
};
[[nodiscard]] std::vector<TextNodeSpan> resolveTextSpansForPreparedLeafRange(
const Markdown::PreparedEditLeafSource &source,
int from,
int till) const;
[[nodiscard]] TextSelectionDropResult moveTextSelectionToDropTarget(
const std::vector<TextNodeSpan> &source,
const Markdown::PreparedEditDropTarget &target);
[[nodiscard]] TextSelectionDropResult moveTextSelectionToDropTarget(
const TextNodeSpan &source,
const Markdown::PreparedEditDropTarget &target);
[[nodiscard]] ApplyResult applyFormattingToTextSpans(
const std::vector<TextNodeSpan> &spans,
TextFormattingAction action,
std::optional<bool> enabled = std::nullopt);
[[nodiscard]] bool toggleSpoilerOnBlocks(
const std::vector<BlockPath> &blocks,
std::optional<bool> enabled = std::nullopt);
[[nodiscard]] bool toggleSpoilerOnGroupedItem(
const BlockPath &path,
int itemIndex,
std::optional<bool> enabled = std::nullopt);
[[nodiscard]] std::optional<ReplaceTarget> replaceTargetForBlock(
const BlockPath &path) const;
[[nodiscard]] std::optional<ReplaceTarget> replaceTargetForGroupedItem(
const BlockPath &path,
int itemIndex) const;
[[nodiscard]] bool replaceBlockWithPreparedBlock(
const ReplaceTarget &target,
RichPage::Block block);
[[nodiscard]] std::optional<int> removeBlock(
const BlockPath &path,
bool forward);
[[nodiscard]] bool canGroupPhotoVideoBlocks(
const Markdown::PreparedEditSelection &selection) const;
[[nodiscard]] bool groupPhotoVideoBlocks(
const Markdown::PreparedEditSelection &selection,
RichPage::GroupedMediaIntent intent);
[[nodiscard]] bool ungroupGroupedMediaBlock(const BlockPath &path);
[[nodiscard]] bool removeGroupedItem(
const BlockPath &path,
int itemIndex);
[[nodiscard]] bool addItemsToGroupedMedia(
const BlockPath &path,
int insertedCount);
[[nodiscard]] bool setGroupedMediaIntent(
const BlockPath &path,
RichPage::GroupedMediaIntent intent);
[[nodiscard]] Markdown::PreparedEditSelection preparedSelectionForBlock(
const BlockPath &path) const;
private:
struct StructuralBlockRange {
BlockContainerPath container;
int from = -1;
int till = -1;
};
struct StructuralListItemRange {
BlockPath block;
int from = -1;
int till = -1;
};
struct StructuralTableRowRange {
BlockPath block;
int from = -1;
int till = -1;
};
struct StructuralTableCellRange {
BlockPath block;
int rowFrom = -1;
int rowTill = -1;
int columnFrom = -1;
int columnTill = -1;
};
struct ActiveNonPullquoteQuote {
BlockPath path;
bool activeLeafIsLastEditableBodyLeaf = false;
};
struct ActiveListItemSurface {
BlockPath path;
int itemIndex = -1;
};
struct ParagraphTarget {
LeafPath leaf;
bool inserted = false;
};
struct ActiveTextInsertTarget {
LeafPath leaf;
InsertionAnchor anchor;
};
struct ActiveTextSelectionTarget {
LeafPath leaf;
int selectionFrom = 0;
int selectionTo = 0;
};
struct RebuiltBoundaryTarget {
BoundaryTarget::Action action = BoundaryTarget::Action::None;
LeafPath leaf;
BlockPath block;
int listItemIndex = -1;
};
template <typename Result>
struct CheckedMutationResult {
bool apply = false;
Result result;
};
[[nodiscard]] std::optional<BlockContainerPath> convertBlockContainerPath(
const Markdown::PreparedEditBlockContainerPath &path) const;
[[nodiscard]] std::optional<LeafPath> convertLeafPath(
const Markdown::PreparedEditLeafSource &source) const;
[[nodiscard]] std::optional<StructuralBlockRange> validateBlockRange(
const Markdown::PreparedEditBlockRange &range) const;
[[nodiscard]] std::optional<StructuralListItemRange> validateListItemRange(
const Markdown::PreparedEditListItemRange &range) const;
[[nodiscard]] std::optional<StructuralTableRowRange> validateTableRowRange(
const Markdown::PreparedEditTableRowRange &range) const;
[[nodiscard]] auto validateTableCellRange(
const Markdown::PreparedEditTableCellRange &range) const
-> std::optional<StructuralTableCellRange>;
[[nodiscard]] bool leafWillBeRemoved(
const LeafPath &path,
const StructuralBlockRange &range) const;
[[nodiscard]] bool leafWillBeRemoved(
const LeafPath &path,
const StructuralListItemRange &range) const;
[[nodiscard]] bool leafWillBeRemoved(
const LeafPath &path,
const StructuralTableRowRange &range) const;
[[nodiscard]] bool leafWillBeRemoved(
const LeafPath &path,
const StructuralTableCellRange &range) const;
[[nodiscard]] bool leafBelongsToBlock(
const LeafPath &leaf,
const BlockPath &path) const;
[[nodiscard]] std::optional<LeafPath> firstSelectedLeaf(
const StructuralTableCellRange &range) const;
[[nodiscard]] std::optional<LeafPath> adjacentLeafOutsideRange(
const StructuralBlockRange &range,
bool forward) const;
[[nodiscard]] std::optional<LeafPath> adjacentLeafOutsideRange(
const StructuralListItemRange &range,
bool forward) const;
[[nodiscard]] std::optional<LeafPath> adjacentLeafOutsideRange(
const StructuralTableRowRange &range,
bool forward) const;
[[nodiscard]] std::optional<LeafPath> adjacentLeafOutsideRange(
const StructuralTableCellRange &range,
bool forward) const;
[[nodiscard]] std::optional<LeafPath> fallbackFocusLeaf() const;
[[nodiscard]] std::optional<LeafPath> plannedFocusForRange(
const StructuralBlockRange &range,
bool forward) const;
[[nodiscard]] std::optional<LeafPath> plannedFocusForRange(
const StructuralListItemRange &range,
bool forward) const;
[[nodiscard]] std::optional<LeafPath> plannedFocusForRange(
const StructuralTableRowRange &range,
bool forward) const;
[[nodiscard]] std::optional<LeafPath> plannedFocusForRange(
const StructuralTableCellRange &range,
bool forward) const;
[[nodiscard]] std::vector<RichPage::Block> *blockContainer(
const BlockContainerPath &path);
[[nodiscard]] const std::vector<RichPage::Block> *blockContainer(
const BlockContainerPath &path) const;
[[nodiscard]] RichPage::Block *block(const BlockPath &path);
[[nodiscard]] const RichPage::Block *block(const BlockPath &path) const;
[[nodiscard]] RichPage::ListItem *listItem(
const BlockPath &blockPath,
int itemIndex);
[[nodiscard]] const RichPage::ListItem *listItem(
const BlockPath &blockPath,
int itemIndex) const;
[[nodiscard]] RichPage::TableCell *tableCell(
const BlockPath &blockPath,
int rowIndex,
int cellIndex);
[[nodiscard]] const RichPage::TableCell *tableCell(
const BlockPath &blockPath,
int rowIndex,
int cellIndex) const;
[[nodiscard]] RichPage::RichText *richText(const LeafPath &path);
[[nodiscard]] const RichPage::RichText *richText(
const LeafPath &path) const;
[[nodiscard]] QString *rawText(const LeafPath &path);
[[nodiscard]] const QString *rawText(const LeafPath &path) const;
[[nodiscard]] const TextNodeDescriptor *textNode(int ordinal) const;
[[nodiscard]] const TextNodeDescriptor *adjacentTextNode(
int ordinal,
bool forward) const;
[[nodiscard]] int textNodeOrdinal(const LeafPath &path) const;
[[nodiscard]] auto convertPreparedLeafSource(const LeafPath &path) const
-> std::optional<Markdown::PreparedEditLeafSource>;
[[nodiscard]] auto convertPreparedLeafSource(
const TextNodeDescriptor &descriptor) const
-> std::optional<Markdown::PreparedEditLeafSource>;
[[nodiscard]] auto tableRenderLimits() const
-> Markdown::MarkdownPrepareTableRenderLimits;
void rebuild();
void rebuildPrepared();
void rebuildTextNodes();
void rebuildTextNodes(
const std::vector<RichPage::Block> &blocks,
const BlockContainerPath &container);
void clearTemporaryDownParagraph();
void clearTemporaryDownParagraphIfInvalid();
void commitCheckedMutation(State state);
template <typename Result, typename Callback>
[[nodiscard]] Result applyCheckedMutation(
Result failure,
Callback &&callback);
[[nodiscard]] std::optional<int> activateRebuiltLeaf(
const LeafPath &path);
[[nodiscard]] InsertionAnchor resolveActiveInsertionTarget() const;
[[nodiscard]] std::optional<int> normalizeTextOnlyListItemForInsertion(
const BlockContainerPath &container);
[[nodiscard]] std::optional<int> normalizeTextOnlyQuoteSurface(
const BlockContainerPath &container,
bool keepEmptyParagraph);
[[nodiscard]] std::optional<int> normalizeTextOnlyQuoteForInsertion(
const BlockContainerPath &container);
[[nodiscard]] bool normalizeTextOnlyContainerForInsertion(
const BlockContainerPath &container,
int *insertAt);
[[nodiscard]] bool shouldReplaceActiveTextOnlyBlock(
const TextNodeDescriptor &descriptor,
const std::vector<RichPage::Block> &blocks) const;
[[nodiscard]] std::optional<ParagraphTarget> reuseOrInsertParagraph(
const BlockContainerPath &container,
int index);
[[nodiscard]] auto resolveActiveTextInsertTarget()
-> std::optional<ActiveTextInsertTarget>;
[[nodiscard]] auto activeNonPullquoteQuote() const
-> std::optional<ActiveNonPullquoteQuote>;
[[nodiscard]] auto activeListItemSurface() const
-> std::optional<ActiveListItemSurface>;
[[nodiscard]] std::optional<LeafPath> leafAfterUnwrappingBlockChildren(
const LeafPath &leaf,
const BlockPath &wrapper) const;
[[nodiscard]] bool unwrapActiveCodeBlockUnchecked(
const ActiveTextInsertContext &context,
ActiveTextSelectionTarget *target);
[[nodiscard]] bool unwrapActiveBlockquoteUnchecked(
const ActiveTextInsertContext &context,
ActiveTextSelectionTarget *target);
[[nodiscard]] bool joinActiveParagraphBoundaryUnchecked(
bool forward,
ActiveTextSelectionTarget *target);
[[nodiscard]] auto normalizeActiveListItemSurface()
-> std::optional<ActiveListItemSurface>;
[[nodiscard]] ApplyResult applyActiveTextUnchecked(TextWithEntities text);
[[nodiscard]] ApplyResult applyActiveRawTextUnchecked(QString text);
[[nodiscard]] ApplyResult applyActiveTextWithLocalLimit(
TextWithEntities text);
[[nodiscard]] ApplyResult applyActiveRawTextWithLocalLimit(QString text);
[[nodiscard]] ApplyResult applySplitParagraphText(
const TextNodeDescriptor &descriptor,
std::vector<TextWithEntities> chunks);
[[nodiscard]] bool leafMutationKeepsTextNodes(
const TextNodeDescriptor &descriptor) const;
[[nodiscard]] bool updatePreparedActiveLeaf(
const TextNodeDescriptor &descriptor);
[[nodiscard]] bool addTableRowUnchecked(
const Markdown::PreparedEditTableCellRange &range,
bool after);
[[nodiscard]] bool addTableColumnUnchecked(
const Markdown::PreparedEditTableCellRange &range,
bool after);
[[nodiscard]] std::optional<int> ensureTrailingParagraphActiveUnchecked();
[[nodiscard]] std::optional<int> insertLeadingParagraphActiveUnchecked(
bool focusInserted);
[[nodiscard]] std::optional<int> moveActiveSpecialBlockDownUnchecked();
[[nodiscard]] std::optional<int> submitActiveSingleLineFieldUnchecked(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> escapeActiveBlockBodyUnchecked();
[[nodiscard]] std::optional<BlockPath> activeBlockBodyEscapeBlock() const;
[[nodiscard]] BoundaryTarget boundaryTargetForLeaf(
const LeafPath &leaf,
const TextNodeDescriptor *descriptor,
bool forward,
bool allowRemoveDirectly) const;
[[nodiscard]] auto captureRebuiltBoundaryTarget(
const BoundaryTarget &target) const
-> std::optional<RebuiltBoundaryTarget>;
void shiftRebuiltBoundaryTargetAfterRemovedBlock(
RebuiltBoundaryTarget &target,
const BlockPath &removed) const;
[[nodiscard]] BoundaryTarget materializeBoundaryTarget(
const RebuiltBoundaryTarget &target) const;
[[nodiscard]] BoundaryTarget removeTemporaryDownParagraphAndMoveUnchecked();
[[nodiscard]] std::optional<int> handleActiveHeadingEnterUnchecked(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveFooterEnterUnchecked(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveListEnterUnchecked(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveParagraphEnterUnchecked(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveQuoteEnterUnchecked(
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleActiveBlockEnterUnchecked(
RichPage::BlockKind kind,
const ActiveEnterContext &context);
[[nodiscard]] std::optional<int> handleEnterAtBlockUnchecked(
const BlockContainerPath &container,
int index,
const ActiveEnterContext &context);
[[nodiscard]] bool insertBlocksAfterActiveUnchecked(
std::vector<RichPage::Block> blocks,
std::optional<ActiveTextInsertContext> context = std::nullopt);
[[nodiscard]] bool insertPreparedBlocksAtExplicitPosition(
std::vector<RichPage::Block> blocks,
const BlockContainerPath &container,
int *insertAt);
[[nodiscard]] bool insertPreparedBlocksAtRemovedBlockRange(
std::vector<RichPage::Block> blocks,
const StructuralBlockRange &range);
[[nodiscard]] bool wrapStructuralBlockSelection(
const Markdown::PreparedEditSelection &selection,
InsertAction action,
BoundaryTarget *destination = nullptr);
[[nodiscard]] bool unwrapMatchingStructuralWrapper(
const Markdown::PreparedEditSelection &selection,
InsertBlockType type,
BoundaryTarget *destination = nullptr);
[[nodiscard]] bool unwrapMatchingListItemWrapper(
const Markdown::PreparedEditSelection &selection,
InsertBlockType type,
BoundaryTarget *destination = nullptr);
[[nodiscard]] std::vector<RichPage::Block> takeListItemBlocksForUnwrap(
RichPage::ListItem *item);
void adoptLeadingParagraphListItemText(RichPage::ListItem *item) const;
[[nodiscard]] bool insertPreparedListItemsAtExplicitPosition(
std::vector<RichPage::ListItem> items,
const BlockPath &path,
int insertAt);
[[nodiscard]] bool insertBlocksAfterActiveWithContextUnchecked(
std::vector<RichPage::Block> &blocks,
const ActiveTextInsertContext &context);
[[nodiscard]] RichPage::RichText *seedInsertedBlocks(
std::vector<RichPage::Block> &blocks,
TextWithEntities text);
[[nodiscard]] RichPage::RichText *seedInsertedBlock(
RichPage::Block &block);
[[nodiscard]] bool appendInsertedTrailingText(
const BlockContainerPath &container,
int insertAt,
int count,
TextWithEntities text);
void normalizeInsertedBlockAnchors(std::vector<RichPage::Block> &blocks);
void normalizeInsertedBlockAnchors(
std::vector<RichPage::Block> &root,
RichPage::Block &block);
void normalizeInsertedRichTextAnchors(
std::vector<RichPage::Block> &root,
RichPage::RichText &text);
void appendBlockTextNode(
const BlockPath &path,
LeafKind kind,
FieldMode mode = FieldMode::Rich,
std::optional<InsertionAnchor> insertionAnchor = std::nullopt);
void appendListItemTextNode(const BlockPath &path, int itemIndex);
void appendTableCellTextNode(
const BlockPath &path,
int rowIndex,
int cellIndex);
void ensureActiveTextOrdinal();
void ensureEditableNodes();
void focusInsertedBlocks(
const BlockContainerPath &container,
int from,
int count);
[[nodiscard]] BoundaryTarget destinationTargetForInsertedBlocks(
const BlockContainerPath &container,
int from,
int count);
[[nodiscard]] BoundaryTarget destinationTargetForInsertedListItems(
const BlockPath &path,
int from,
int count);
[[nodiscard]] std::optional<int> adjacentEditableOrdinal(
bool forward) const;
void collectBoundarySteps(
const std::vector<RichPage::Block> &blocks,
const BlockContainerPath &container,
bool forward,
std::vector<BoundaryTarget> *steps) const;
void appendBoundaryTextStep(
LeafPath leaf,
std::vector<BoundaryTarget> *steps) const;
void appendBoundaryBlockStep(
const BlockPath &path,
std::vector<BoundaryTarget> *steps) const;
void appendBoundaryListItemStep(
const BlockPath &path,
int itemIndex,
std::vector<BoundaryTarget> *steps) const;
[[nodiscard]] Markdown::PreparedEditSelection preparedSelectionForListItem(
const BlockPath &path,
int itemIndex) const;
[[nodiscard]] bool shouldRemoveActiveOwnerDirectly(
const TextNodeDescriptor &descriptor) const;
[[nodiscard]] bool descriptorBelongsToBlock(
const TextNodeDescriptor &descriptor,
const BlockPath &path) const;
[[nodiscard]] bool removalTargetIsEmpty(
const RemovalTarget &target) const;
[[nodiscard]] bool removeTarget(const RemovalTarget &target);
[[nodiscard]] bool anchorIdExists(const QString &id) const;
[[nodiscard]] bool anchorIdExists(
const std::vector<RichPage::Block> &blocks,
const QString &id) const;
[[nodiscard]] QString nextAnchorId() const;
[[nodiscard]] RichPage::Block makeBlock(InsertAction action) const;
[[nodiscard]] static TextWithEntities MakeText(QString text);
[[nodiscard]] static RichPage::Block MakeParagraphBlock();
[[nodiscard]] static RichPage::Block MakeFooterBlock();
[[nodiscard]] static RichPage::Block MakeHeadingBlock(int level);
[[nodiscard]] static RichPage::Block MakeQuoteBlock(bool pullquote);
[[nodiscard]] static RichPage::Block MakeCodeBlock();
[[nodiscard]] static RichPage::Block MakeMathBlock();
[[nodiscard]] static RichPage::Block MakeDividerBlock();
[[nodiscard]] static RichPage::Block MakeAnchorBlock(QString anchorId);
[[nodiscard]] static RichPage::Block MakeListBlock(
RichPage::ListKind kind,
RichPage::TaskState taskState = RichPage::TaskState::None);
[[nodiscard]] static RichPage::ListItem MakeParagraphListItem(
RichPage::TaskState taskState);
[[nodiscard]] static RichPage::Block MakeDetailsBlock();
[[nodiscard]] static RichPage::Block MakeTableBlock();
[[nodiscard]] static RichPage::Block MakeMediaBlock(
RichPage::BlockKind kind);
[[nodiscard]] static RichPage::Block MakeMapBlock(
double latitude,
double longitude);
[[nodiscard]] static bool RichTextIsEmpty(const RichPage::RichText &text);
[[nodiscard]] static bool ListItemIsEmpty(
const RichPage::ListItem &item);
[[nodiscard]] static bool BlockIsEmpty(const RichPage::Block &block);
[[nodiscard]] static bool StripWrapperEntityInEditMode(EntityType type);
[[nodiscard]] static TextWithEntities StripEditModeWrapperEntities(
TextWithEntities text);
std::shared_ptr<RichPage> _richPage;
std::shared_ptr<Markdown::MediaRuntime> _mediaRuntime;
RichMessageLimits _limits;
Markdown::MarkdownArticleContent _prepared;
std::vector<TextNodeDescriptor> _textNodes;
int _activeTextOrdinal = -1;
PreparedMutationKind _lastPreparedMutationKind
= PreparedMutationKind::None;
std::optional<RichMessageLimitError> _lastLimitError;
std::optional<LeafPath> _temporaryDownParagraph;
};
enum class RequestMediaType : uchar {
PhotoVideo,
Audio,
PhotoVideoAudio,
};
struct MediaUploadState {
bool uploading = false;
};
[[nodiscard]] bool CanEditRichPage(const RichPage &page);
[[nodiscard]] bool CanEditRichPage(
const std::shared_ptr<const RichPage> &page);
} // namespace Iv::Editor