feat(posted): add pinned posts support and createPostV2 API

- Add mPinnedPosts (RsTlvGxsMsgIdSet) to RsPostedGroup for pin/unpin state
- Add PINNED_POSTS_CHANGED event code
- Add pinPost/unpinPost virtual methods to RsPosted interface
- Implement pin/unpin in p3posted.cc with editBoard pattern
- Add backward-compatible serialization with length checks
- Update createPostDialog to use createPostV2 with error messages
- Add Pin/Unpin context menu for post publishers
- Sort pinned posts to top regardless of sort strategy
- Add pinned visual indicator via feedFrame CSS property
This commit is contained in:
unknown 2026-07-28 12:50:26 +01:00
parent c7b9746bcd
commit 5eb94e2307
5 changed files with 74 additions and 0 deletions

View File

@ -32,6 +32,7 @@
#include "retroshare/rsgxscommon.h"
#include "retroshare/rsgxscircles.h"
#include "serialiser/rsserializable.h"
#include "serialiser/rstlvidset.h"
class RsPosted;
@ -46,6 +47,9 @@ struct RsPostedGroup: public RsSerializable, RsGxsGenericGroupData
std::string mDescription;
RsGxsImage mGroupImage;
/** @brief List of board pinned posts, those are displayed on top */
RsTlvGxsMsgIdSet mPinnedPosts;
/// @see RsSerializable
virtual void serial_process( RsGenericSerializer::SerializeJob j,
RsGenericSerializer::SerializeContext& ctx ) override
@ -53,6 +57,7 @@ struct RsPostedGroup: public RsSerializable, RsGxsGenericGroupData
RS_SERIAL_PROCESS(mMeta);
RS_SERIAL_PROCESS(mDescription);
RS_SERIAL_PROCESS(mGroupImage);
RS_SERIAL_PROCESS(mPinnedPosts);
}
};
@ -115,6 +120,8 @@ struct RsPostedPost: public RsSerializable, RsGxsGenericMsgData
#define RSPOSTED_VIEWMODE_HOT 3
#define RSPOSTED_VIEWMODE_COMMENTS 4
#define RSPOSTED_FLAG_POST_IS_PINNED 0x0001
enum class RsPostedEventCode: uint8_t
{
@ -131,6 +138,7 @@ enum class RsPostedEventCode: uint8_t
NEW_COMMENT = 0x0a,
NEW_VOTE = 0x0b,
BOARD_DELETED = 0x0c,
PINNED_POSTS_CHANGED = 0x0d,
};
@ -240,6 +248,24 @@ public:
*/
virtual bool editBoard(RsPostedGroup& board) =0;
/**
* @brief Pin a post to the top of a board. Admin-only. Blocking API.
* @jsonapi{development}
* @param[in] boardId Id of the board
* @param[in] postId Id of the post to pin
* @return false on error, true otherwise
*/
virtual bool pinPost(const RsGxsGroupId& boardId, const RsGxsMessageId& postId) =0;
/**
* @brief Unpin a post from a board. Admin-only. Blocking API.
* @jsonapi{development}
* @param[in] boardId Id of the board
* @param[in] postId Id of the post to unpin
* @return false on error, true otherwise
*/
virtual bool unpinPost(const RsGxsGroupId& boardId, const RsGxsMessageId& postId) =0;
/**
* @brief Create board. Blocking API.
* @jsonapi{development}

View File

@ -53,6 +53,12 @@ void RsGxsPostedGroupItem::serial_process(RsGenericSerializer::SerializeJob j,Rs
return ;
RsTypeSerializer::serial_process<RsTlvItem>(j,ctx,mGroupImage,"mGroupImage") ;
// Backward compat: if only description+image were written, stop here
if(j == RsGenericSerializer::DESERIALIZE && ctx.mOffset == ctx.mSize)
return ;
RsTypeSerializer::serial_process<RsTlvItem>(j,ctx,mPinnedPosts,"mPinnedPosts") ;
}
RsItem *RsGxsPostedSerialiser::create_item(uint16_t service_id,uint8_t item_subtype) const
@ -119,6 +125,7 @@ void RsGxsPostedGroupItem::clear()
{
mDescription.clear();
mGroupImage.TlvClear();
mPinnedPosts.TlvClear();
}
bool RsGxsPostedGroupItem::fromPostedGroup(RsPostedGroup &group, bool moveImage)
@ -137,6 +144,10 @@ bool RsGxsPostedGroupItem::fromPostedGroup(RsPostedGroup &group, bool moveImage)
{
mGroupImage.binData.setBinData(group.mGroupImage.mData, group.mGroupImage.mSize);
}
// Copy pinned posts
mPinnedPosts = group.mPinnedPosts;
return true;
}
@ -154,5 +165,9 @@ bool RsGxsPostedGroupItem::toPostedGroup(RsPostedGroup &group, bool moveImage)
{
group.mGroupImage.copy((uint8_t *) mGroupImage.binData.bin_data, mGroupImage.binData.bin_len);
}
// Copy pinned posts
group.mPinnedPosts = mPinnedPosts;
return true;
}

View File

@ -26,6 +26,7 @@
#include "rsitems/rsgxscommentitems.h"
#include "rsitems/rsgxsitems.h"
#include "serialiser/rstlvimage.h"
#include "serialiser/rstlvidset.h"
#include "retroshare/rsposted.h"
@ -48,6 +49,7 @@ public:
std::string mDescription;
RsTlvImage mGroupImage;
RsTlvGxsMsgIdSet mPinnedPosts;
};

View File

@ -890,5 +890,33 @@ bool p3Posted::editBoard(RsPostedGroup& board)
return true;
}
bool p3Posted::pinPost(const RsGxsGroupId& boardId, const RsGxsMessageId& postId)
{
std::vector<RsPostedGroup> groupsInfo;
if(!getBoardsInfo({boardId}, groupsInfo))
{
std::cerr << __PRETTY_FUNCTION__ << " Error! Board not found." << std::endl;
return false;
}
RsPostedGroup board = groupsInfo.front();
board.mPinnedPosts.ids.insert(postId);
return editBoard(board);
}
bool p3Posted::unpinPost(const RsGxsGroupId& boardId, const RsGxsMessageId& postId)
{
std::vector<RsPostedGroup> groupsInfo;
if(!getBoardsInfo({boardId}, groupsInfo))
{
std::cerr << __PRETTY_FUNCTION__ << " Error! Board not found." << std::endl;
return false;
}
RsPostedGroup board = groupsInfo.front();
board.mPinnedPosts.ids.erase(postId);
return editBoard(board);
}
RsPosted::~RsPosted() = default;
RsGxsPostedEvent::~RsGxsPostedEvent() = default;

View File

@ -77,6 +77,9 @@ virtual void receiveHelperChanges(std::vector<RsGxsNotify*>& changes)
bool editBoard(RsPostedGroup& board) override;
bool pinPost(const RsGxsGroupId& boardId, const RsGxsMessageId& postId) override;
bool unpinPost(const RsGxsGroupId& boardId, const RsGxsMessageId& postId) override;
bool createBoard(RsPostedGroup& board) override;
bool createBoardV2(const std::string& board_name,