upstream: integrar Oasis 1.3.1

Base del merge a tres bandas: git 0.9.5 de epsylon, esta rama y el src de la
APK oficial 1.3.1 (la variante movil: OASIS_MOBILE, regex sin \p{}, audio y
video de Android). Corre en el mismo nodejs-mobile 18.20.4 que ya usabamos.

Se conserva de la rama: Karvan, identidades y multicuenta, topbar y barra
inferior, borrado de EXIF sin sharp, filtro hot del foro, RTC sin STUN y los
modulos apagados en movil (wallet, banking, torrents).

Se toma de upstream lo que ya hace de serie: respuesta citada en chats, ssb-lan
en movil, configuracion en ~/.ssb/oasis (sobrevive a reinstalar el backend).
El asistente de bienvenida deja elegir workflow tambien en movil.
This commit is contained in:
oasis-bot 2026-10-18 02:52:17 +02:00
parent 8d838dd8b1
commit c332afd6ea
265 changed files with 80970 additions and 29167 deletions

View file

@ -1,7 +1,7 @@
const { form, button, div, h2, p, section, table, tr, td, input, a, br, ul, li, span, strong } = require("../server/node_modules/hyperaxe");
const { template, i18n } = require('./main_views');
const { getConfig } = require('../configs/config-manager.js');
const { MOBILE_MODULES, ALL_MODULES } = require('../models/workflows_model');
const { PRESETS: PRESET_LIST } = require('../models/workflows_model');
const modulesView = () => {
const config = getConfig().modules;
@ -9,6 +9,13 @@ const modulesView = () => {
{ name: 'agenda', label: i18n.modulesAgendaLabel, description: i18n.modulesAgendaDescription },
{ name: 'ai', label: i18n.modulesAILabel, description: i18n.modulesAIDescription },
{ name: 'aiNav', label: i18n.modulesAINavLabel, description: i18n.modulesAINavDescription },
{ name: 'emergencies', label: i18n.modulesEmergenciesLabel, description: i18n.modulesEmergenciesDescription },
{ name: 'mailing', label: i18n.modulesMailingLabel, description: i18n.modulesMailingDescription },
{ name: 'rooms', label: i18n.modulesRoomsLabel, description: i18n.modulesRoomsDescription },
{ name: 'phone', label: i18n.modulesPhoneLabel, description: i18n.modulesPhoneDescription },
{ name: 'logistics', label: i18n.modulesLogisticsLabel, description: i18n.modulesLogisticsDescription },
{ name: 'podcasts', label: i18n.modulesPodcastsLabel, description: i18n.modulesPodcastsDescription },
{ name: 'campaigns', label: i18n.modulesCampaignsLabel, description: i18n.modulesCampaignsDescription },
{ name: 'audios', label: i18n.modulesAudiosLabel, description: i18n.modulesAudiosDescription },
{ name: 'banking', label: i18n.modulesBankingLabel, description: i18n.modulesBankingDescription },
{ name: 'bookmarks', label: i18n.modulesBookmarksLabel, description: i18n.modulesBookmarksDescription },
@ -23,16 +30,18 @@ const modulesView = () => {
{ name: 'blogs', label: i18n.modulesBlogsLabel, description: i18n.modulesBlogsDescription },
{ name: 'fediverse', label: i18n.modulesFediverseLabel, description: i18n.modulesFediverseDescription },
{ name: 'feed', label: i18n.modulesFeedLabel, description: i18n.modulesFeedDescription },
{ name: 'files', label: i18n.modulesFilesLabel, description: i18n.modulesFilesDescription },
{ name: 'forum', label: i18n.modulesForumLabel, description: i18n.modulesForumDescription },
{ name: 'games', label: i18n.modulesGamesLabel, description: i18n.modulesGamesDescription },
{ name: 'graphos', label: i18n.modulesGraphosLabel, description: i18n.modulesGraphosDescription },
{ name: 'housing', label: i18n.modulesHousingLabel, description: i18n.modulesHousingDescription },
{ name: 'images', label: i18n.modulesImagesLabel, description: i18n.modulesImagesDescription },
{ name: 'industry', label: i18n.modulesIndustryLabel, description: i18n.modulesIndustryDescription },
{ name: 'invites', label: i18n.modulesInvitesLabel, description: i18n.modulesInvitesDescription },
{ name: 'jobs', label: i18n.modulesJobsLabel, description: i18n.modulesJobsDescription },
{ name: 'karvan', label: i18n.modulesKarvanLabel, description: i18n.modulesKarvanDescription },
{ name: 'larp', label: i18n.modulesLarpLabel, description: i18n.modulesLarpDescription },
{ name: 'legacy', label: i18n.modulesLegacyLabel, description: i18n.modulesLegacyDescription },
{ name: 'backup', label: i18n.modulesBackupLabel, description: i18n.modulesBackupDescription },
{ name: 'logs', label: i18n.modulesLogsLabel, description: i18n.modulesLogsDescription },
{ name: 'maps', label: i18n.modulesMapLabel, description: i18n.modulesMapDescription },
{ name: 'market', label: i18n.modulesMarketLabel, description: i18n.modulesMarketDescription },
@ -44,6 +53,7 @@ const modulesView = () => {
{ name: 'polls', label: i18n.modulesPollsLabel, description: i18n.modulesPollsDescription },
{ name: 'projects', label: i18n.modulesProjectsLabel, description: i18n.modulesProjectsDescription },
{ name: 'reports', label: i18n.modulesReportsLabel, description: i18n.modulesReportsDescription },
{ name: 'school', label: i18n.modulesSchoolLabel, description: i18n.modulesSchoolDescription },
{ name: 'shops', label: i18n.modulesShopsLabel, description: i18n.modulesShopsDescription },
{ name: 'tags', label: i18n.modulesTagsLabel, description: i18n.modulesTagsDescription },
{ name: 'tasks', label: i18n.modulesTasksLabel, description: i18n.modulesTasksDescription },
@ -53,49 +63,49 @@ const modulesView = () => {
{ name: 'tribes', label: i18n.modulesTribesLabel, description: i18n.modulesTribesDescription },
{ name: 'videos', label: i18n.modulesVideosLabel, description: i18n.modulesVideosDescription },
{ name: 'votes', label: i18n.modulesVotationsLabel, description: i18n.modulesVotationsDescription },
{ name: 'wallet', label: i18n.modulesWalletLabel, description: i18n.modulesWalletDescription }
];
{ name: 'wallet', label: i18n.modulesWalletLabel, description: i18n.modulesWalletDescription },
{ name: 'wiki', label: i18n.modulesWikiLabel, description: i18n.modulesWikiDescription }
].sort((x, y) => String(x.label || x.name).localeCompare(String(y.label || y.name), undefined, { sensitivity: 'base' }));
const moduleStates = modules.reduce((acc, mod) => {
const MOBILE_UNAVAILABLE = ['ai', 'aiNav', 'dev', 'games', 'logs', 'maps', 'pixelia'];
const isMobile = process.env.OASIS_MOBILE === '1';
const visibleModules = isMobile ? modules.filter(mod => !MOBILE_UNAVAILABLE.includes(mod.name)) : modules;
const moduleStates = visibleModules.reduce((acc, mod) => {
acc[`${mod.name}Mod`] = config[`${mod.name}Mod`] === 'on' ? 'on' : 'off';
return acc;
}, {});
const activeModulesCount = modules.filter(mod => moduleStates[`${mod.name}Mod`] === 'on').length;
const totalModulesCount = modules.length;
const activeModulesCount = visibleModules.filter(mod => moduleStates[`${mod.name}Mod`] === 'on').length;
const totalModulesCount = visibleModules.length;
const disabledModulesCount = totalModulesCount - activeModulesCount;
const header = div({ class: 'tags-header' },
const header = [div({ class: 'tags-header module-header-line' },
h2(i18n.modulesViewTitle),
p(i18n.modulesViewDescription),
div({ class: 'modules-counts' },
`${i18n.modulesTotalModulesLabel}: `,
strong({ class: 'modules-count-value' }, totalModulesCount),
ul({ class: 'modules-count-list' },
li({ class: 'modules-count-item' },
`${i18n.modulesEnabledModulesLabel}: `,
span({ class: 'modules-count-value' }, activeModulesCount)
),
li({ class: 'modules-count-item' },
`${i18n.modulesDisabledModulesLabel}: `,
span({ class: 'modules-count-value' }, disabledModulesCount)
)
)
)
);
p(i18n.modulesViewDescription)
)];
const PRESETS = {
minimal: ['feed', 'forum', 'games', 'images', 'videos', 'audios', 'bookmarks', 'tags', 'trending', 'blogs', 'polls', 'opinions', 'cipher', 'legacy'],
social: ['agenda', 'audios', 'bookmarks', 'calendars', 'chats', 'cipher', 'courts', 'docs', 'events', 'favorites', 'fediverse', 'feed', 'forum', 'games', 'images', 'invites', 'larp', 'legacy', 'logs', 'maps', 'blogs', 'polls', 'opinions', 'pads', 'parliament', 'pixelia', 'melody', 'projects', 'reports', 'tags', 'tasks', 'trending', 'tribes', 'videos', 'votes'],
economy: ['agenda', 'audios', 'bookmarks', 'calendars', 'chats', 'cipher', 'courts', 'docs', 'events', 'favorites', 'fediverse', 'feed', 'forum', 'games', 'images', 'invites', 'larp', 'legacy', 'logs', 'maps', 'blogs', 'polls', 'opinions', 'pads', 'parliament', 'pixelia', 'melody', 'projects', 'reports', 'tags', 'tasks', 'trending', 'tribes', 'videos', 'votes', 'banking', 'wallet', 'transfers', 'market', 'jobs', 'shops', 'industry'],
mobile: MOBILE_MODULES,
full: ALL_MODULES
};
const counts = div({ class: 'modules-counts' },
span({ class: 'modules-count-item' },
`${i18n.modulesTotalModulesLabel}: `,
strong({ class: 'modules-count-value' }, totalModulesCount)
),
span({ class: 'modules-count-item' },
`${i18n.modulesEnabledModulesLabel}: `,
strong({ class: 'modules-count-value' }, activeModulesCount)
),
span({ class: 'modules-count-item' },
`${i18n.modulesDisabledModulesLabel}: `,
strong({ class: 'modules-count-value' }, disabledModulesCount)
)
);
const PRESETS = PRESET_LIST;
const presetButtons = div({ class: 'preset-group' },
Object.entries(PRESETS).map(([key, mods]) => {
const presetLabel = (i18n[`modulesPreset_${key}`] || key).toUpperCase();
const isActive = modules.every(m => mods.includes(m.name) === (moduleStates[`${m.name}Mod`] === 'on'));
const isActive = visibleModules.every(m => mods.includes(m.name) === (moduleStates[`${m.name}Mod`] === 'on'));
return form({ action: "/modules/preset", method: "post", class: "modules-preset-form" },
input({ type: "hidden", name: "preset", value: key }),
button({
@ -110,8 +120,8 @@ const modulesView = () => {
i18n.modules,
section(header),
section(
h2(i18n.modulesPresetTitle || "Common Configurations"),
presetButtons,
...(isMobile ? [] : [h2(i18n.modulesPresetTitle || "Common Configurations"), presetButtons]),
counts,
form(
{ action: "/save-modules", method: "post" },
table(
@ -121,7 +131,7 @@ const modulesView = () => {
td(i18n.modulesModuleDescription),
td({ class: 'module-status-cell' }, i18n.modulesModuleStatus)
),
modules.map(mod =>
visibleModules.map(mod =>
tr(
td(a({ href: `/${mod.name}` }, mod.label)),
td(p(mod.description)),