From 9c100ed77377990ff94de6e2d16deb97d207abbb Mon Sep 17 00:00:00 2001 From: s1to Date: Thu, 9 Jul 2026 16:44:17 +0200 Subject: [PATCH] Configurable bottom bar and empty-state helper - Bottom bar: replace Activity with a + slot to pin a module; hide slots for disabled modules. - Add a reusable empty-state helper and the inbox empty state. - Mobile styles and translations for the above. --- src/client/assets/styles/mobile.css | 55 +++++++++++ src/client/assets/translations/oasis_ar.js | 9 ++ src/client/assets/translations/oasis_de.js | 9 ++ src/client/assets/translations/oasis_en.js | 9 ++ src/client/assets/translations/oasis_es.js | 9 ++ src/client/assets/translations/oasis_eu.js | 9 ++ src/client/assets/translations/oasis_fr.js | 9 ++ src/client/assets/translations/oasis_hi.js | 9 ++ src/client/assets/translations/oasis_it.js | 9 ++ src/client/assets/translations/oasis_pt.js | 9 ++ src/client/assets/translations/oasis_ru.js | 9 ++ src/client/assets/translations/oasis_zh.js | 9 ++ src/views/main_views.js | 108 +++++++++++++++++++-- 13 files changed, 252 insertions(+), 10 deletions(-) diff --git a/src/client/assets/styles/mobile.css b/src/client/assets/styles/mobile.css index 97dca01..54b1797 100644 --- a/src/client/assets/styles/mobile.css +++ b/src/client/assets/styles/mobile.css @@ -1531,3 +1531,58 @@ button, input[type="submit"], input[type="button"], .main-column { padding: 10px 12px 16px 12px !important; } .activity-filter-acc { margin-bottom: 4px !important; } +.empty-state { + display: flex !important; flex-direction: column !important; align-items: center !important; + justify-content: center !important; text-align: center !important; + padding: 40px 16px !important; gap: 8px !important; width: 100% !important; box-sizing: border-box !important; +} +.empty-state-icon { font-size: 44px !important; line-height: 1 !important; opacity: 0.55 !important; } +.empty-state-title { font-size: 16px !important; font-weight: 700 !important; margin: 4px 0 0 0 !important; color: #eee !important; } +.empty-state-text { color: var(--c-muted, #8c8c8c) !important; font-size: 14px !important; line-height: 1.4 !important; max-width: 40ch !important; margin: 0 !important; } +.empty-state-cta { margin-top: 10px !important; min-height: 48px !important; display: inline-flex !important; align-items: center !important; } + +.activity-filter-collapsed { + display: flex !important; align-items: center !important; gap: 10px !important; + flex-wrap: wrap !important; margin-bottom: 8px !important; width: 100% !important; +} +.activity-back { + display: inline-flex !important; align-items: center !important; gap: 4px !important; + min-height: 44px !important; padding: 6px 14px 6px 10px !important; flex: 0 0 auto !important; +} +.activity-back-arrow { font-size: 22px !important; line-height: 1 !important; font-weight: bold !important; } +.activity-active-filter { + font-weight: 700 !important; color: #FFA500 !important; text-transform: uppercase !important; + font-size: 13px !important; letter-spacing: .03em !important; +} + +.oasis-bottombar .bb-item { min-height: 48px !important; } +.oasis-bottombar .bb-pair .bb-item { min-height: 44px !important; padding: 4px 10px !important; } +.oasis-mobile-menus .oasis-menu-btn { min-height: 48px !important; } +.pm-bubble .pm-actions .pm-btn { min-height: 40px !important; padding: 6px 12px !important; } +.score-btn, .vote-btn, .feed-action-btn { + min-height: 44px !important; min-width: 44px !important; + display: inline-flex !important; align-items: center !important; justify-content: center !important; +} +.score-btn:active, .vote-btn:active, .feed-action-btn:active { filter: brightness(.85) !important; } + +.oasis-bottombar .bb-add .bb-ico { + background: #FFA500 !important; color: #121212 !important; border-radius: 999px !important; + width: 30px !important; height: 30px !important; display: inline-flex !important; + align-items: center !important; justify-content: center !important; + font-weight: bold !important; font-size: 20px !important; line-height: 1 !important; +} +.bb-pick-grid { + display: grid !important; grid-template-columns: repeat(3, 1fr) !important; + gap: 8px !important; width: 100% !important; margin-top: 8px !important; +} +.bb-pick-form { margin: 0 !important; display: block !important; } +.bb-pick-btn { + display: flex !important; flex-direction: column !important; align-items: center !important; + justify-content: center !important; gap: 6px !important; width: 100% !important; + min-height: 76px !important; padding: 10px 6px !important; box-sizing: border-box !important; + background: #1c1c1c !important; border: 1px solid #333 !important; border-radius: 12px !important; color: #eee !important; +} +.bb-pick-btn.active { border-color: #FFA500 !important; background: #2a2412 !important; } +.bb-pick-ico { font-size: 26px !important; color: #FFA500 !important; line-height: 1 !important; } +.bb-pick-lbl { font-size: 11px !important; text-align: center !important; line-height: 1.2 !important; } + diff --git a/src/client/assets/translations/oasis_ar.js b/src/client/assets/translations/oasis_ar.js index af7e1dd..90ed0ae 100644 --- a/src/client/assets/translations/oasis_ar.js +++ b/src/client/assets/translations/oasis_ar.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { ar: { + bbAdd: "إضافة", + bbQuickActions: "إجراءات سريعة", + bbPickTitle: "تثبيت وحدة", + bbPickDesc: "اختر وحدة لخانة + في الشريط السفلي.", + bbPickNone: "لا شيء", + activityChangeFilter: "تغيير التصفية", + emptyConnectHint: "اتصل بـ pub للمزامنة مع الشبكة.", + emptyConnectCta: "الاتصال بـ pub", + menuCommunity: "المجتمع", activityGroupCommunity: "المجتمع والحوكمة", activityGroupOrg: "التنظيم", activityGroupComm: "التواصل", diff --git a/src/client/assets/translations/oasis_de.js b/src/client/assets/translations/oasis_de.js index 4ce472a..34d833a 100644 --- a/src/client/assets/translations/oasis_de.js +++ b/src/client/assets/translations/oasis_de.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { de: { + bbAdd: "Hinzufügen", + bbQuickActions: "Schnellaktionen", + bbPickTitle: "Modul anheften", + bbPickDesc: "Wähle ein Modul für den +-Platz in der unteren Leiste.", + bbPickNone: "Keins", + activityChangeFilter: "Filter ändern", + emptyConnectHint: "Verbinde dich mit einem Pub, um dich mit dem Netzwerk zu synchronisieren.", + emptyConnectCta: "Mit einem Pub verbinden", + menuCommunity: "Gemeinschaft", activityGroupCommunity: "Gemeinschaft & Governance", activityGroupOrg: "Organisation", activityGroupComm: "Kommunikation", diff --git a/src/client/assets/translations/oasis_en.js b/src/client/assets/translations/oasis_en.js index ae5e327..1223c2f 100644 --- a/src/client/assets/translations/oasis_en.js +++ b/src/client/assets/translations/oasis_en.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { en: { + bbAdd: "Add", + bbQuickActions: "Quick actions", + bbPickTitle: "Pin a module", + bbPickDesc: "Choose a module for the + slot in the bottom bar.", + bbPickNone: "None", + activityChangeFilter: "Change filter", + emptyConnectHint: "Connect to a pub to sync with the network.", + emptyConnectCta: "Connect to a pub", + menuCommunity: "Community", activityGroupCommunity: "Community & Governance", activityGroupOrg: "Organization", activityGroupComm: "Communication", diff --git a/src/client/assets/translations/oasis_es.js b/src/client/assets/translations/oasis_es.js index 46f3f48..9e602af 100644 --- a/src/client/assets/translations/oasis_es.js +++ b/src/client/assets/translations/oasis_es.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { es: { + bbAdd: "Añadir", + bbQuickActions: "Acciones rápidas", + bbPickTitle: "Fijar un módulo", + bbPickDesc: "Elige un módulo para el hueco + de la barra inferior.", + bbPickNone: "Ninguno", + activityChangeFilter: "Cambiar filtro", + emptyConnectHint: "Conéctate a un pub para sincronizar con la red.", + emptyConnectCta: "Conéctate a un pub", + menuCommunity: "Comunidad", activityGroupCommunity: "Comunidad y Gobernanza", activityGroupOrg: "Organización", activityGroupComm: "Comunicación", diff --git a/src/client/assets/translations/oasis_eu.js b/src/client/assets/translations/oasis_eu.js index 251748d..0c20808 100644 --- a/src/client/assets/translations/oasis_eu.js +++ b/src/client/assets/translations/oasis_eu.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { eu: { + bbAdd: "Gehitu", + bbQuickActions: "Ekintza azkarrak", + bbPickTitle: "Ainguratu modulu bat", + bbPickDesc: "Aukeratu modulu bat beheko barrako + hutsunerako.", + bbPickNone: "Bat ere ez", + activityChangeFilter: "Aldatu iragazkia", + emptyConnectHint: "Konektatu pub batera sarearekin sinkronizatzeko.", + emptyConnectCta: "Konektatu pub batera", + menuCommunity: "Komunitatea", activityGroupCommunity: "Komunitatea eta gobernantza", activityGroupOrg: "Antolakuntza", activityGroupComm: "Komunikazioa", diff --git a/src/client/assets/translations/oasis_fr.js b/src/client/assets/translations/oasis_fr.js index 3d6f340..4a5a79c 100644 --- a/src/client/assets/translations/oasis_fr.js +++ b/src/client/assets/translations/oasis_fr.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { fr: { + bbAdd: "Ajouter", + bbQuickActions: "Actions rapides", + bbPickTitle: "Épingler un module", + bbPickDesc: "Choisis un module pour l'emplacement + de la barre inférieure.", + bbPickNone: "Aucun", + activityChangeFilter: "Changer de filtre", + emptyConnectHint: "Connecte-toi à un pub pour synchroniser avec le réseau.", + emptyConnectCta: "Se connecter à un pub", + menuCommunity: "Communauté", activityGroupCommunity: "Communauté et gouvernance", activityGroupOrg: "Organisation", activityGroupComm: "Communication", diff --git a/src/client/assets/translations/oasis_hi.js b/src/client/assets/translations/oasis_hi.js index 0108885..90037df 100644 --- a/src/client/assets/translations/oasis_hi.js +++ b/src/client/assets/translations/oasis_hi.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { hi: { + bbAdd: "जोड़ें", + bbQuickActions: "त्वरित क्रियाएँ", + bbPickTitle: "एक मॉड्यूल पिन करें", + bbPickDesc: "नीचे की बार में + स्लॉट के लिए एक मॉड्यूल चुनें।", + bbPickNone: "कोई नहीं", + activityChangeFilter: "फ़िल्टर बदलें", + emptyConnectHint: "नेटवर्क से सिंक करने के लिए किसी pub से जुड़ें।", + emptyConnectCta: "pub से जुड़ें", + menuCommunity: "समुदाय", activityGroupCommunity: "समुदाय और शासन", activityGroupOrg: "संगठन", activityGroupComm: "संचार", diff --git a/src/client/assets/translations/oasis_it.js b/src/client/assets/translations/oasis_it.js index fcd8d21..8f3ae14 100644 --- a/src/client/assets/translations/oasis_it.js +++ b/src/client/assets/translations/oasis_it.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { it: { + bbAdd: "Aggiungi", + bbQuickActions: "Azioni rapide", + bbPickTitle: "Fissa un modulo", + bbPickDesc: "Scegli un modulo per lo spazio + nella barra inferiore.", + bbPickNone: "Nessuno", + activityChangeFilter: "Cambia filtro", + emptyConnectHint: "Connettiti a un pub per sincronizzarti con la rete.", + emptyConnectCta: "Connettiti a un pub", + menuCommunity: "Comunità", activityGroupCommunity: "Comunità e governance", activityGroupOrg: "Organizzazione", activityGroupComm: "Comunicazione", diff --git a/src/client/assets/translations/oasis_pt.js b/src/client/assets/translations/oasis_pt.js index 0a84578..d8662a3 100644 --- a/src/client/assets/translations/oasis_pt.js +++ b/src/client/assets/translations/oasis_pt.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { pt: { + bbAdd: "Adicionar", + bbQuickActions: "Ações rápidas", + bbPickTitle: "Fixar um módulo", + bbPickDesc: "Escolhe um módulo para o espaço + da barra inferior.", + bbPickNone: "Nenhum", + activityChangeFilter: "Mudar filtro", + emptyConnectHint: "Liga-te a um pub para sincronizar com a rede.", + emptyConnectCta: "Ligar a um pub", + menuCommunity: "Comunidade", activityGroupCommunity: "Comunidade e governança", activityGroupOrg: "Organização", activityGroupComm: "Comunicação", diff --git a/src/client/assets/translations/oasis_ru.js b/src/client/assets/translations/oasis_ru.js index a69a349..0115520 100644 --- a/src/client/assets/translations/oasis_ru.js +++ b/src/client/assets/translations/oasis_ru.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { ru: { + bbAdd: "Добавить", + bbQuickActions: "Быстрые действия", + bbPickTitle: "Закрепить модуль", + bbPickDesc: "Выберите модуль для слота + в нижней панели.", + bbPickNone: "Нет", + activityChangeFilter: "Сменить фильтр", + emptyConnectHint: "Подключитесь к пабу для синхронизации с сетью.", + emptyConnectCta: "Подключиться к пабу", + menuCommunity: "Сообщество", activityGroupCommunity: "Сообщество и управление", activityGroupOrg: "Организация", activityGroupComm: "Коммуникация", diff --git a/src/client/assets/translations/oasis_zh.js b/src/client/assets/translations/oasis_zh.js index 8ea0cf1..5036a15 100644 --- a/src/client/assets/translations/oasis_zh.js +++ b/src/client/assets/translations/oasis_zh.js @@ -1,6 +1,15 @@ const { a, em, strong } = require('../../../server/node_modules/hyperaxe'); module.exports = { zh: { + bbAdd: "添加", + bbQuickActions: "快捷操作", + bbPickTitle: "固定一个模块", + bbPickDesc: "为底部栏的 + 位置选择一个模块。", + bbPickNone: "无", + activityChangeFilter: "更改筛选", + emptyConnectHint: "连接到一个 pub 以与网络同步。", + emptyConnectCta: "连接到 pub", + menuCommunity: "社区", activityGroupCommunity: "社区与治理", activityGroupOrg: "组织", activityGroupComm: "沟通", diff --git a/src/views/main_views.js b/src/views/main_views.js index b16b17e..7f6dbe3 100644 --- a/src/views/main_views.js +++ b/src/views/main_views.js @@ -112,6 +112,42 @@ const renderContentActions = (msgId, viewHref) => { }; exports.renderContentActions = renderContentActions; +const emptyState = ({ icon, title, text, ctaHref, ctaLabel }) => div( + { class: "empty-state" }, + icon ? span({ class: "empty-state-icon" }, icon) : "", + title ? p({ class: "empty-state-title" }, title) : "", + text ? p({ class: "empty-state-text" }, text) : "", + (ctaHref && ctaLabel) ? a({ class: "empty-state-cta filter-btn", href: ctaHref }, ctaLabel) : "" +); +exports.emptyState = emptyState; + +exports.bottomBarPickerView = () => { + const cfg = getConfig(); + const modOn = (m) => !m || cfg.modules[m] === 'on' || cfg.modules[m] === undefined; + const current = cfg.bottomBarPin || ''; + const pick = (key, glyph, labelText) => form({ method: 'POST', action: '/settings/bottombar', class: 'bb-pick-form' }, + input({ type: 'hidden', name: 'pin', value: key }), + button({ type: 'submit', class: current === key ? 'bb-pick-btn active' : 'bb-pick-btn' }, + span({ class: 'bb-pick-ico' }, glyph), + span({ class: 'bb-pick-lbl' }, labelText) + ) + ); + const items = exports.PINNABLE_MODULES.filter(m => modOn(m.mod)).map(m => pick(m.key, m.glyph, i18n[m.labelKey] || m.key)); + return template( + i18n.bbPickTitle || 'Pin a module', + section( + div({ class: 'tags-header' }, + h2(i18n.bbPickTitle || 'Pin a module'), + p(i18n.bbPickDesc || 'Choose a module for the + slot in the bottom bar.') + ), + div({ class: 'bb-pick-grid' }, + pick('', '✕', i18n.bbPickNone || 'None'), + ...items + ) + ) + ); +}; + exports.renderStateChip = renderStateChip; exports.renderOpenClosedChip = renderOpenClosedChip; exports.renderVisibilityChip = renderVisibilityChip; @@ -1080,24 +1116,76 @@ const renderMobileTopbar = () => div( a({ class: "omt-avatar", href: "/profile" }, img({ src: (typeof global !== "undefined" && global.__OASIS_SELF_AVATAR__) || "/assets/images/default-avatar.png", alt: i18n.profile || "Profile" })) ); +const PINNABLE_MODULES = [ + { key: 'tribes', href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' }, + { key: 'chats', href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' }, + { key: 'market', href: '/market', glyph: 'ꕻ', labelKey: 'marketTitle', mod: 'marketMod' }, + { key: 'events', href: '/events', glyph: 'ꕆ', labelKey: 'eventsLabel', mod: 'eventsMod' }, + { key: 'calendars', href: '/calendars', glyph: 'ꖯ', labelKey: 'calendarsTitle', mod: 'calendarsMod' }, + { key: 'banking', href: '/banking', glyph: 'ꗴ', labelKey: 'bankingTitle', mod: 'bankingMod' }, + { key: 'forum', href: '/forum', glyph: 'ꕒ', labelKey: 'forumTitle', mod: 'forumMod' }, + { key: 'videos', href: '/videos', glyph: 'ꗟ', labelKey: 'videosLabel', mod: 'videosMod' }, + { key: 'images', href: '/images', glyph: 'ꕥ', labelKey: 'imagesLabel', mod: 'imagesMod' }, + { key: 'audios', href: '/audios', glyph: 'ꔿ', labelKey: 'audiosLabel', mod: 'audiosMod' }, + { key: 'documents', href: '/documents', glyph: 'ꕨ', labelKey: 'docsLabel', mod: 'docsMod' }, + { key: 'bookmarks', href: '/bookmarks', glyph: 'ꔪ', labelKey: 'bookmarksLabel', mod: 'bookmarksMod' }, + { key: 'maps', href: '/maps', glyph: 'ꔌ', labelKey: 'mapsLabel', mod: 'mapsMod' }, + { key: 'trending', href: '/trending', glyph: 'ꗝ', labelKey: 'trendingLabel', mod: 'trendingMod' }, + { key: 'jobs', href: '/jobs', glyph: 'ꗒ', labelKey: 'jobsTitle', mod: 'jobsMod' }, + { key: 'agenda', href: '/agenda', glyph: 'ꗤ', labelKey: 'agendaTitle', mod: 'agendaMod' }, + { key: 'wallet', href: '/wallet', glyph: '❄', labelKey: 'wallet', mod: 'walletMod' }, + { key: 'tasks', href: '/tasks', glyph: 'ꖧ', labelKey: 'tasksTitle', mod: 'tasksMod' }, + { key: 'torrents', href: '/torrents', glyph: 'ꖅ', labelKey: 'torrentsLabel', mod: 'torrentsMod' }, + { key: 'tags', href: '/tags', glyph: 'ꖶ', labelKey: 'tagsLabel', mod: 'tagsMod' }, + { key: 'pads', href: '/pads', glyph: 'ꔗ', labelKey: 'padsTitle', mod: 'padsMod' }, + { key: 'projects', href: '/projects', glyph: 'ꕧ', labelKey: 'projectsTitle', mod: 'projectsMod' }, + { key: 'shops', href: '/shops', glyph: 'ꔜ', labelKey: 'shopsTitle', mod: 'shopsMod' }, + { key: 'transfers', href: '/transfers', glyph: 'ꘉ', labelKey: 'transfersTitle', mod: 'transfersMod' }, + { key: 'feed', href: '/feed', glyph: 'ꕿ', labelKey: 'feedTitle', mod: 'feedMod' }, + { key: 'pixelia', href: '/pixelia', glyph: 'ꔘ', labelKey: 'pixeliaTitle', mod: 'pixeliaMod' }, + { key: 'melody', href: '/melody', glyph: '♪', labelKey: 'melodyTitle', mod: 'melodyMod' }, + { key: 'games', href: '/games', glyph: 'ꕇ', labelKey: 'gamesTitle', mod: 'gamesMod' }, + { key: 'votes', href: '/votes', glyph: 'ꔰ', labelKey: 'votationsTitle', mod: 'votesMod' }, + { key: 'reports', href: '/reports', glyph: 'ꕥ', labelKey: 'reportsTitle', mod: 'reportsMod' }, + { key: 'opinions', href: '/opinions', glyph: 'ꔍ', labelKey: 'opinionsTitle', mod: 'opinionsMod' }, + { key: 'parliament',href: '/parliament',glyph: 'ꗞ', labelKey: 'parliamentTitle',mod: 'parliamentMod' }, + { key: 'courts', href: '/courts', glyph: 'ꖻ', labelKey: 'courtsTitle', mod: 'courtsMod' }, + { key: 'larp', href: '/larp', glyph: '✦', labelKey: 'larpTitle', mod: 'larpMod' }, + { key: 'ai', href: '/ai', glyph: 'ꘜ', labelKey: 'ai', mod: 'aiMod' }, + { key: 'cipher', href: '/cipher', glyph: 'ꗄ', labelKey: 'cipher', mod: 'cipherMod' }, + { key: 'fediverse', href: '/fediverse', glyph: 'ꗵ', labelKey: 'fediverseTimeline', mod: 'fediverseMod' }, + { key: 'inhabitants', href: '/inhabitants', glyph: 'ꖘ', labelKey: 'inhabitantsLabel', mod: null }, + { key: 'cv', href: '/cv', glyph: 'ꕛ', labelKey: 'cvTitle', mod: null }, + { key: 'stats', href: '/stats', glyph: 'ꕷ', labelKey: 'statistics', mod: null }, + { key: 'blockexplorer', href: '/blockexplorer', glyph: 'ꖸ', labelKey: 'blockchain', mod: null }, +]; +exports.PINNABLE_MODULES = PINNABLE_MODULES; + const renderBottomBar = () => { const inboxN = sharedState.getInboxCount(); + const cfg = getConfig(); + const modOn = (m) => !m || cfg.modules[m] === 'on' || cfg.modules[m] === undefined; const bbLink = (href, ico, label, badge) => a( { class: "bb-item", href }, span({ class: "bb-ico" }, ico), (badge && badge > 0) ? span({ class: "bb-badge" }, String(badge)) : "", span({ class: "bb-lbl" }, label) ); + const pinned = PINNABLE_MODULES.find(m => m.key === (cfg.bottomBarPin || '') && modOn(m.mod)); + const pinSlot = pinned + ? bbLink(pinned.href, pinned.glyph, i18n[pinned.labelKey] || pinned.key) + : a({ class: "bb-item bb-add", href: "/settings/bottombar" }, + span({ class: "bb-ico" }, "+"), + span({ class: "bb-lbl" }, i18n.bbAdd || "Add")); + const pair = [ bbLink("/peers", "⧖", i18n.peers || "Peers") ]; + if (modOn('invitesMod')) pair.push(bbLink("/invites", "ꔮ", i18n.invites || "Invites")); return nav( - { class: "oasis-bottombar", style: "display:none", "aria-label": "Quick actions" }, - bbLink("/search", "ꔅ", i18n.searchTitle || "Buscar"), - bbLink("/inbox", "☂", i18n.inbox || "Inbox", inboxN), - bbLink("/publish", "❂", i18n.publish || "Write"), - bbLink("/activity", "ꔙ", i18n.activityTitle || "Activity"), - div({ class: "bb-pair" }, - bbLink("/peers", "⧖", i18n.peers || "Peers"), - bbLink("/invites", "ꔮ", i18n.invites || "Invites") - ) + { class: "oasis-bottombar", style: "display:none", "aria-label": i18n.bbQuickActions || "Quick actions" }, + bbLink("/search", "ꔅ", i18n.searchTitle || "Search"), + bbLink("/inbox", "☂", i18n.inbox || "Inbox", inboxN), + bbLink("/publish", "❂", i18n.publish || "Write"), + pinSlot, + div({ class: "bb-pair" }, ...pair) ); }; @@ -3402,7 +3490,7 @@ exports.privateView = async (messagesInput, filter, decrypted = null) => { threadGroups[tid].push(msg) } - if (!threadOrder.length) return p({ class: 'empty' }, i18n.noPrivateMessages) + if (!threadOrder.length) return emptyState({ icon: "✉", title: i18n.noPrivateMessages, text: i18n.emptyConnectHint, ctaHref: "/invites", ctaLabel: i18n.emptyConnectCta }) return threadOrder.map(tid => { const msgs = threadGroups[tid]