From 3fe71a0ec23cdd3722510d23ab2a8e5935fb8aac Mon Sep 17 00:00:00 2001 From: SITO Date: Tue, 18 Aug 2026 21:15:23 +0200 Subject: [PATCH] views: mover topbar, barra inferior y hexagonos fuera de main_views main_views.js es el fichero de upstream con mas movimiento despues de backend.js (+256/-502 lineas de 0.9.1 a 0.9.5). La interfaz propia de la rama vivia dentro, asi que cada integracion de una version nueva la ponia en riesgo. renderMobileTopbar, renderBottomBar, renderHiveNav y PINNABLE_MODULES pasan a src/views/fork/hive_nav.js, una factory que recibe i18n por referencia (main_views lo muta sin reasignarlo). En main_views quedan 8 lineas de enganche en lugar de 168. Verificado que el HTML renderizado es identico byte a byte antes y despues, en las tres funciones y con los filtros personal, community y sin filtro. --- src/views/fork/hive_nav.js | 184 +++++++++++++++++++++++++++++++++++++ src/views/main_views.js | 176 ++--------------------------------- 2 files changed, 192 insertions(+), 168 deletions(-) create mode 100644 src/views/fork/hive_nav.js diff --git a/src/views/fork/hive_nav.js b/src/views/fork/hive_nav.js new file mode 100644 index 0000000..bbf3887 --- /dev/null +++ b/src/views/fork/hive_nav.js @@ -0,0 +1,184 @@ +"use strict"; +// Navegacion propia de la rama experimental para movil: topbar, barra inferior +// personalizable y rejilla de hexagonos. +// +// Vive fuera de main_views.js a proposito: upstream reescribe ese fichero en cada +// release (de 0.9.1 a 0.9.5 movio +256/-502 lineas) y esta interfaz se perdia en +// cada integracion. Aqui solo depende de i18n, que se inyecta porque main_views +// lo muta en cada peticion sin reasignarlo. + +const { a, div, img, input, label, nav, span } = require("../../server/node_modules/hyperaxe"); +const { getConfig } = require("../../configs/config-manager.js"); +const sharedState = require("../../configs/shared-state"); + +module.exports = ({ i18n }) => { + // === Oasis UX (portado de 0.8.7_UX): topbar + bottombar + hive === + // topbar fija arriba: [logo] [Personal] [Community] [avatar] en una línea (sin título OASIS) + const renderMobileTopbar = () => { + // FORK_IA_UX v3: Personal/Community VUELVEN al topbar (ya funcionan: la hoja cerrada es + // visibility:hidden y no bloquea los taps). Llena el top y da el filtro a un toque. + const hiveFilter = (typeof global !== "undefined" && global.__OASIS_HIVE_FILTER__) || ""; + return div( + { class: "oasis-mobile-topbar" }, + a({ class: "omt-logo", href: "/" }, img({ src: "/assets/images/snh-oasis.jpg", alt: "Oasis" })), + div({ class: "omt-menus" }, + a({ class: hiveFilter === "personal" ? "oasis-menu-btn active" : "oasis-menu-btn", href: hiveFilter === "personal" ? "/activity" : "/activity?hive=personal" }, span(i18n.menuPersonal || "Personal")), + a({ class: hiveFilter === "community" ? "oasis-menu-btn active" : "oasis-menu-btn", href: hiveFilter === "community" ? "/activity" : "/activity?hive=community" }, span(i18n.menuCommunity || "Community")) + ), + 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: 'search', href: '/search', glyph: 'ꔅ', labelKey: 'searchTitle', mod: null }, + { key: 'inbox', href: '/inbox', glyph: '☂', labelKey: 'inbox', mod: null }, + { key: 'publish', href: '/publish', glyph: '❂', labelKey: 'publish', mod: null }, + { key: 'activity', href: '/activity', glyph: 'ꔙ', labelKey: 'activityTitle', mod: null }, + { key: 'tribes', href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' }, + { key: 'chats', href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' }, + { key: 'karvan', href: '/karvan', glyph: '⧗', labelKey: 'karvanTitle', mod: 'karvanMod' }, + { 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 }, + ]; + + const renderBottomBar = () => { + if (process.env.OASIS_MOBILE !== '1') return ""; + 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) + ); + // FORK_IA_UX v4: barra PERSONALIZABLE (pins) + lápiz ✎ para editar (/settings/bottombar) + // + par FIJO Peers/Invites a la derecha. Sin FAB "+". (restaurada de la versión que gustaba) + const MAX = 4; + const pins = (cfg.bottomBarPins || []) + .map(key => PINNABLE_MODULES.find(m => m.key === key)) + .filter(m => m && modOn(m.mod)) + .slice(0, MAX); + const slots = pins.map(m => bbLink( + m.href, m.glyph, i18n[m.labelKey] || m.key, m.key === 'inbox' ? inboxN : 0 + )); + const editSlot = a( + { class: "bb-item bb-add", href: "/settings/bottombar" }, + span({ class: "bb-ico" }, "✎"), + span({ class: "bb-lbl" }, i18n.bbManage || "Edit") + ); + const pair = [ bbLink("/peers", "⧖", i18n.peers || "Peers") ]; + if (modOn('invitesMod')) pair.push(bbLink("/invites", "ꔮ", i18n.invites || "Invites")); + return nav( + { class: "oasis-bottombar", "aria-label": i18n.bbQuickActions || "Quick actions" }, + ...slots, + editSlot, + div({ class: "bb-pair" }, ...pair) + ); + }; + + const renderHiveNav = () => { + if (process.env.OASIS_MOBILE !== '1') return ""; + const cfg = getConfig(); + const modOn = (m) => !m || cfg.modules[m] === 'on' || cfg.modules[m] === undefined; + const CATS = [ + { id: 'personal', emoji: '⚉', title: i18n.menuPersonal, mods: [ + { href: '/profile', glyph: '⚉', labelKey: 'profile', mod: null }, { href: '/cv', glyph: 'ꕛ', labelKey: 'cvTitle', mod: null }, + { href: '/agenda', glyph: 'ꗤ', labelKey: 'agendaTitle', mod: 'agendaMod' }, { href: '/favorites', glyph: 'ꘝ', labelKey: 'favoritesTitle', mod: 'favoritesMod' }, + { href: '/logs', glyph: 'ꗯ', labelKey: 'logsTitle', mod: 'logsMod' }, { href: '/wallet', glyph: '❄', labelKey: 'wallet', mod: 'walletMod' }, + { href: '/modules', glyph: 'ꗣ', labelKey: 'modules', mod: null }, { href: '/settings', glyph: '⚙', labelKey: 'settings', mod: null } ] }, + { id: 'governance', emoji: '⚖', title: i18n.menuGovernance, mods: [ + { href: '/inhabitants', glyph: 'ꖘ', labelKey: 'inhabitantsLabel', mod: null }, { href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' }, + { href: '/larp', glyph: '✦', labelKey: 'larpTitle', mod: 'larpMod' }, { href: '/parliament', glyph: 'ꗞ', labelKey: 'parliamentTitle', mod: 'parliamentMod' }, + { href: '/courts', glyph: 'ꖻ', labelKey: 'courtsTitle', mod: 'courtsMod' } ] }, + { id: 'office', emoji: '⌂', title: i18n.menuOffice, mods: [ + { href: '/votes', glyph: 'ꔰ', labelKey: 'votationsTitle', mod: 'votesMod' }, { href: '/events', glyph: 'ꕆ', labelKey: 'eventsLabel', mod: 'eventsMod' }, + { href: '/calendars', glyph: 'ꖯ', labelKey: 'calendarsTitle', mod: 'calendarsMod' }, { href: '/tasks', glyph: 'ꖧ', labelKey: 'tasksTitle', mod: 'tasksMod' }, + { href: '/reports', glyph: 'ꕥ', labelKey: 'reportsTitle', mod: 'reportsMod' } ] }, + { id: 'economy', emoji: '¤', title: i18n.menuEconomy, mods: [ + { href: '/banking', glyph: 'ꗴ', labelKey: 'bankingTitle', mod: 'bankingMod' }, { href: '/market', glyph: 'ꕻ', labelKey: 'marketTitle', mod: 'marketMod' }, + { href: '/projects', glyph: 'ꕧ', labelKey: 'projectsTitle', mod: 'projectsMod' }, { href: '/jobs', glyph: 'ꗒ', labelKey: 'jobsTitle', mod: 'jobsMod' }, + { href: '/shops', glyph: 'ꔜ', labelKey: 'shopsTitle', mod: 'shopsMod' }, { href: '/transfers', glyph: 'ꘉ', labelKey: 'transfersTitle', mod: 'transfersMod' } ] }, + { id: 'tools', emoji: '⚒', title: i18n.menuTools, mods: [ + { href: '/ai', glyph: 'ꘜ', labelKey: 'ai', mod: 'aiMod' }, { href: '/blockexplorer', glyph: 'ꖸ', labelKey: 'blockchain', mod: null }, + { href: '/cipher', glyph: 'ꗄ', labelKey: 'cipher', mod: 'cipherMod' }, { href: '/invites', glyph: 'ꔹ', labelKey: 'invites', mod: 'invitesMod' }, + { href: '/legacy', glyph: 'ꖤ', labelKey: 'legacy', mod: 'legacyMod' }, { href: '/stats', glyph: 'ꕷ', labelKey: 'statistics', mod: null } ] }, + { id: 'network', emoji: '☍', title: i18n.menuNetwork, mods: [ + { href: '/activity', glyph: 'ꔙ', labelKey: 'activityTitle', mod: null }, { href: '/tags', glyph: 'ꖶ', labelKey: 'tagsLabel', mod: 'tagsMod' }, + { href: '/trending', glyph: 'ꗝ', labelKey: 'trendingLabel', mod: 'trendingMod' }, { href: '/opinions', glyph: 'ꔍ', labelKey: 'opinionsTitle', mod: 'opinionsMod' }, + { href: '/pads', glyph: 'ꔗ', labelKey: 'padsTitle', mod: 'padsMod' }, { href: '/forum', glyph: 'ꕒ', labelKey: 'forumTitle', mod: 'forumMod' }, + { href: '/maps', glyph: 'ꔌ', labelKey: 'mapsLabel', mod: 'mapsMod' }, { href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' }, + { href: '/karvan', glyph: '⧗', labelKey: 'karvanTitle', mod: 'karvanMod' } ] }, + { id: 'blogs', emoji: '✦', title: i18n.menuBlogs, mods: [ + { href: '/mentions', glyph: '✺', labelKey: 'mentions', mod: null }, { href: '/public/latest', glyph: '☄', labelKey: 'latest', mod: 'latestMod' }, + { href: '/public/latest/threads', glyph: '♺', labelKey: 'threads', mod: 'threadsMod' }, { href: '/public/latest/topics', glyph: 'ϟ', labelKey: 'topics', mod: 'topicsMod' }, + { href: '/public/latest/summaries', glyph: '※', labelKey: 'summaries', mod: 'summariesMod' }, { href: '/public/popular/day', glyph: '⌘', labelKey: 'popular', mod: 'popularMod' }, + { href: '/public/latest/extended', glyph: '∞', labelKey: 'multiverse', mod: 'multiverseMod' } ] }, + { id: 'creative', emoji: '✎', title: i18n.menuCreative, mods: [ + { href: '/feed', glyph: 'ꕿ', labelKey: 'feedTitle', mod: 'feedMod' }, { href: '/games', glyph: 'ꕇ', labelKey: 'gamesTitle', mod: 'gamesMod' }, + { href: '/pixelia', glyph: 'ꔘ', labelKey: 'pixeliaTitle', mod: 'pixeliaMod' }, { href: '/melody', glyph: '♪', labelKey: 'melodyTitle', mod: 'melodyMod' } ] }, + { id: 'media', emoji: '▤', title: i18n.menuMedia, mods: [ + { href: '/audios', glyph: 'ꔿ', labelKey: 'audiosLabel', mod: 'audiosMod' }, { href: '/bookmarks', glyph: 'ꔪ', labelKey: 'bookmarksLabel', mod: 'bookmarksMod' }, + { href: '/documents', glyph: 'ꕨ', labelKey: 'docsLabel', mod: 'docsMod' }, { href: '/images', glyph: 'ꕥ', labelKey: 'imagesLabel', mod: 'imagesMod' }, + { href: '/torrents', glyph: 'ꖅ', labelKey: 'torrentsLabel', mod: 'torrentsMod' }, { href: '/videos', glyph: 'ꗟ', labelKey: 'videosLabel', mod: 'videosMod' } ] }, + { id: 'fediverse', emoji: '⌬', title: i18n.fediverse, mods: [ + { href: '/fediverse', glyph: 'ꗵ', labelKey: 'fediverseTimeline', mod: 'fediverseMod' } ] }, + ]; + // filtro server-side (lo pone el middleware desde ?hive=): personal = CATS[0..4], community = CATS[5..9], vacío = las 10 + const hiveFilter = (typeof global !== "undefined" && global.__OASIS_HIVE_FILTER__) || ""; + const shown = hiveFilter === "personal" ? CATS.slice(0, 5) : hiveFilter === "community" ? CATS.slice(5) : CATS; + const rowSizes = shown.length <= 5 ? [3, 2] : [3, 4, 3]; + const catHex = (c) => label({ for: `hive-${c.id}`, class: 'hex-cat' }, + span({ class: 'hx-ico' }, c.emoji), span({ class: 'hx-lbl' }, c.title)); + const modHex = (m) => a({ class: 'hex-mod', href: m.href }, + span({ class: 'hx-ico' }, m.glyph), span({ class: 'hx-lbl' }, i18n[m.labelKey] || '')); + const radios = shown.map(c => input({ type: 'radio', name: 'hive-cat', id: `hive-${c.id}`, class: 'hive-radio' })); + const catRows = []; + let idx = 0; + for (const size of rowSizes) { catRows.push(div({ class: 'hive-row' }, ...shown.slice(idx, idx + size).map(catHex))); idx += size; } + const grid = div({ class: 'hive-grid hive-cats' }, ...catRows); + const panels = shown.map(c => div({ class: 'hive-panel', id: `hive-panel-${c.id}` }, + div({ class: 'hive-mods' }, ...c.mods.filter(m => modOn(m.mod)).map(modHex)) + )); + return div({ class: 'hive-nav' }, ...radios, grid, ...panels); + }; + + return { renderMobileTopbar, renderBottomBar, renderHiveNav, PINNABLE_MODULES }; +}; diff --git a/src/views/main_views.js b/src/views/main_views.js index 4298afb..72a7c9c 100644 --- a/src/views/main_views.js +++ b/src/views/main_views.js @@ -1102,174 +1102,14 @@ const renderTasksLink = () => { }; -// === Oasis UX (portado de 0.8.7_UX): topbar + bottombar + hive === -// topbar fija arriba: [logo] [Personal] [Community] [avatar] en una línea (sin título OASIS) -const renderMobileTopbar = () => { - // FORK_IA_UX v3: Personal/Community VUELVEN al topbar (ya funcionan: la hoja cerrada es - // visibility:hidden y no bloquea los taps). Llena el top y da el filtro a un toque. - const hiveFilter = (typeof global !== "undefined" && global.__OASIS_HIVE_FILTER__) || ""; - return div( - { class: "oasis-mobile-topbar" }, - a({ class: "omt-logo", href: "/" }, img({ src: "/assets/images/snh-oasis.jpg", alt: "Oasis" })), - div({ class: "omt-menus" }, - a({ class: hiveFilter === "personal" ? "oasis-menu-btn active" : "oasis-menu-btn", href: hiveFilter === "personal" ? "/activity" : "/activity?hive=personal" }, span(i18n.menuPersonal || "Personal")), - a({ class: hiveFilter === "community" ? "oasis-menu-btn active" : "oasis-menu-btn", href: hiveFilter === "community" ? "/activity" : "/activity?hive=community" }, span(i18n.menuCommunity || "Community")) - ), - 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: 'search', href: '/search', glyph: 'ꔅ', labelKey: 'searchTitle', mod: null }, - { key: 'inbox', href: '/inbox', glyph: '☂', labelKey: 'inbox', mod: null }, - { key: 'publish', href: '/publish', glyph: '❂', labelKey: 'publish', mod: null }, - { key: 'activity', href: '/activity', glyph: 'ꔙ', labelKey: 'activityTitle', mod: null }, - { key: 'tribes', href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' }, - { key: 'chats', href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' }, - { key: 'karvan', href: '/karvan', glyph: '⧗', labelKey: 'karvanTitle', mod: 'karvanMod' }, - { 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 = () => { - if (process.env.OASIS_MOBILE !== '1') return ""; - 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) - ); - // FORK_IA_UX v4: barra PERSONALIZABLE (pins) + lápiz ✎ para editar (/settings/bottombar) - // + par FIJO Peers/Invites a la derecha. Sin FAB "+". (restaurada de la versión que gustaba) - const MAX = 4; - const pins = (cfg.bottomBarPins || []) - .map(key => PINNABLE_MODULES.find(m => m.key === key)) - .filter(m => m && modOn(m.mod)) - .slice(0, MAX); - const slots = pins.map(m => bbLink( - m.href, m.glyph, i18n[m.labelKey] || m.key, m.key === 'inbox' ? inboxN : 0 - )); - const editSlot = a( - { class: "bb-item bb-add", href: "/settings/bottombar" }, - span({ class: "bb-ico" }, "✎"), - span({ class: "bb-lbl" }, i18n.bbManage || "Edit") - ); - const pair = [ bbLink("/peers", "⧖", i18n.peers || "Peers") ]; - if (modOn('invitesMod')) pair.push(bbLink("/invites", "ꔮ", i18n.invites || "Invites")); - return nav( - { class: "oasis-bottombar", "aria-label": i18n.bbQuickActions || "Quick actions" }, - ...slots, - editSlot, - div({ class: "bb-pair" }, ...pair) - ); -}; - -const renderHiveNav = () => { - if (process.env.OASIS_MOBILE !== '1') return ""; - const cfg = getConfig(); - const modOn = (m) => !m || cfg.modules[m] === 'on' || cfg.modules[m] === undefined; - const CATS = [ - { id: 'personal', emoji: '⚉', title: i18n.menuPersonal, mods: [ - { href: '/profile', glyph: '⚉', labelKey: 'profile', mod: null }, { href: '/cv', glyph: 'ꕛ', labelKey: 'cvTitle', mod: null }, - { href: '/agenda', glyph: 'ꗤ', labelKey: 'agendaTitle', mod: 'agendaMod' }, { href: '/favorites', glyph: 'ꘝ', labelKey: 'favoritesTitle', mod: 'favoritesMod' }, - { href: '/logs', glyph: 'ꗯ', labelKey: 'logsTitle', mod: 'logsMod' }, { href: '/wallet', glyph: '❄', labelKey: 'wallet', mod: 'walletMod' }, - { href: '/modules', glyph: 'ꗣ', labelKey: 'modules', mod: null }, { href: '/settings', glyph: '⚙', labelKey: 'settings', mod: null } ] }, - { id: 'governance', emoji: '⚖', title: i18n.menuGovernance, mods: [ - { href: '/inhabitants', glyph: 'ꖘ', labelKey: 'inhabitantsLabel', mod: null }, { href: '/tribes', glyph: 'ꖥ', labelKey: 'tribesTitle', mod: 'tribesMod' }, - { href: '/larp', glyph: '✦', labelKey: 'larpTitle', mod: 'larpMod' }, { href: '/parliament', glyph: 'ꗞ', labelKey: 'parliamentTitle', mod: 'parliamentMod' }, - { href: '/courts', glyph: 'ꖻ', labelKey: 'courtsTitle', mod: 'courtsMod' } ] }, - { id: 'office', emoji: '⌂', title: i18n.menuOffice, mods: [ - { href: '/votes', glyph: 'ꔰ', labelKey: 'votationsTitle', mod: 'votesMod' }, { href: '/events', glyph: 'ꕆ', labelKey: 'eventsLabel', mod: 'eventsMod' }, - { href: '/calendars', glyph: 'ꖯ', labelKey: 'calendarsTitle', mod: 'calendarsMod' }, { href: '/tasks', glyph: 'ꖧ', labelKey: 'tasksTitle', mod: 'tasksMod' }, - { href: '/reports', glyph: 'ꕥ', labelKey: 'reportsTitle', mod: 'reportsMod' } ] }, - { id: 'economy', emoji: '¤', title: i18n.menuEconomy, mods: [ - { href: '/banking', glyph: 'ꗴ', labelKey: 'bankingTitle', mod: 'bankingMod' }, { href: '/market', glyph: 'ꕻ', labelKey: 'marketTitle', mod: 'marketMod' }, - { href: '/projects', glyph: 'ꕧ', labelKey: 'projectsTitle', mod: 'projectsMod' }, { href: '/jobs', glyph: 'ꗒ', labelKey: 'jobsTitle', mod: 'jobsMod' }, - { href: '/shops', glyph: 'ꔜ', labelKey: 'shopsTitle', mod: 'shopsMod' }, { href: '/transfers', glyph: 'ꘉ', labelKey: 'transfersTitle', mod: 'transfersMod' } ] }, - { id: 'tools', emoji: '⚒', title: i18n.menuTools, mods: [ - { href: '/ai', glyph: 'ꘜ', labelKey: 'ai', mod: 'aiMod' }, { href: '/blockexplorer', glyph: 'ꖸ', labelKey: 'blockchain', mod: null }, - { href: '/cipher', glyph: 'ꗄ', labelKey: 'cipher', mod: 'cipherMod' }, { href: '/invites', glyph: 'ꔹ', labelKey: 'invites', mod: 'invitesMod' }, - { href: '/legacy', glyph: 'ꖤ', labelKey: 'legacy', mod: 'legacyMod' }, { href: '/stats', glyph: 'ꕷ', labelKey: 'statistics', mod: null } ] }, - { id: 'network', emoji: '☍', title: i18n.menuNetwork, mods: [ - { href: '/activity', glyph: 'ꔙ', labelKey: 'activityTitle', mod: null }, { href: '/tags', glyph: 'ꖶ', labelKey: 'tagsLabel', mod: 'tagsMod' }, - { href: '/trending', glyph: 'ꗝ', labelKey: 'trendingLabel', mod: 'trendingMod' }, { href: '/opinions', glyph: 'ꔍ', labelKey: 'opinionsTitle', mod: 'opinionsMod' }, - { href: '/pads', glyph: 'ꔗ', labelKey: 'padsTitle', mod: 'padsMod' }, { href: '/forum', glyph: 'ꕒ', labelKey: 'forumTitle', mod: 'forumMod' }, - { href: '/maps', glyph: 'ꔌ', labelKey: 'mapsLabel', mod: 'mapsMod' }, { href: '/chats', glyph: 'ꖒ', labelKey: 'chatsTitle', mod: 'chatsMod' }, - { href: '/karvan', glyph: '⧗', labelKey: 'karvanTitle', mod: 'karvanMod' } ] }, - { id: 'blogs', emoji: '✦', title: i18n.menuBlogs, mods: [ - { href: '/mentions', glyph: '✺', labelKey: 'mentions', mod: null }, { href: '/public/latest', glyph: '☄', labelKey: 'latest', mod: 'latestMod' }, - { href: '/public/latest/threads', glyph: '♺', labelKey: 'threads', mod: 'threadsMod' }, { href: '/public/latest/topics', glyph: 'ϟ', labelKey: 'topics', mod: 'topicsMod' }, - { href: '/public/latest/summaries', glyph: '※', labelKey: 'summaries', mod: 'summariesMod' }, { href: '/public/popular/day', glyph: '⌘', labelKey: 'popular', mod: 'popularMod' }, - { href: '/public/latest/extended', glyph: '∞', labelKey: 'multiverse', mod: 'multiverseMod' } ] }, - { id: 'creative', emoji: '✎', title: i18n.menuCreative, mods: [ - { href: '/feed', glyph: 'ꕿ', labelKey: 'feedTitle', mod: 'feedMod' }, { href: '/games', glyph: 'ꕇ', labelKey: 'gamesTitle', mod: 'gamesMod' }, - { href: '/pixelia', glyph: 'ꔘ', labelKey: 'pixeliaTitle', mod: 'pixeliaMod' }, { href: '/melody', glyph: '♪', labelKey: 'melodyTitle', mod: 'melodyMod' } ] }, - { id: 'media', emoji: '▤', title: i18n.menuMedia, mods: [ - { href: '/audios', glyph: 'ꔿ', labelKey: 'audiosLabel', mod: 'audiosMod' }, { href: '/bookmarks', glyph: 'ꔪ', labelKey: 'bookmarksLabel', mod: 'bookmarksMod' }, - { href: '/documents', glyph: 'ꕨ', labelKey: 'docsLabel', mod: 'docsMod' }, { href: '/images', glyph: 'ꕥ', labelKey: 'imagesLabel', mod: 'imagesMod' }, - { href: '/torrents', glyph: 'ꖅ', labelKey: 'torrentsLabel', mod: 'torrentsMod' }, { href: '/videos', glyph: 'ꗟ', labelKey: 'videosLabel', mod: 'videosMod' } ] }, - { id: 'fediverse', emoji: '⌬', title: i18n.fediverse, mods: [ - { href: '/fediverse', glyph: 'ꗵ', labelKey: 'fediverseTimeline', mod: 'fediverseMod' } ] }, - ]; - // filtro server-side (lo pone el middleware desde ?hive=): personal = CATS[0..4], community = CATS[5..9], vacío = las 10 - const hiveFilter = (typeof global !== "undefined" && global.__OASIS_HIVE_FILTER__) || ""; - const shown = hiveFilter === "personal" ? CATS.slice(0, 5) : hiveFilter === "community" ? CATS.slice(5) : CATS; - const rowSizes = shown.length <= 5 ? [3, 2] : [3, 4, 3]; - const catHex = (c) => label({ for: `hive-${c.id}`, class: 'hex-cat' }, - span({ class: 'hx-ico' }, c.emoji), span({ class: 'hx-lbl' }, c.title)); - const modHex = (m) => a({ class: 'hex-mod', href: m.href }, - span({ class: 'hx-ico' }, m.glyph), span({ class: 'hx-lbl' }, i18n[m.labelKey] || '')); - const radios = shown.map(c => input({ type: 'radio', name: 'hive-cat', id: `hive-${c.id}`, class: 'hive-radio' })); - const catRows = []; - let idx = 0; - for (const size of rowSizes) { catRows.push(div({ class: 'hive-row' }, ...shown.slice(idx, idx + size).map(catHex))); idx += size; } - const grid = div({ class: 'hive-grid hive-cats' }, ...catRows); - const panels = shown.map(c => div({ class: 'hive-panel', id: `hive-panel-${c.id}` }, - div({ class: 'hive-mods' }, ...c.mods.filter(m => modOn(m.mod)).map(modHex)) - )); - return div({ class: 'hive-nav' }, ...radios, grid, ...panels); -}; +// === Oasis UX: topbar + bottombar + hive === +// La implementacion vive en ./fork/hive_nav.js para que las reescrituras de +// upstream sobre este fichero no arrastren la interfaz de la rama. +const forkNav = require('./fork/hive_nav')({ i18n }); +const renderMobileTopbar = forkNav.renderMobileTopbar; +const renderBottomBar = forkNav.renderBottomBar; +const renderHiveNav = forkNav.renderHiveNav; +exports.PINNABLE_MODULES = forkNav.PINNABLE_MODULES; exports.renderHiveNav = renderHiveNav; const template = (titlePrefix, ...elements) => {