RSNewWebUI/webui-src/app/forums/forums_util.js
2026-08-02 20:46:00 +05:30

292 lines
8.5 KiB
JavaScript

const m = require('mithril');
const rs = require('rswebui');
const GROUP_SUBSCRIBE_ADMIN = 0x01; // means: you have the admin key for this group
const GROUP_SUBSCRIBE_PUBLISH = 0x02; // means: you have the publish key for thiss group. Typical use: publish key in forums are shared with specific friends.
const GROUP_SUBSCRIBE_SUBSCRIBED = 0x04; // means: you are subscribed to a group, which makes you a source for this group to your friend nodes.
const GROUP_SUBSCRIBE_NOT_SUBSCRIBED = 0x08;
const GROUP_MY_FORUM = GROUP_SUBSCRIBE_ADMIN + GROUP_SUBSCRIBE_SUBSCRIBED + GROUP_SUBSCRIBE_PUBLISH;
const THREAD_UNREAD = 0x00000003;
const Data = {
DisplayForums: {},
Threads: {},
ParentThreads: {},
ParentThreadMap: {},
loading: new Set(),
};
function getTimestampValue(ts) {
if (!ts) return 0;
if (typeof ts === 'object') {
if (ts.xint64 !== undefined) return ts.xint64;
if (ts.xstr64 !== undefined) return Number(ts.xstr64);
return 0;
}
return ts;
}
function formatTimestamp(ts) {
const val = getTimestampValue(ts);
if (!val || val === 0) return '???';
try {
const localDate = new Date(val * 1000);
const offset = localDate.getTimezoneOffset() * 60000;
return new Date(localDate.getTime() - offset).toISOString().replace('T', ' ').slice(0, 16);
} catch (e) {
return 'Invalid Date';
}
}
async function updatedisplayforums(keyid) {
if (Data.loading.has(keyid)) return;
Data.loading.add(keyid);
try {
const res1 = await rs.rsJsonApiRequest('/rsgxsforums/getForumsInfo', {
forumIds: [keyid], // keyid: Forumid
});
if (res1 && res1.body && res1.body.retval && res1.body.forumsInfo && res1.body.forumsInfo.length > 0) {
const forumInfo = res1.body.forumsInfo[0];
Data.DisplayForums[keyid] = {
// struct for a forum
name: forumInfo.mMeta.mGroupName,
author: forumInfo.mMeta.mAuthorId,
isSearched: true,
description: forumInfo.mDescription,
isSubscribed:
forumInfo.mMeta.mSubscribeFlags === GROUP_SUBSCRIBE_SUBSCRIBED ||
forumInfo.mMeta.mSubscribeFlags === GROUP_MY_FORUM,
activity: forumInfo.mMeta.mLastPost,
created: forumInfo.mMeta.mPublishTs,
};
if (Data.Threads[keyid] === undefined) {
Data.Threads[keyid] = {};
}
const res2 = await rs.rsJsonApiRequest('/rsgxsforums/getForumPostsHierarchy', {
group: forumInfo,
});
if (res2 && res2.body && res2.body.vect) {
const vect = res2.body.vect;
// Index 0 is the root sentinel in GXS hierarchy
const rootSentinel = vect[0];
if (rootSentinel && rootSentinel.mChildren) {
Data.ParentThreads[keyid] = {};
rootSentinel.mChildren.forEach((topIndex) => {
const EntryToThread = (entryIndex) => {
const entry = vect[entryIndex];
const replies = {};
// Map ForumPostEntry to a structure compatible with the existing UI
const meta = {
mGroupId: keyid,
mMsgId: entry.mMsgId,
mOrigMsgId: entry.mMsgId,
mThreadId: entry.mMsgId,
mParentId:
entry.mParent !== 0
? vect[entry.mParent].mMsgId
: '00000000000000000000000000000000',
mAuthorId: entry.mAuthorId,
mMsgName: entry.mTitle,
mPublishTs: entry.mPublishTs,
mMostRecentTsInThread: getTimestampValue(entry.mPublishTs),
mMsgStatus: entry.mMsgStatus,
};
// Populate ParentThreadMap for compatibility
if (meta.mParentId !== '00000000000000000000000000000000') {
if (!Data.ParentThreadMap[meta.mParentId]) Data.ParentThreadMap[meta.mParentId] = {};
Data.ParentThreadMap[meta.mParentId][meta.mMsgId] = meta;
}
const threadStruct = {
thread: { mMeta: meta, mMsg: null },
replies,
showReplies: false,
};
// Add to flat map
Data.Threads[keyid][meta.mMsgId] = threadStruct;
if (entry.mChildren) {
entry.mChildren.forEach((childIndex) => {
const childThread = EntryToThread(childIndex);
replies[childThread.thread.mMeta.mMsgId] = childThread;
const childTs = childThread.thread.mMeta.mMostRecentTsInThread || 0;
if (childTs > meta.mMostRecentTsInThread) meta.mMostRecentTsInThread = childTs;
});
}
return threadStruct;
};
const topThread = EntryToThread(topIndex);
Data.ParentThreads[keyid][topThread.thread.mMeta.mMsgId] = topThread.thread.mMeta;
});
}
}
m.redraw();
}
} catch (e) {
console.error('[RS] Error updating forum display for:', keyid, e);
} finally {
Data.loading.delete(keyid);
m.redraw(); // Final redraw just in case
}
}
/**
* Load the body (mMsg) of a single forum post on demand.
* Returns a Promise that resolves to the body string, or null on failure.
*/
async function loadPostContent(forumId, msgId) {
// If body is already loaded, return it immediately
if (
Data.Threads[forumId] &&
Data.Threads[forumId][msgId] &&
Data.Threads[forumId][msgId].thread.mMsg !== null
) {
return Data.Threads[forumId][msgId].thread.mMsg;
}
try {
const res = await rs.rsJsonApiRequest('/rsgxsforums/getForumContent', {
forumId,
msgsIds: [msgId],
});
if (res && res.body && res.body.retval && res.body.msgs && res.body.msgs.length > 0) {
const body = res.body.msgs[0].mMsg;
// Cache the body in the existing thread entry
if (Data.Threads[forumId] && Data.Threads[forumId][msgId]) {
Data.Threads[forumId][msgId].thread.mMsg = body;
}
m.redraw();
return body;
}
} catch (e) {
console.error('[RS] Error loading post content:', forumId, msgId, e);
}
return null;
}
const DisplayForumsFromList = () => {
return {
view: (v) =>
m(
'tr',
{
key: v.attrs.id,
class:
Data.DisplayForums[v.attrs.id] && Data.DisplayForums[v.attrs.id].isSearched
? ''
: 'hidden',
onclick: () => {
m.route.set('/forums/:tab/:mGroupId', {
tab: v.attrs.category,
mGroupId: v.attrs.id,
});
},
},
[m('td', Data.DisplayForums[v.attrs.id] ? Data.DisplayForums[v.attrs.id].name : '')]
),
};
};
const ForumSummary = () => {
let keyid = {};
return {
oninit: (v) => {
keyid = v.attrs.details.mGroupId;
updatedisplayforums(keyid);
},
view: (v) => { },
};
};
const ForumTable = () => {
return {
view: (v) => m('table.forums', [m('tr', [m('th', 'Forum Name')]), v.children]),
};
};
const ThreadsTable = () => {
return {
oninit: (v) => { },
view: (v) =>
m('table.threads', [
v.children,
]),
};
};
const ThreadsReplyTable = () => {
return {
oninit: (v) => { },
view: (v) =>
m('table.threadreply', [
v.children,
]),
};
};
const SearchBar = () => {
let searchString = '';
return {
view: (v) =>
m('input[type=text][id=searchforum][placeholder=Search Subject].searchbar', {
value: searchString,
oninput: (e) => {
searchString = e.target.value.toLowerCase();
for (const hash in Data.DisplayForums) {
if (Data.DisplayForums[hash].name.toLowerCase().indexOf(searchString) > -1) {
Data.DisplayForums[hash].isSearched = true;
} else {
Data.DisplayForums[hash].isSearched = false;
}
}
},
}),
};
};
function popupmessage(message) {
const container = document.getElementById('modal-container');
container.style.display = 'block';
m.render(
container,
m('.modal-content', [
m(
'button.red',
{
onclick: () => (container.style.display = 'none'),
},
m('i.fas.fa-times')
),
message,
])
);
}
module.exports = {
Data,
SearchBar,
ForumSummary,
DisplayForumsFromList,
ForumTable,
ThreadsTable,
ThreadsReplyTable,
popupmessage,
updatedisplayforums,
loadPostContent,
getTimestampValue,
formatTimestamp,
GROUP_SUBSCRIBE_ADMIN,
GROUP_SUBSCRIBE_NOT_SUBSCRIBED,
GROUP_SUBSCRIBE_PUBLISH,
GROUP_SUBSCRIBE_SUBSCRIBED,
GROUP_MY_FORUM,
THREAD_UNREAD,
};