[0.9.1] Mobile: fixed topbar, global hive, per-message chat reply/switcher, forum subtopics, square module grid

- Topbar: single fixed line [logo][Personal][Community][avatar] in one dark
  tone (#121212) with gold accents only; the hive hexagons scroll below it.
- Hive: Personal/Community filter the home hive from any page; opening a
  category shows its modules as a 2x2 grid of square buttons (a lone odd
  button is centered instead of left-aligned).
- Chats: per-message reply (?replyTo=), chat switcher strip and quote banner;
  the message rate limit is lifted on mobile.
- Forums: optional subtopics (?topic=) with threaded rendering.
- Bottombar: fixed Inbox - PM - Write - Search - Graphos - Peers.
This commit is contained in:
s1to 2026-08-05 23:36:18 +02:00
parent 2b2c65fc7a
commit ed9f01e8e6
8 changed files with 280 additions and 129 deletions

View file

@ -282,8 +282,20 @@ exports.forumView = async (forums, currentFilter, params = {}) => {
);
};
exports.singleForumView = async (forum, messagesData, currentFilter) => {
exports.singleForumView = async (forum, messagesData, currentFilter, topic = '') => {
const CAT_I18N_MAP_UP = ALL_CATS.reduce((m,c)=>{ m[c]=(catLabel(c)||c).toUpperCase(); return m; },{});
// móvil: subtopics — etiqueta opcional en los mensajes raíz → tira de pestañas + filtro por ?topic=
const forumMobile = process.env.OASIS_MOBILE === '1';
const rootMsgs = Array.isArray(messagesData.messages) ? messagesData.messages : [];
const forumTopics = forumMobile ? [...new Set(rootMsgs.map(m => String(m.subtopic || '').trim()).filter(Boolean))] : [];
const activeTopic = forumMobile ? String(topic || '').trim() : '';
const shownMsgs = activeTopic ? rootMsgs.filter(m => String(m.subtopic || '').trim() === activeTopic) : rootMsgs;
const topicStrip = (forumMobile && forumTopics.length)
? div({ class: 'forum-topics' },
a({ class: activeTopic ? 'forum-topic-chip' : 'forum-topic-chip active', href: `/forum/${encodeURIComponent(forum.key)}` }, i18n.menuAll || 'All'),
...forumTopics.map(t => a({ class: t === activeTopic ? 'forum-topic-chip active' : 'forum-topic-chip', href: `/forum/${encodeURIComponent(forum.key)}?topic=${encodeURIComponent(t)}` }, t))
)
: null;
return template(forum.title,
section(
div({ class: 'tags-header' },
@ -305,6 +317,7 @@ exports.singleForumView = async (forum, messagesData, currentFilter) => {
)
),
div({ class: 'forum-thread-container' },
topicStrip,
div({
class: 'forum-card forum-thread-header',
style: 'display:flex;align-items:flex-start;'
@ -394,6 +407,7 @@ exports.singleForumView = async (forum, messagesData, currentFilter) => {
action: `/forum/${encodeURIComponent(forum.key)}/message`,
class: 'new-message-form'
},
forumMobile ? input({ type: 'text', name: 'subtopic', value: activeTopic, placeholder: 'Topic (optional)', class: 'forum-topic-input' }) : null,
textarea({
name: 'message',
rows: 4,
@ -408,7 +422,7 @@ exports.singleForumView = async (forum, messagesData, currentFilter) => {
}, i18n.forumSendButton)
)
),
...renderThread(messagesData.messages, 0, forum.key)
...renderThread(shownMsgs, 0, forum.key)
)
);
};