Use the rich text char format to store mentions rather than a separate structure in ChatBarCache.
This removes mentions from ChatBarCache and instead sets mentions as an anchor using the QTextCursor. Saving and restoring the chatbar text content is then done using QTextDocumentFragments which retain the rich text formatting.
This commit is contained in:
@@ -400,7 +400,6 @@ void ModelTest::testCompletionModel()
|
|||||||
auto model = new CompletionModel(this);
|
auto model = new CompletionModel(this);
|
||||||
auto tester = new QAbstractItemModelTester(model, model);
|
auto tester = new QAbstractItemModelTester(model, model);
|
||||||
tester->setUseFetchMore(true);
|
tester->setUseFetchMore(true);
|
||||||
model->setRoom(room);
|
|
||||||
model->setAutoCompletionType(CompletionModel::Room);
|
model->setAutoCompletionType(CompletionModel::Room);
|
||||||
auto roomListModel = new RoomListModel(this);
|
auto roomListModel = new RoomListModel(this);
|
||||||
roomListModel->setConnection(connection);
|
roomListModel->setConnection(connection);
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ QQC2.Control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
readonly property LibNeoChat.CompletionModel completionModel: LibNeoChat.CompletionModel {
|
readonly property LibNeoChat.CompletionModel completionModel: LibNeoChat.CompletionModel {
|
||||||
room: root.room
|
|
||||||
type: root.chatBarType
|
|
||||||
textItem: root.model.focusedTextItem
|
textItem: root.model.focusedTextItem
|
||||||
roomListModel: RoomManager.roomListModel
|
roomListModel: RoomManager.roomListModel
|
||||||
userListModel: RoomManager.userListModel
|
userListModel: RoomManager.userListModel
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ target_sources(LibNeoChat PRIVATE
|
|||||||
neochatroommember.cpp
|
neochatroommember.cpp
|
||||||
accountmanager.cpp
|
accountmanager.cpp
|
||||||
chatbarcache.cpp
|
chatbarcache.cpp
|
||||||
|
blockcache.cpp
|
||||||
chatbarsyntaxhighlighter.cpp
|
chatbarsyntaxhighlighter.cpp
|
||||||
chatkeyhelper.cpp
|
chatkeyhelper.cpp
|
||||||
chatmarkdownhelper.cpp
|
chatmarkdownhelper.cpp
|
||||||
|
|||||||
25
src/libneochat/blockcache.cpp
Normal file
25
src/libneochat/blockcache.cpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include "blockcache.h"
|
||||||
|
|
||||||
|
#include "chattextitemhelper.h"
|
||||||
|
|
||||||
|
using namespace Block;
|
||||||
|
|
||||||
|
void Cache::fill(QList<MessageComponent> components)
|
||||||
|
{
|
||||||
|
std::ranges::for_each(components, [this](const MessageComponent &component) {
|
||||||
|
if (!MessageComponentType::isTextType(component.type)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto textItem = component.attributes["chatTextItemHelper"_L1].value<ChatTextItemHelper *>();
|
||||||
|
if (!textItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
append(CacheItem{
|
||||||
|
.type = component.type,
|
||||||
|
.content = textItem->toFragment(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
28
src/libneochat/blockcache.h
Normal file
28
src/libneochat/blockcache.h
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2026 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QList>
|
||||||
|
#include <QTextDocumentFragment>
|
||||||
|
|
||||||
|
#include "enums/messagecomponenttype.h"
|
||||||
|
#include "messagecomponent.h"
|
||||||
|
|
||||||
|
namespace Block
|
||||||
|
{
|
||||||
|
struct CacheItem {
|
||||||
|
MessageComponentType::Type type = MessageComponentType::Other;
|
||||||
|
QTextDocumentFragment content;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Cache : private QList<CacheItem>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using QList<CacheItem>::constBegin, QList<CacheItem>::constEnd;
|
||||||
|
using QList<CacheItem>::isEmpty;
|
||||||
|
using QList<CacheItem>::clear;
|
||||||
|
|
||||||
|
void fill(QList<MessageComponent> components);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -33,6 +33,11 @@ ChatBarCache::ChatBarCache(QObject *parent)
|
|||||||
connect(this, &ChatBarCache::relationIdChanged, this, &ChatBarCache::relationAuthorIsPresentChanged);
|
connect(this, &ChatBarCache::relationIdChanged, this, &ChatBarCache::relationAuthorIsPresentChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Block::Cache &ChatBarCache::cache()
|
||||||
|
{
|
||||||
|
return m_cache;
|
||||||
|
}
|
||||||
|
|
||||||
QString ChatBarCache::text() const
|
QString ChatBarCache::text() const
|
||||||
{
|
{
|
||||||
return m_text;
|
return m_text;
|
||||||
@@ -55,27 +60,7 @@ QString ChatBarCache::sendText() const
|
|||||||
return text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : text();
|
return text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : text();
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatMentions();
|
return text();
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatBarCache::formatMentions() const
|
|
||||||
{
|
|
||||||
auto mentions = m_mentions;
|
|
||||||
std::sort(mentions.begin(), mentions.end(), [](const auto &a, const auto &b) {
|
|
||||||
return a.cursor.anchor() > b.cursor.anchor();
|
|
||||||
});
|
|
||||||
|
|
||||||
auto formattedText = text();
|
|
||||||
for (const auto &mention : mentions) {
|
|
||||||
if (mention.text.isEmpty() || mention.id.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
formattedText = formattedText.replace(mention.cursor.anchor(),
|
|
||||||
mention.cursor.position() - mention.cursor.anchor(),
|
|
||||||
u"[%1](https://matrix.to/#/%2)"_s.arg(mention.text.toHtmlEscaped(), mention.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
return formattedText;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ChatBarCache::isReplying() const
|
bool ChatBarCache::isReplying() const
|
||||||
@@ -232,11 +217,6 @@ void ChatBarCache::clearRelations()
|
|||||||
Q_EMIT attachmentPathChanged();
|
Q_EMIT attachmentPathChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<Mention> *ChatBarCache::mentions()
|
|
||||||
{
|
|
||||||
return &m_mentions;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString ChatBarCache::savedText() const
|
QString ChatBarCache::savedText() const
|
||||||
{
|
{
|
||||||
return m_savedText;
|
return m_savedText;
|
||||||
@@ -294,7 +274,6 @@ void ChatBarCache::postMessage()
|
|||||||
void ChatBarCache::clearCache()
|
void ChatBarCache::clearCache()
|
||||||
{
|
{
|
||||||
setText({});
|
setText({});
|
||||||
m_mentions.clear();
|
|
||||||
m_savedText = QString();
|
m_savedText = QString();
|
||||||
clearRelations();
|
clearRelations();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,22 +8,13 @@
|
|||||||
#include <QQuickTextDocument>
|
#include <QQuickTextDocument>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
|
||||||
|
#include "blockcache.h"
|
||||||
|
|
||||||
namespace Quotient
|
namespace Quotient
|
||||||
{
|
{
|
||||||
class RoomMember;
|
class RoomMember;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Defines a user mention in the current chat or edit text.
|
|
||||||
*/
|
|
||||||
struct Mention {
|
|
||||||
QTextCursor cursor; /**< Contains the mention's text and position in the text. */
|
|
||||||
QString text; /**< The inserted text of the mention. */
|
|
||||||
int start = 0; /**< Start position of the mention. */
|
|
||||||
int position = 0; /**< End position of the mention. */
|
|
||||||
QString id; /**< The id the mention (used to create link when sending the message). */
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class ChatBarCache
|
* @class ChatBarCache
|
||||||
*
|
*
|
||||||
@@ -147,6 +138,8 @@ public:
|
|||||||
|
|
||||||
explicit ChatBarCache(QObject *parent = nullptr);
|
explicit ChatBarCache(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
Block::Cache &cache();
|
||||||
|
|
||||||
QString text() const;
|
QString text() const;
|
||||||
QString sendText() const;
|
QString sendText() const;
|
||||||
void setText(const QString &text);
|
void setText(const QString &text);
|
||||||
@@ -178,11 +171,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
Q_INVOKABLE void clearRelations();
|
Q_INVOKABLE void clearRelations();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Retrieve the mentions for the current chat bar text.
|
|
||||||
*/
|
|
||||||
QList<Mention> *mentions();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the saved chat bar text.
|
* @brief Get the saved chat bar text.
|
||||||
*/
|
*/
|
||||||
@@ -209,14 +197,14 @@ Q_SIGNALS:
|
|||||||
void relationAuthorIsPresentChanged();
|
void relationAuthorIsPresentChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
Block::Cache m_cache;
|
||||||
|
|
||||||
QString m_text = QString();
|
QString m_text = QString();
|
||||||
QString formatMentions() const;
|
|
||||||
|
|
||||||
QString m_relationId = QString();
|
QString m_relationId = QString();
|
||||||
RelationType m_relationType = RelationType::None;
|
RelationType m_relationType = RelationType::None;
|
||||||
QString m_threadId = QString();
|
QString m_threadId = QString();
|
||||||
QString m_attachmentPath = QString();
|
QString m_attachmentPath = QString();
|
||||||
QList<Mention> m_mentions;
|
|
||||||
QString m_savedText;
|
QString m_savedText;
|
||||||
|
|
||||||
void clearCache();
|
void clearCache();
|
||||||
|
|||||||
@@ -4,22 +4,16 @@
|
|||||||
|
|
||||||
#include "chatbarsyntaxhighlighter.h"
|
#include "chatbarsyntaxhighlighter.h"
|
||||||
|
|
||||||
#include "chatbarcache.h"
|
|
||||||
#include "chattextitemhelper.h"
|
#include "chattextitemhelper.h"
|
||||||
#include "enums/chatbartype.h"
|
|
||||||
|
|
||||||
ChatBarSyntaxHighlighter::ChatBarSyntaxHighlighter(QObject *parent)
|
ChatBarSyntaxHighlighter::ChatBarSyntaxHighlighter(QObject *parent)
|
||||||
: QSyntaxHighlighter(parent)
|
: QSyntaxHighlighter(parent)
|
||||||
{
|
{
|
||||||
m_theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
m_theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||||
connect(m_theme, &Kirigami::Platform::PlatformTheme::colorsChanged, this, [this]() {
|
connect(m_theme, &Kirigami::Platform::PlatformTheme::colorsChanged, this, [this]() {
|
||||||
m_mentionFormat.setForeground(m_theme->linkColor());
|
|
||||||
m_errorFormat.setForeground(m_theme->negativeTextColor());
|
m_errorFormat.setForeground(m_theme->negativeTextColor());
|
||||||
});
|
});
|
||||||
|
|
||||||
m_mentionFormat.setFontWeight(QFont::Bold);
|
|
||||||
m_mentionFormat.setForeground(m_theme->linkColor());
|
|
||||||
|
|
||||||
m_errorFormat.setForeground(m_theme->negativeTextColor());
|
m_errorFormat.setForeground(m_theme->negativeTextColor());
|
||||||
m_errorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
|
m_errorFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline);
|
||||||
|
|
||||||
@@ -48,36 +42,4 @@ void ChatBarSyntaxHighlighter::highlightBlock(const QString &text)
|
|||||||
setFormat(error.first, error.second.size(), m_errorFormat);
|
setFormat(error.first, error.second.size(), m_errorFormat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!room || type == ChatBarType::None) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto mentions = room->cacheForType(type)->mentions();
|
|
||||||
mentions->erase(std::remove_if(mentions->begin(),
|
|
||||||
mentions->end(),
|
|
||||||
[this](auto &mention) {
|
|
||||||
if (document()->toPlainText().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mention.cursor.position() == 0 && mention.cursor.anchor() == 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mention.cursor.position() - mention.cursor.anchor() != mention.text.size()) {
|
|
||||||
mention.cursor.setPosition(mention.start);
|
|
||||||
mention.cursor.setPosition(mention.cursor.anchor() + mention.text.size(), QTextCursor::KeepAnchor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mention.cursor.selectedText() != mention.text) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (currentBlock() == mention.cursor.block()) {
|
|
||||||
mention.start = mention.cursor.anchor();
|
|
||||||
mention.position = mention.cursor.position();
|
|
||||||
setFormat(mention.cursor.selectionStart(), mention.cursor.selectedText().size(), m_mentionFormat);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}),
|
|
||||||
mentions->end());
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Kirigami::Platform::PlatformTheme *m_theme = nullptr;
|
Kirigami::Platform::PlatformTheme *m_theme = nullptr;
|
||||||
QTextCharFormat m_mentionFormat;
|
|
||||||
QTextCharFormat m_errorFormat;
|
QTextCharFormat m_errorFormat;
|
||||||
|
|
||||||
Sonnet::BackgroundChecker *m_checker = new Sonnet::BackgroundChecker(this);
|
Sonnet::BackgroundChecker *m_checker = new Sonnet::BackgroundChecker(this);
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ void ChatTextItemHelper::setTextItem(QQuickItem *textItem)
|
|||||||
connect(doc, &QTextDocument::contentsChange, this, &ChatTextItemHelper::contentsChange);
|
connect(doc, &QTextDocument::contentsChange, this, &ChatTextItemHelper::contentsChange);
|
||||||
m_highlighter->setDocument(doc);
|
m_highlighter->setDocument(doc);
|
||||||
}
|
}
|
||||||
initializeChars();
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_EMIT textItemChanged();
|
Q_EMIT textItemChanged();
|
||||||
@@ -133,24 +133,21 @@ void ChatTextItemHelper::setFixedChars(const QString &startChars, const QString
|
|||||||
}
|
}
|
||||||
m_fixedStartChars = startChars;
|
m_fixedStartChars = startChars;
|
||||||
m_fixedEndChars = endChars;
|
m_fixedEndChars = endChars;
|
||||||
initializeChars();
|
initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ChatTextItemHelper::initialText() const
|
QTextDocumentFragment ChatTextItemHelper::initialFragment() const
|
||||||
{
|
{
|
||||||
return m_initialText;
|
return m_initialFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatTextItemHelper::setInitialText(const QString &text)
|
void ChatTextItemHelper::setInitialFragment(const QTextDocumentFragment &fragment)
|
||||||
{
|
{
|
||||||
if (text == m_initialText) {
|
m_initialFragment = fragment;
|
||||||
return;
|
initialize();
|
||||||
}
|
|
||||||
m_initialText = text;
|
|
||||||
initializeChars();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatTextItemHelper::initializeChars()
|
void ChatTextItemHelper::initialize()
|
||||||
{
|
{
|
||||||
const auto doc = document();
|
const auto doc = document();
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
@@ -166,8 +163,8 @@ void ChatTextItemHelper::initializeChars()
|
|||||||
|
|
||||||
cursor.beginEditBlock();
|
cursor.beginEditBlock();
|
||||||
int finalCursorPos = cursor.position();
|
int finalCursorPos = cursor.position();
|
||||||
if (doc->isEmpty() && !m_initialText.isEmpty()) {
|
if (doc->isEmpty() && !m_initialFragment.isEmpty()) {
|
||||||
cursor.insertText(m_initialText);
|
cursor.insertFragment(m_initialFragment);
|
||||||
finalCursorPos = cursor.position();
|
finalCursorPos = cursor.position();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -618,6 +615,16 @@ QString ChatTextItemHelper::plainText() const
|
|||||||
return trim(doc->toPlainText());
|
return trim(doc->toPlainText());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QTextDocumentFragment ChatTextItemHelper::toFragment() const
|
||||||
|
{
|
||||||
|
auto cursor = textCursor();
|
||||||
|
if (cursor.isNull()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
cursor.select(QTextCursor::Document);
|
||||||
|
return cursor.selection();
|
||||||
|
}
|
||||||
|
|
||||||
QString ChatTextItemHelper::trim(QString string) const
|
QString ChatTextItemHelper::trim(QString string) const
|
||||||
{
|
{
|
||||||
while (string.startsWith(u"\n"_s)) {
|
while (string.startsWith(u"\n"_s)) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
|
#include <QTextDocumentFragment>
|
||||||
|
#include <qtextdocumentfragment.h>
|
||||||
|
|
||||||
#include "enums/chatbartype.h"
|
#include "enums/chatbartype.h"
|
||||||
#include "enums/richformat.h"
|
#include "enums/richformat.h"
|
||||||
@@ -108,16 +110,16 @@ public:
|
|||||||
void setFixedChars(const QString &startChars, const QString &endChars);
|
void setFixedChars(const QString &startChars, const QString &endChars);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Any text to initialise the text item with when set.
|
* @brief Any QTextDocumentFragment to initialise the text item with when set.
|
||||||
*/
|
*/
|
||||||
QString initialText() const;
|
QTextDocumentFragment initialFragment() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set the text to initialise the text item with when set.
|
* @brief Set the QTextDocumentFragment to initialise the text item with when set.
|
||||||
*
|
*
|
||||||
* This text will only be set if the text item is empty when set.
|
* This text will only be set if the text item is empty when set.
|
||||||
*/
|
*/
|
||||||
void setInitialText(const QString &text);
|
void setInitialFragment(const QTextDocumentFragment &fragment);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The underlying QTextDocument.
|
* @brief The underlying QTextDocument.
|
||||||
@@ -248,6 +250,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
QString plainText() const;
|
QString plainText() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Output the text in the text item as a QTextDocumentFragment.
|
||||||
|
*/
|
||||||
|
QTextDocumentFragment toFragment() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
/**
|
/**
|
||||||
* @brief Emitted when the NeoChatRoom used by the syntax highlighter is changed.
|
* @brief Emitted when the NeoChatRoom used by the syntax highlighter is changed.
|
||||||
@@ -309,8 +316,8 @@ private:
|
|||||||
|
|
||||||
QString m_fixedStartChars = {};
|
QString m_fixedStartChars = {};
|
||||||
QString m_fixedEndChars = {};
|
QString m_fixedEndChars = {};
|
||||||
QString m_initialText = {};
|
QTextDocumentFragment m_initialFragment = {};
|
||||||
void initializeChars();
|
void initialize();
|
||||||
bool m_initializingChars = false;
|
bool m_initializingChars = false;
|
||||||
|
|
||||||
std::optional<int> lineLength(int lineNumber) const;
|
std::optional<int> lineLength(int lineNumber) const;
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QTextCursor>
|
#include <QTextCursor>
|
||||||
|
|
||||||
|
#include <Kirigami/Platform/PlatformTheme>
|
||||||
|
|
||||||
#include "chattextitemhelper.h"
|
#include "chattextitemhelper.h"
|
||||||
#include "completionproxymodel.h"
|
#include "completionproxymodel.h"
|
||||||
#include "models/actionsmodel.h"
|
#include "models/actionsmodel.h"
|
||||||
@@ -24,35 +26,6 @@ CompletionModel::CompletionModel(QObject *parent)
|
|||||||
m_emojiModel->addSourceModel(&EmojiModel::instance());
|
m_emojiModel->addSourceModel(&EmojiModel::instance());
|
||||||
}
|
}
|
||||||
|
|
||||||
NeoChatRoom *CompletionModel::room() const
|
|
||||||
{
|
|
||||||
return m_room;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompletionModel::setRoom(NeoChatRoom *room)
|
|
||||||
{
|
|
||||||
if (m_room == room) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_room = room;
|
|
||||||
Q_EMIT roomChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatBarType::Type CompletionModel::type() const
|
|
||||||
{
|
|
||||||
return m_type;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CompletionModel::setType(ChatBarType::Type type)
|
|
||||||
{
|
|
||||||
if (type == m_type) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_type = type;
|
|
||||||
Q_EMIT typeChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatTextItemHelper *CompletionModel::textItem() const
|
ChatTextItemHelper *CompletionModel::textItem() const
|
||||||
{
|
{
|
||||||
return m_textItem;
|
return m_textItem;
|
||||||
@@ -322,29 +295,22 @@ void CompletionModel::insertCompletion(const QString &text, const QUrl &link)
|
|||||||
}
|
}
|
||||||
cursor.removeSelectedText();
|
cursor.removeSelectedText();
|
||||||
|
|
||||||
const int start = cursor.position();
|
const auto previousFormat = cursor.charFormat();
|
||||||
const auto insertString = u"%1 %2"_s.arg(text, link.isEmpty() ? QString() : u" "_s);
|
auto charFormat = previousFormat;
|
||||||
cursor.insertText(insertString);
|
if (link.isValid()) {
|
||||||
cursor.setPosition(start);
|
const auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||||
cursor.setPosition(start + text.size(), QTextCursor::KeepAnchor);
|
charFormat = QTextCharFormat();
|
||||||
cursor.setKeepPositionOnInsert(true);
|
charFormat.setForeground(theme->linkColor());
|
||||||
cursor.endEditBlock();
|
charFormat.setFontWeight(QFont::Bold);
|
||||||
if (!link.isEmpty()) {
|
charFormat.setAnchor(true);
|
||||||
pushMention({
|
charFormat.setAnchorHref(link.toString());
|
||||||
.cursor = cursor,
|
|
||||||
.text = text,
|
|
||||||
.id = link.toString(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
cursor.insertText(text, charFormat);
|
||||||
|
if (!link.isEmpty()) {
|
||||||
|
cursor.insertText(u" "_s, previousFormat);
|
||||||
|
}
|
||||||
|
cursor.endEditBlock();
|
||||||
m_textItem->rehighlight();
|
m_textItem->rehighlight();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompletionModel::pushMention(const Mention mention) const
|
|
||||||
{
|
|
||||||
if (!m_room || m_type == ChatBarType::None) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_room->cacheForType(m_type)->mentions()->push_back(mention);
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "moc_completionmodel.cpp"
|
#include "moc_completionmodel.cpp"
|
||||||
|
|||||||
@@ -30,16 +30,6 @@ class CompletionModel : public QAbstractListModel
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The current room that the text document is being handled for.
|
|
||||||
*/
|
|
||||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief The ChatBarType::Type of the chat bar.
|
|
||||||
*/
|
|
||||||
Q_PROPERTY(ChatBarType::Type type READ type WRITE setType NOTIFY typeChanged)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The QML text Item that completions are being provided for.
|
* @brief The QML text Item that completions are being provided for.
|
||||||
*/
|
*/
|
||||||
@@ -94,12 +84,6 @@ public:
|
|||||||
|
|
||||||
explicit CompletionModel(QObject *parent = nullptr);
|
explicit CompletionModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
NeoChatRoom *room() const;
|
|
||||||
void setRoom(NeoChatRoom *room);
|
|
||||||
|
|
||||||
ChatBarType::Type type() const;
|
|
||||||
void setType(ChatBarType::Type type);
|
|
||||||
|
|
||||||
ChatTextItemHelper *textItem() const;
|
ChatTextItemHelper *textItem() const;
|
||||||
void setTextItem(ChatTextItemHelper *textItem);
|
void setTextItem(ChatTextItemHelper *textItem);
|
||||||
|
|
||||||
@@ -140,8 +124,6 @@ public:
|
|||||||
Q_INVOKABLE void insertCompletion(const QString &text, const QUrl &link);
|
Q_INVOKABLE void insertCompletion(const QString &text, const QUrl &link);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void roomChanged();
|
|
||||||
void typeChanged();
|
|
||||||
void textItemChanged();
|
void textItemChanged();
|
||||||
void autoCompletionTypeChanged();
|
void autoCompletionTypeChanged();
|
||||||
void roomListModelChanged();
|
void roomListModelChanged();
|
||||||
@@ -149,8 +131,6 @@ Q_SIGNALS:
|
|||||||
void isCompletingChanged();
|
void isCompletingChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<NeoChatRoom> m_room;
|
|
||||||
ChatBarType::Type m_type = ChatBarType::None;
|
|
||||||
QPointer<ChatTextItemHelper> m_textItem;
|
QPointer<ChatTextItemHelper> m_textItem;
|
||||||
|
|
||||||
bool m_ignoreCurrentCompletion = false;
|
bool m_ignoreCurrentCompletion = false;
|
||||||
@@ -165,6 +145,4 @@ private:
|
|||||||
UserListModel *m_userListModel;
|
UserListModel *m_userListModel;
|
||||||
RoomListModel *m_roomListModel;
|
RoomListModel *m_roomListModel;
|
||||||
QConcatenateTablesProxyModel *m_emojiModel;
|
QConcatenateTablesProxyModel *m_emojiModel;
|
||||||
|
|
||||||
void pushMention(const Mention mention) const;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ QQC2.TextArea {
|
|||||||
*/
|
*/
|
||||||
property bool isReply: false
|
property bool isReply: false
|
||||||
|
|
||||||
Layout.fillWidth: NeoChatConfig.compactLayout
|
Layout.fillWidth: true
|
||||||
Layout.maximumWidth: Message.maxContentWidth
|
Layout.maximumWidth: Message.maxContentWidth
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: (event) => {
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ void ChatBarMessageContentModel::initializeModel(const QString &initialText)
|
|||||||
const auto textItem = new ChatTextItemHelper(this);
|
const auto textItem = new ChatTextItemHelper(this);
|
||||||
textItem->setRoom(m_room);
|
textItem->setRoom(m_room);
|
||||||
textItem->setType(m_type);
|
textItem->setType(m_type);
|
||||||
textItem->setInitialText(initialText);
|
textItem->setInitialFragment(QTextDocumentFragment::fromPlainText(initialText));
|
||||||
connectTextItem(textItem);
|
connectTextItem(textItem);
|
||||||
m_components += MessageComponent{
|
m_components += MessageComponent{
|
||||||
.type = MessageComponentType::Text,
|
.type = MessageComponentType::Text,
|
||||||
@@ -125,25 +125,17 @@ void ChatBarMessageContentModel::initializeFromCache()
|
|||||||
|
|
||||||
clearModel();
|
clearModel();
|
||||||
|
|
||||||
const auto currentCache = m_room->cacheForType(m_type);
|
const auto ¤tCache = m_room->cacheForType(m_type);
|
||||||
const auto textSections = (m_type == ChatBarType::Room ? currentCache->text() : currentCache->relationMessage()).split(u"\n\n"_s);
|
const auto &blockCache = currentCache->cache();
|
||||||
if (textSections.length() == 1 && textSections[0].isEmpty()) {
|
if (blockCache.isEmpty()) {
|
||||||
initializeModel();
|
initializeModel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
for (const auto §ion : textSections) {
|
std::ranges::for_each(blockCache.constBegin(), blockCache.constEnd(), [this](const Block::CacheItem &cacheItem) {
|
||||||
const auto type = MessageComponentType::typeForString(section);
|
insertComponent(rowCount(), cacheItem.type, {}, cacheItem.content);
|
||||||
auto cleanText = section;
|
});
|
||||||
if (type == MessageComponentType::Code) {
|
|
||||||
cleanText.remove(0, 4);
|
|
||||||
cleanText.remove(cleanText.length() - 4, 4);
|
|
||||||
} else if (type == MessageComponentType::Quote) {
|
|
||||||
cleanText.remove(0, 2);
|
|
||||||
}
|
|
||||||
insertComponent(rowCount(), type, {}, cleanText);
|
|
||||||
}
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
|
||||||
if (currentCache->attachmentPath().length() > 0) {
|
if (currentCache->attachmentPath().length() > 0) {
|
||||||
@@ -390,7 +382,7 @@ void ChatBarMessageContentModel::addAttachment(const QUrl &path)
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChatBarMessageContentModel::ComponentIt
|
ChatBarMessageContentModel::ComponentIt
|
||||||
ChatBarMessageContentModel::insertComponent(int row, MessageComponentType::Type type, QVariantMap attributes, const QString &intialText)
|
ChatBarMessageContentModel::insertComponent(int row, MessageComponentType::Type type, QVariantMap attributes, const QTextDocumentFragment &intialFragment)
|
||||||
{
|
{
|
||||||
if (row < 0 || row > rowCount()) {
|
if (row < 0 || row > rowCount()) {
|
||||||
return m_components.end();
|
return m_components.end();
|
||||||
@@ -398,7 +390,7 @@ ChatBarMessageContentModel::insertComponent(int row, MessageComponentType::Type
|
|||||||
|
|
||||||
if (MessageComponentType::isTextType(type)) {
|
if (MessageComponentType::isTextType(type)) {
|
||||||
const auto textItemWrapper = new ChatTextItemHelper(this);
|
const auto textItemWrapper = new ChatTextItemHelper(this);
|
||||||
textItemWrapper->setInitialText(intialText);
|
textItemWrapper->setInitialFragment(intialFragment);
|
||||||
textItemWrapper->setRoom(m_room);
|
textItemWrapper->setRoom(m_room);
|
||||||
textItemWrapper->setType(m_type);
|
textItemWrapper->setType(m_type);
|
||||||
if (type == MessageComponentType::Quote) {
|
if (type == MessageComponentType::Quote) {
|
||||||
@@ -600,7 +592,8 @@ void ChatBarMessageContentModel::updateCache() const
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_room->cacheForType(m_type)->setText(messageText());
|
m_room->cacheForType(m_type)->cache().clear();
|
||||||
|
m_room->cacheForType(m_type)->cache().fill(m_components);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QString formatQuote(const QString &input)
|
inline QString formatQuote(const QString &input)
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ private:
|
|||||||
QPointer<ChatKeyHelper> m_keyHelper;
|
QPointer<ChatKeyHelper> m_keyHelper;
|
||||||
void connectKeyHelper();
|
void connectKeyHelper();
|
||||||
|
|
||||||
ComponentIt insertComponent(int row, MessageComponentType::Type type, QVariantMap attributes = {}, const QString &intialText = {});
|
ComponentIt insertComponent(int row, MessageComponentType::Type type, QVariantMap attributes = {}, const QTextDocumentFragment &intialFragment = {});
|
||||||
ComponentIt removeComponent(ComponentIt it);
|
ComponentIt removeComponent(ComponentIt it);
|
||||||
void removeComponent(ChatTextItemHelper *textItem);
|
void removeComponent(ChatTextItemHelper *textItem);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user