diff --git a/src/backend/fork_routes.js b/src/backend/fork_routes.js
index 368ef0c..21471d7 100644
--- a/src/backend/fork_routes.js
+++ b/src/backend/fork_routes.js
@@ -4,8 +4,9 @@
// Va en la cadena justo delante de las rutas del dev (backend.js lo monta con una
// sola linea). Lo que no atiende pasa al del dev con next(). Aqui viven:
// - mobile_layer.js: lanzador, filtro y avatar de la barra de arriba
-// - wrapUpstream: mira lo que contesta el dev (seccion apagada, guardar modulos)
-// - las rutas propias: tema y Modulos en el movil, barra de abajo, identidades
+// - wrapUpstream: mira lo que contesta el dev (seccion apagada, guardar modulos,
+// vuelta a Ajustes desde los formularios de la bienvenida)
+// - las rutas propias: tema, colores y Modulos en el movil, barra de abajo, identidades
//
// backend.js es del dev y cambia en 34 de cada 39 versiones: nada de esto va alli.
// Ver GUIA_DE_DESARROLLO.md.
@@ -43,6 +44,13 @@ const wrapUpstream = async (ctx, next) => {
await next();
if (!isMobile() || ctx.status < 300 || ctx.status >= 400) return;
const to = String(ctx.response.get('Location') || '');
+ // identidad y saludo desde Ajustes usan las rutas de la bienvenida (las del dev);
+ // estas vuelven a /welcome y aqui se cambia por el bloque de Ajustes de donde se vino
+ if (ctx.method === 'POST' && /^\/welcome\/(profile|greeting)$/.test(ctx.path) && /^(https?:\/\/[^/]+)?\/welcome$/.test(to)) {
+ const back = String((ctx.request.body || {}).returnTo || '');
+ if (/^\/settings(#[a-z-]+)?$/.test(back)) ctx.redirect(back);
+ return;
+ }
// guardar en Modulos: volver con el aviso de guardado
if (ctx.method === 'POST' && ctx.path === '/save-modules') { ctx.redirect('/modules?saved=1'); return; }
if (ctx.method === 'GET' && ctx.path !== '/modules' && /^(https?:\/\/[^/]+)?\/modules$/.test(to)) {
@@ -69,6 +77,27 @@ const mobileLayer = require('./mobile_layer')({ getViewerId, about });
ctx.cookies.set('theme', theme, { httpOnly: true, sameSite: 'strict', secure: ctx.secure });
ctx.redirect('/settings#theme');
})
+ // colores del movil (mobile_settings.js): se guardan en la config y el aspecto pasa
+ // a Acampada, que es el tema que lee esas variables
+ .post('/settings/colors', koaBody(), async (ctx, next) => {
+ if (!isMobile()) return next();
+ const body = ctx.request.body || {};
+ const cfg = getConfig();
+ cfg.mobileColors = require('../views/mobile_settings').colorsFromBody(body);
+ if ((cfg.themes || {}).current !== 'Acampada') {
+ cfg.themes = { ...(cfg.themes || {}), current: 'Acampada' };
+ ctx.cookies.set('theme', 'Acampada', { httpOnly: true, sameSite: 'strict', secure: ctx.secure });
+ }
+ saveConfig(cfg);
+ const back = String(body.returnTo || '');
+ ctx.redirect(/^\/(settings|welcome)(#[a-z-]+)?$/.test(back) ? back : '/settings#colors');
+ })
+ .get('/mobile-colors.css', async (ctx, next) => {
+ if (!isMobile()) return next();
+ ctx.type = 'text/css';
+ ctx.set('Cache-Control', 'public, max-age=31536000, immutable');
+ ctx.body = require('../views/mobile_settings').colorsCss(getConfig());
+ })
// Modulos en el movil: la vista propia (mobile_modules_view.js); en escritorio, la de upstream
.get('/modules', async (ctx, next) => {
if (!isMobile()) return next();
diff --git a/src/backend/mobile_layer.js b/src/backend/mobile_layer.js
index 611e3c7..0976852 100644
--- a/src/backend/mobile_layer.js
+++ b/src/backend/mobile_layer.js
@@ -2,7 +2,7 @@
// Lo que la interfaz de la rama necesita del backend, fuera de backend.js.
//
// Prepara lo que leen la barra de arriba y el lanzador de hive_views.js: el filtro
-// Personal/Social, que el lanzador no va en la portada y el avatar propio. Lo
+// Personal/Social, que el lanzador no va en la portada y el avatar y nombre propios. Lo
// monta el router propio (fork_routes.js) delante de las rutas del dev. Nada de
// esto corre en escritorio. Ver GUIA_DE_DESARROLLO.md.
//
@@ -24,11 +24,17 @@ module.exports = ({ getViewerId, about }) => {
if (typeof img === 'string' && img[0] === '&' && img !== NULL_IMG) global.__OASIS_SELF_AVATAR__ = `/image/256/${encodeURIComponent(img)}`;
else if (typeof img === 'string' && img[0] === '/') global.__OASIS_SELF_AVATAR__ = img;
else global.__OASIS_SELF_AVATAR__ = null;
+ // el nombre, para el bloque de identidad de Ajustes
+ const name = me ? await about.name(me) : null;
+ // sin nombre puesto, ssb-about da un trozo de la clave: eso no es un nombre
+ global.__OASIS_SELF_NAME__ = typeof name === 'string' && name[0] !== '@' && !String(me).startsWith('@' + name) ? name : '';
} catch (_) {}
};
return async (ctx, next) => {
if (!isMobile() || !isPage(ctx.path)) return next();
+ // al guardar nombre o avatar, la siguiente pagina ya los lee nuevos
+ if (ctx.method === 'POST' && /^\/(welcome\/profile|profile\/edit)$/.test(ctx.path)) { await next(); avatarAt = 0; return; }
global.__OASIS_HIVE_FILTER__ = (ctx.path === '/activity' && (ctx.query.hive === 'personal' || ctx.query.hive === 'community')) ? ctx.query.hive : '';
// el lanzador ya no va en la portada: la portada es la actividad y los modulos
// estan en la hoja de la pestaña Menu (docs/ESTUDIO_NAVEGACION.md)
diff --git a/src/client/assets/images/theme-acampada.png b/src/client/assets/images/theme-acampada.png
index c635277..2546f50 100644
Binary files a/src/client/assets/images/theme-acampada.png and b/src/client/assets/images/theme-acampada.png differ
diff --git a/src/client/assets/images/theme-oasismobile.png b/src/client/assets/images/theme-oasismobile.png
index 2f9bfaf..668009e 100644
Binary files a/src/client/assets/images/theme-oasismobile.png and b/src/client/assets/images/theme-oasismobile.png differ
diff --git a/src/client/assets/styles/mobile_ux.css b/src/client/assets/styles/mobile_ux.css
index 7408f0a..eb2a903 100644
--- a/src/client/assets/styles/mobile_ux.css
+++ b/src/client/assets/styles/mobile_ux.css
@@ -15,7 +15,7 @@
--ux-bg: var(--a26-bg, #121212);
--ux-fg: var(--a26-fg, #f2f2f2);
--ux-accent: var(--a26-neon, #ffd700);
- --ux-on-accent: #000;
+ --ux-on-accent: var(--a26-on-neon, #000);
--ux-soft: var(--a26-soft, #1e1e1e);
--ux-muted: var(--a26-muted, #a8a8a8);
--ux-line: var(--a26-fg, #3a3a3a);
@@ -365,7 +365,7 @@ body .main-content > .main-column { padding-left: 0 !important; padding-right: 0
/* filas etiqueta / valor de las fichas (plazo, estado, creado el): en dos columnas */
.main-column table.tribe-info-table tr { display: grid !important; grid-template-columns: minmax(96px, 38%) 1fr; align-items: center; }
.main-column table.tribe-info-table td { display: block !important; border: 0 !important; padding: 8px 10px !important; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
-.main-column table.tribe-info-table tr + tr { border-top: 1px solid #ccc !important; }
+.main-column table.tribe-info-table tr + tr { border-top: 1px solid var(--a26-line, #ccc) !important; }
/* bienvenida: "Ahora no" al final, como enlace discreto */
.main-column .welcome-skip-end { text-align: center; margin: 8px 0 0 !important; }
@@ -624,6 +624,56 @@ body:has(#oasis-menu-toggle:checked) form.oasis-phone-state { display: none !imp
}
@media (min-width: 700px) { .main-column .wf-pick button.ux-pick-card { flex-direction: row; text-align: left; margin: 0 0 10px !important; } }
+/* ---------- Ajustes: los seis accesos de arriba (mobile_settings.js) ---------- */
+/* rejilla de 3x2 a 360px: icono, nombre y lo que hay puesto. Cada acceso baja a su
+ bloque; el bloque deja aire arriba para que el titulo no quede pegado al borde */
+.main-column section[id] { scroll-margin-top: 76px; }
+.main-column div.quick-grid { display: grid !important; grid-template-columns: repeat(3, minmax(0, 1fr)); grid-gap: 10px; margin: 0 0 6px !important; padding: 0 !important; }
+.main-column a.quick-tile {
+ display: flex !important; flex-direction: column; align-items: center; justify-content: flex-start; text-align: center;
+ min-height: 104px; min-width: 0; padding: 12px 6px 10px !important; box-sizing: border-box; text-decoration: none !important;
+ background: var(--ux-bg) !important; color: var(--ux-fg) !important;
+ border: 2px solid var(--ux-line) !important; border-radius: 14px !important; box-shadow: 0 3px 0 var(--ux-line) !important;
+}
+.main-column a.quick-tile:active { transform: translateY(3px); box-shadow: 0 0 0 var(--ux-line) !important; background: var(--ux-accent) !important; color: var(--ux-on-accent) !important; }
+.main-column .quick-tile .quick-ico.has-svg { display: block; width: 30px; height: 30px; margin: 0 0 6px; }
+.main-column .quick-tile .quick-ico svg { width: 100%; height: 100%; }
+.main-column .quick-tile .quick-name { display: block; font-size: 14px; font-weight: 700; line-height: 1.15; }
+.main-column .quick-tile .quick-now { display: block; max-width: 100%; margin-top: 3px; font-size: 12px; line-height: 1.2; color: var(--ux-muted) !important; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.main-column .quick-tile:active .quick-now { color: inherit !important; }
+.main-column div.quick-links { display: block !important; margin: 0 !important; padding: 0 !important; }
+.main-column div.quick-links a { display: block !important; text-align: center; margin: 0 0 10px !important; }
+.main-column section#identity .filter-btn { display: inline-block; margin-top: 12px !important; }
+
+/* ---------- colores ---------- */
+/* paletas de un toque, dos por fila, con su muestra (fondo, texto y acento); debajo,
+ "A tu gusto" con los tres selectores de color del sistema */
+.main-column div.color-pick { margin: 0 !important; padding: 0 !important; }
+.main-column div.pal-grid { display: grid !important; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-gap: 10px; padding: 0 !important; margin: 0 0 12px !important; }
+.main-column button.pal-card {
+ display: flex !important; flex-direction: row !important; flex-wrap: nowrap !important; align-items: center; width: 100% !important; min-width: 0 !important; min-height: 56px !important; height: auto !important;
+ margin: 0 !important; padding: 8px 10px !important; text-align: left; text-transform: none !important; font-family: var(--ux-text) !important;
+ background: var(--ux-bg) !important; color: var(--ux-fg) !important; border: 2px solid var(--ux-line) !important; border-radius: 14px !important; box-shadow: 0 3px 0 var(--ux-line) !important;
+}
+.main-column button.pal-card.is-current { border-width: 3px !important; box-shadow: 0 0 0 3px var(--ux-accent), 0 3px 0 var(--ux-line) !important; }
+.main-column button.pal-card { justify-content: flex-start !important; }
+.main-column .pal-card .pal-name { flex: 1 1 auto; min-width: 0; text-align: left; font-size: 15px; font-weight: 700; line-height: 1.15; }
+.main-column .pal-card .ux-pick-now { display: none; }
+.pal-swatch { display: inline-flex; flex: 0 0 auto; width: 42px; height: 30px; margin-right: 10px; border-radius: 8px; overflow: hidden; border: 2px solid var(--ux-line); }
+.pal-swatch .pal-bg { flex: 2 1 0; }
+.pal-swatch .pal-fg { flex: 1 1 0; }
+.pal-swatch .pal-ac { flex: 1 1 0; }
+.main-column details.color-custom { margin: 4px 0 0 !important; padding: 0 !important; border: 2px solid var(--ux-line) !important; border-radius: 14px !important; background: var(--ux-bg) !important; }
+.main-column details.color-custom > summary { display: flex; align-items: center; min-height: 52px; padding: 8px 12px; font-weight: 700; cursor: pointer; list-style: none; }
+.main-column details.color-custom > summary::-webkit-details-marker { display: none; }
+.main-column details.color-custom > summary::after { content: "\25BE"; margin-left: auto; font-size: 18px; }
+.main-column details.color-custom[open] > summary::after { content: "\25B4"; }
+.main-column form.color-custom-form { padding: 4px 12px 14px !important; margin: 0 !important; border: 0 !important; background: transparent !important; box-shadow: none !important; }
+.main-column form.color-custom-form label.color-field { display: flex !important; align-items: center; justify-content: space-between; min-height: 52px; margin: 0 0 8px !important; }
+.main-column label.color-field input[type="color"] { width: 64px !important; height: 44px !important; padding: 2px !important; border: 2px solid var(--ux-line) !important; border-radius: 10px !important; background: var(--ux-bg) !important; }
+.main-column form.color-custom-form .create-button { width: 100% !important; margin-top: 6px !important; }
+.main-column .color-pick .welcome-action { margin-top: 4px !important; }
+
/* ---------- tabletas y pantallas anchas ---------- */
/* a 800dp todo se estiraba: lineas larguisimas y la hoja del menu con cinco iconos
repartidos en 800px. Contenido a 720px como mucho, centrado; la hoja, igual */
diff --git a/src/client/assets/translations/i18n_fork.js b/src/client/assets/translations/i18n_fork.js
index f5f3904..9e51ab1 100644
--- a/src/client/assets/translations/i18n_fork.js
+++ b/src/client/assets/translations/i18n_fork.js
@@ -22,6 +22,36 @@ module.exports = {
bbMenuClose: "Close",
uxPickHint: "Tap one and it is saved. You can change it any time in Settings.",
uxPickCurrent: "In use",
+ quickTitle: "The basics",
+ quickHint: "What you chose when you started, one tap away.",
+ quick_identity: "Identity",
+ quick_workflow: "Profile",
+ quick_ux: "Home screen",
+ quick_colors: "Colours",
+ quick_community: "Community",
+ quick_greeting: "Greeting",
+ quickProfileMore: "Description and more",
+ quickJoinPub: "Join a pub",
+ quickSeePeers: "Who you sync with",
+ quickGreetingPlaceholder: "Say hello to the community",
+ welcomeHintColors: "Background, text and highlight colour of the app.",
+ colorsHint: "Tap a palette and it is applied.",
+ colorsSkinNote: "Colours go with the Acampada look; choosing one switches to it.",
+ colorsCustom: "Your own",
+ colorsBg: "Background",
+ colorsFg: "Text",
+ colorsAccent: "Highlight",
+ colorsApply: "Apply",
+ colorsFixed: "The text could not be read on that background, so it was set to black or white.",
+ colorsKeep: "Keep these",
+ palette_acampada: "Acampada",
+ palette_noche: "Night",
+ palette_oro: "Gold",
+ palette_bosque: "Forest",
+ palette_lila: "Lilac",
+ palette_brasa: "Ember",
+ palette_papel: "Paper",
+ palette_contraste: "High contrast",
welcomeStepUseMobile: "Profile",
welcomeHintUseMobile: "What you use Oasis for. It turns on the modules you need.",
wfHint_activists: "Assemblies, tribes, votes, forums and shared documents",
@@ -116,6 +146,36 @@ module.exports = {
bbMenuClose: "Cerrar",
uxPickHint: "Toca uno y queda guardado. Puedes cambiarlo cuando quieras en Ajustes.",
uxPickCurrent: "En uso",
+ quickTitle: "Lo básico",
+ quickHint: "Lo que elegiste al empezar, a un toque.",
+ quick_identity: "Identidad",
+ quick_workflow: "Perfil",
+ quick_ux: "Portada",
+ quick_colors: "Colores",
+ quick_community: "Comunidad",
+ quick_greeting: "Saludo",
+ quickProfileMore: "Descripción y más",
+ quickJoinPub: "Unirme a un pub",
+ quickSeePeers: "Con quién sincronizas",
+ quickGreetingPlaceholder: "Saluda a la comunidad",
+ welcomeHintColors: "Fondo, texto y color de resalte de la app.",
+ colorsHint: "Toca una paleta y se aplica.",
+ colorsSkinNote: "Los colores van con el aspecto Acampada; al elegir uno se pasa a ese aspecto.",
+ colorsCustom: "A tu gusto",
+ colorsBg: "Fondo",
+ colorsFg: "Texto",
+ colorsAccent: "Resalte",
+ colorsApply: "Aplicar",
+ colorsFixed: "El texto no se leía sobre ese fondo, así que se ha puesto en negro o blanco.",
+ colorsKeep: "Dejar estos",
+ palette_acampada: "Acampada",
+ palette_noche: "Noche",
+ palette_oro: "Oro",
+ palette_bosque: "Bosque",
+ palette_lila: "Lila",
+ palette_brasa: "Brasa",
+ palette_papel: "Papel",
+ palette_contraste: "Alto contraste",
welcomeStepUseMobile: "Perfil",
welcomeHintUseMobile: "Para qué usas Oasis. Enciende los módulos que hacen falta.",
wfHint_activists: "Asambleas, tribus, votaciones, foros y documentos compartidos",
@@ -216,6 +276,36 @@ module.exports = {
bbMenuClose: "Fermer",
uxPickHint: "Touchez-en un et c'est enregistré. Vous pouvez le changer à tout moment dans les Paramètres.",
uxPickCurrent: "Actuel",
+ quickTitle: "L'essentiel",
+ quickHint: "Ce que vous avez choisi au début, à portée de doigt.",
+ quick_identity: "Identité",
+ quick_workflow: "Profil",
+ quick_ux: "Accueil",
+ quick_colors: "Couleurs",
+ quick_community: "Communauté",
+ quick_greeting: "Salut",
+ quickProfileMore: "Description et plus",
+ quickJoinPub: "Rejoindre un pub",
+ quickSeePeers: "Avec qui vous synchronisez",
+ quickGreetingPlaceholder: "Dites bonjour à la communauté",
+ welcomeHintColors: "Fond, texte et couleur d'accent de l'application.",
+ colorsHint: "Touchez une palette et elle s'applique.",
+ colorsSkinNote: "Les couleurs vont avec l'aspect Acampada ; en choisir une passe à cet aspect.",
+ colorsCustom: "À votre goût",
+ colorsBg: "Fond",
+ colorsFg: "Texte",
+ colorsAccent: "Accent",
+ colorsApply: "Appliquer",
+ colorsFixed: "Le texte n'était pas lisible sur ce fond, il a été mis en noir ou en blanc.",
+ colorsKeep: "Garder celles-ci",
+ palette_acampada: "Acampada",
+ palette_noche: "Nuit",
+ palette_oro: "Or",
+ palette_bosque: "Forêt",
+ palette_lila: "Lilas",
+ palette_brasa: "Braise",
+ palette_papel: "Papier",
+ palette_contraste: "Contraste élevé",
welcomeStepUseMobile: "Profil",
welcomeHintUseMobile: "À quoi vous sert Oasis. Active les modules nécessaires.",
wfHint_activists: "Assemblées, tribus, votes, forums et documents partagés",
@@ -310,6 +400,36 @@ module.exports = {
bbMenuClose: "Itxi",
uxPickHint: "Bat ukitu eta gordeta geratzen da. Nahi duzunean alda dezakezu Ezarpenetan.",
uxPickCurrent: "Erabiltzen",
+ quickTitle: "Oinarrizkoa",
+ quickHint: "Hasieran aukeratu zenuena, ukitu batera.",
+ quick_identity: "Nortasuna",
+ quick_workflow: "Profila",
+ quick_ux: "Azala",
+ quick_colors: "Koloreak",
+ quick_community: "Komunitatea",
+ quick_greeting: "Agurra",
+ quickProfileMore: "Deskribapena eta gehiago",
+ quickJoinPub: "Pub batean sartu",
+ quickSeePeers: "Norekin sinkronizatzen zaren",
+ quickGreetingPlaceholder: "Agurtu komunitatea",
+ welcomeHintColors: "Aplikazioaren hondoa, testua eta nabarmentze-kolorea.",
+ colorsHint: "Ukitu paleta bat eta aplikatzen da.",
+ colorsSkinNote: "Koloreak Acampada itxurarekin doaz; bat aukeratzean itxura horretara pasatzen da.",
+ colorsCustom: "Zure gustura",
+ colorsBg: "Hondoa",
+ colorsFg: "Testua",
+ colorsAccent: "Nabarmendua",
+ colorsApply: "Aplikatu",
+ colorsFixed: "Testua ez zen irakurtzen hondo horretan, beltzez edo zuriz jarri da.",
+ colorsKeep: "Hauek utzi",
+ palette_acampada: "Acampada",
+ palette_noche: "Gaua",
+ palette_oro: "Urrea",
+ palette_bosque: "Basoa",
+ palette_lila: "Lila",
+ palette_brasa: "Txingarra",
+ palette_papel: "Papera",
+ palette_contraste: "Kontraste handia",
welcomeStepUseMobile: "Profila",
welcomeHintUseMobile: "Oasis zertarako erabiltzen duzun. Behar diren moduluak pizten ditu.",
wfHint_activists: "Batzarrak, tribuak, bozketak, foroak eta dokumentu partekatuak",
@@ -404,6 +524,36 @@ module.exports = {
bbMenuClose: "Schließen",
uxPickHint: "Tippe eins an und es ist gespeichert. Du kannst es jederzeit in den Einstellungen ändern.",
uxPickCurrent: "Aktiv",
+ quickTitle: "Das Wichtigste",
+ quickHint: "Was du am Anfang gewählt hast, mit einem Tipp.",
+ quick_identity: "Identität",
+ quick_workflow: "Profil",
+ quick_ux: "Startseite",
+ quick_colors: "Farben",
+ quick_community: "Gemeinschaft",
+ quick_greeting: "Gruß",
+ quickProfileMore: "Beschreibung und mehr",
+ quickJoinPub: "Einem Pub beitreten",
+ quickSeePeers: "Mit wem du synchronisierst",
+ quickGreetingPlaceholder: "Sag der Gemeinschaft Hallo",
+ welcomeHintColors: "Hintergrund, Text und Akzentfarbe der App.",
+ colorsHint: "Tippe auf eine Palette und sie wird angewendet.",
+ colorsSkinNote: "Die Farben gehören zum Acampada-Design; mit einer Auswahl wechselst du dorthin.",
+ colorsCustom: "Eigene",
+ colorsBg: "Hintergrund",
+ colorsFg: "Text",
+ colorsAccent: "Akzent",
+ colorsApply: "Anwenden",
+ colorsFixed: "Der Text war auf diesem Hintergrund nicht lesbar, daher wurde er schwarz oder weiß gesetzt.",
+ colorsKeep: "Diese behalten",
+ palette_acampada: "Acampada",
+ palette_noche: "Nacht",
+ palette_oro: "Gold",
+ palette_bosque: "Wald",
+ palette_lila: "Flieder",
+ palette_brasa: "Glut",
+ palette_papel: "Papier",
+ palette_contraste: "Hoher Kontrast",
welcomeStepUseMobile: "Profil",
welcomeHintUseMobile: "Wofür du Oasis nutzt. Schaltet die nötigen Module ein.",
wfHint_activists: "Versammlungen, Stämme, Abstimmungen, Foren und gemeinsame Dokumente",
@@ -498,6 +648,36 @@ module.exports = {
bbMenuClose: "Chiudi",
uxPickHint: "Toccane uno ed è salvato. Puoi cambiarlo quando vuoi nelle Impostazioni.",
uxPickCurrent: "In uso",
+ quickTitle: "L'essenziale",
+ quickHint: "Quello che hai scelto all'inizio, a portata di tocco.",
+ quick_identity: "Identità",
+ quick_workflow: "Profilo",
+ quick_ux: "Pagina iniziale",
+ quick_colors: "Colori",
+ quick_community: "Comunità",
+ quick_greeting: "Saluto",
+ quickProfileMore: "Descrizione e altro",
+ quickJoinPub: "Entra in un pub",
+ quickSeePeers: "Con chi sincronizzi",
+ quickGreetingPlaceholder: "Saluta la comunità",
+ welcomeHintColors: "Sfondo, testo e colore di risalto dell'app.",
+ colorsHint: "Tocca una tavolozza e viene applicata.",
+ colorsSkinNote: "I colori vanno con l'aspetto Acampada; sceglierne uno passa a quell'aspetto.",
+ colorsCustom: "A modo tuo",
+ colorsBg: "Sfondo",
+ colorsFg: "Testo",
+ colorsAccent: "Risalto",
+ colorsApply: "Applica",
+ colorsFixed: "Il testo non si leggeva su quello sfondo, quindi è stato messo nero o bianco.",
+ colorsKeep: "Tieni questi",
+ palette_acampada: "Acampada",
+ palette_noche: "Notte",
+ palette_oro: "Oro",
+ palette_bosque: "Bosco",
+ palette_lila: "Lilla",
+ palette_brasa: "Brace",
+ palette_papel: "Carta",
+ palette_contraste: "Alto contrasto",
welcomeStepUseMobile: "Profilo",
welcomeHintUseMobile: "A cosa ti serve Oasis. Attiva i moduli necessari.",
wfHint_activists: "Assemblee, tribù, votazioni, forum e documenti condivisi",
@@ -592,6 +772,36 @@ module.exports = {
bbMenuClose: "Fechar",
uxPickHint: "Toque num e fica guardado. Pode mudá-lo quando quiser nas Definições.",
uxPickCurrent: "Em uso",
+ quickTitle: "O essencial",
+ quickHint: "O que escolheu ao começar, a um toque.",
+ quick_identity: "Identidade",
+ quick_workflow: "Perfil",
+ quick_ux: "Página inicial",
+ quick_colors: "Cores",
+ quick_community: "Comunidade",
+ quick_greeting: "Saudação",
+ quickProfileMore: "Descrição e mais",
+ quickJoinPub: "Entrar num pub",
+ quickSeePeers: "Com quem sincroniza",
+ quickGreetingPlaceholder: "Diga olá à comunidade",
+ welcomeHintColors: "Fundo, texto e cor de destaque da app.",
+ colorsHint: "Toque numa paleta e ela aplica-se.",
+ colorsSkinNote: "As cores vão com o aspeto Acampada; escolher uma passa para esse aspeto.",
+ colorsCustom: "À sua maneira",
+ colorsBg: "Fundo",
+ colorsFg: "Texto",
+ colorsAccent: "Destaque",
+ colorsApply: "Aplicar",
+ colorsFixed: "O texto não se lia nesse fundo, por isso ficou preto ou branco.",
+ colorsKeep: "Manter estas",
+ palette_acampada: "Acampada",
+ palette_noche: "Noite",
+ palette_oro: "Ouro",
+ palette_bosque: "Floresta",
+ palette_lila: "Lilás",
+ palette_brasa: "Brasa",
+ palette_papel: "Papel",
+ palette_contraste: "Alto contraste",
welcomeStepUseMobile: "Perfil",
welcomeHintUseMobile: "Para que usa o Oasis. Liga os módulos necessários.",
wfHint_activists: "Assembleias, tribos, votações, fóruns e documentos partilhados",
@@ -686,6 +896,36 @@ module.exports = {
bbMenuClose: "关闭",
uxPickHint: "点一下即保存。随时可以在设置中更改。",
uxPickCurrent: "使用中",
+ quickTitle: "基本设置",
+ quickHint: "开始时的选择,一点即达。",
+ quick_identity: "身份",
+ quick_workflow: "用途",
+ quick_ux: "首页",
+ quick_colors: "颜色",
+ quick_community: "社区",
+ quick_greeting: "问候",
+ quickProfileMore: "简介及更多",
+ quickJoinPub: "加入 pub",
+ quickSeePeers: "与谁同步",
+ quickGreetingPlaceholder: "向社区问好",
+ welcomeHintColors: "应用的背景、文字和强调色。",
+ colorsHint: "点一个配色即可应用。",
+ colorsSkinNote: "颜色配合 Acampada 外观;选择后会切换到该外观。",
+ colorsCustom: "自定义",
+ colorsBg: "背景",
+ colorsFg: "文字",
+ colorsAccent: "强调色",
+ colorsApply: "应用",
+ colorsFixed: "文字在该背景上看不清,已改为黑色或白色。",
+ colorsKeep: "保留这些",
+ palette_acampada: "Acampada",
+ palette_noche: "夜晚",
+ palette_oro: "金色",
+ palette_bosque: "森林",
+ palette_lila: "丁香",
+ palette_brasa: "余烬",
+ palette_papel: "纸张",
+ palette_contraste: "高对比度",
welcomeStepUseMobile: "用途",
welcomeHintUseMobile: "你用 Oasis 做什么。会打开需要的模块。",
wfHint_activists: "集会、部落、投票、论坛和共享文档",
@@ -780,6 +1020,36 @@ module.exports = {
bbMenuClose: "إغلاق",
uxPickHint: "المس واحدًا ويُحفظ. يمكنك تغييره في أي وقت من الإعدادات.",
uxPickCurrent: "قيد الاستخدام",
+ quickTitle: "الأساسيات",
+ quickHint: "ما اخترته في البداية، بلمسة واحدة.",
+ quick_identity: "الهوية",
+ quick_workflow: "الملف",
+ quick_ux: "الصفحة الرئيسية",
+ quick_colors: "الألوان",
+ quick_community: "المجتمع",
+ quick_greeting: "التحية",
+ quickProfileMore: "الوصف والمزيد",
+ quickJoinPub: "الانضمام إلى pub",
+ quickSeePeers: "مع من تتزامن",
+ quickGreetingPlaceholder: "قل مرحبًا للمجتمع",
+ welcomeHintColors: "لون الخلفية والنص والتمييز في التطبيق.",
+ colorsHint: "المس لوحة ألوان فتُطبَّق.",
+ colorsSkinNote: "الألوان تأتي مع مظهر Acampada؛ اختيار لون ينقلك إليه.",
+ colorsCustom: "على ذوقك",
+ colorsBg: "الخلفية",
+ colorsFg: "النص",
+ colorsAccent: "التمييز",
+ colorsApply: "تطبيق",
+ colorsFixed: "لم يكن النص مقروءًا على تلك الخلفية، فجُعل أسود أو أبيض.",
+ colorsKeep: "الإبقاء على هذه",
+ palette_acampada: "Acampada",
+ palette_noche: "ليل",
+ palette_oro: "ذهب",
+ palette_bosque: "غابة",
+ palette_lila: "ليلكي",
+ palette_brasa: "جمر",
+ palette_papel: "ورق",
+ palette_contraste: "تباين عالٍ",
welcomeStepUseMobile: "الملف",
welcomeHintUseMobile: "لماذا تستخدم Oasis. يفعّل الوحدات التي تحتاجها.",
wfHint_activists: "جمعيات وقبائل وتصويت ومنتديات ووثائق مشتركة",
@@ -874,6 +1144,36 @@ module.exports = {
bbMenuClose: "बंद करें",
uxPickHint: "एक पर टैप करें और वह सेव हो जाएगा। सेटिंग्स में इसे कभी भी बदल सकते हैं।",
uxPickCurrent: "चालू",
+ quickTitle: "मूल बातें",
+ quickHint: "शुरुआत में जो चुना था, एक टैप पर।",
+ quick_identity: "पहचान",
+ quick_workflow: "प्रोफ़ाइल",
+ quick_ux: "मुखपृष्ठ",
+ quick_colors: "रंग",
+ quick_community: "समुदाय",
+ quick_greeting: "अभिवादन",
+ quickProfileMore: "विवरण और अधिक",
+ quickJoinPub: "किसी pub से जुड़ें",
+ quickSeePeers: "आप किसके साथ सिंक करते हैं",
+ quickGreetingPlaceholder: "समुदाय को नमस्ते कहें",
+ welcomeHintColors: "ऐप का पृष्ठभूमि, टेक्स्ट और हाइलाइट रंग।",
+ colorsHint: "किसी पैलेट पर टैप करें और वह लागू हो जाएगा।",
+ colorsSkinNote: "रंग Acampada रूप के साथ चलते हैं; कोई चुनने पर वही रूप लग जाता है।",
+ colorsCustom: "अपनी पसंद",
+ colorsBg: "पृष्ठभूमि",
+ colorsFg: "टेक्स्ट",
+ colorsAccent: "हाइलाइट",
+ colorsApply: "लागू करें",
+ colorsFixed: "उस पृष्ठभूमि पर टेक्स्ट पढ़ा नहीं जा रहा था, इसलिए काला या सफ़ेद कर दिया गया।",
+ colorsKeep: "इन्हें रखें",
+ palette_acampada: "Acampada",
+ palette_noche: "रात",
+ palette_oro: "सोना",
+ palette_bosque: "जंगल",
+ palette_lila: "बकाइन",
+ palette_brasa: "अंगार",
+ palette_papel: "काग़ज़",
+ palette_contraste: "उच्च कंट्रास्ट",
welcomeStepUseMobile: "प्रोफ़ाइल",
welcomeHintUseMobile: "आप Oasis किस लिए इस्तेमाल करते हैं। ज़रूरी मॉड्यूल चालू करता है।",
wfHint_activists: "सभाएँ, ट्राइब, मतदान, फ़ोरम और साझा दस्तावेज़",
@@ -968,6 +1268,36 @@ module.exports = {
bbMenuClose: "Закрыть",
uxPickHint: "Нажмите на вариант, и он сохранится. Изменить можно в любой момент в Настройках.",
uxPickCurrent: "Сейчас",
+ quickTitle: "Основное",
+ quickHint: "То, что вы выбрали в начале, в одно касание.",
+ quick_identity: "Личность",
+ quick_workflow: "Профиль",
+ quick_ux: "Главный экран",
+ quick_colors: "Цвета",
+ quick_community: "Сообщество",
+ quick_greeting: "Приветствие",
+ quickProfileMore: "Описание и другое",
+ quickJoinPub: "Войти в pub",
+ quickSeePeers: "С кем вы синхронизируетесь",
+ quickGreetingPlaceholder: "Поздоровайтесь с сообществом",
+ welcomeHintColors: "Фон, текст и цвет выделения в приложении.",
+ colorsHint: "Нажмите на палитру, и она применится.",
+ colorsSkinNote: "Цвета идут с оформлением Acampada; при выборе включается оно.",
+ colorsCustom: "Свои",
+ colorsBg: "Фон",
+ colorsFg: "Текст",
+ colorsAccent: "Выделение",
+ colorsApply: "Применить",
+ colorsFixed: "Текст не читался на этом фоне, поэтому он стал чёрным или белым.",
+ colorsKeep: "Оставить эти",
+ palette_acampada: "Acampada",
+ palette_noche: "Ночь",
+ palette_oro: "Золото",
+ palette_bosque: "Лес",
+ palette_lila: "Сирень",
+ palette_brasa: "Угли",
+ palette_papel: "Бумага",
+ palette_contraste: "Высокий контраст",
welcomeStepUseMobile: "Профиль",
welcomeHintUseMobile: "Для чего вам Oasis. Включает нужные модули.",
wfHint_activists: "Собрания, племена, голосования, форумы и общие документы",
diff --git a/src/views/main_views.js b/src/views/main_views.js
index 8ee372e..d70acf4 100644
--- a/src/views/main_views.js
+++ b/src/views/main_views.js
@@ -2010,6 +2010,7 @@ const template = (titlePrefix, ...elements) => {
? [ link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}` }), acampadaBase(theme), themeLink, acampadaLink(theme) ]
: [ themeLink, link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}`, media: "(max-width: 768px)" }) ]),
process.env.OASIS_MOBILE === '1' ? link({ rel: "stylesheet", href: `/assets/styles/mobile_ux.css?v=${assetVersion()}` }) : null,
+ process.env.OASIS_MOBILE === '1' ? require('./mobile_ui').colorsLink() : null,
link({ rel: "icon", href: "/assets/images/favicon.svg" }),
meta({ charset: "utf-8" }),
meta({ name: "description", content: i18n.oasisDescription }),
@@ -2398,6 +2399,7 @@ exports.ainavHomeView = ({ recentTags = [] } = {}) => {
? [ link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}` }), acampadaBase(theme), link({ rel: "stylesheet", href: `/assets/themes/${theme}.css?v=${assetVersion()}` }), acampadaLink(theme) ]
: [ link({ rel: "stylesheet", href: `/assets/themes/${theme}.css?v=${assetVersion()}` }), link({ rel: "stylesheet", href: `/assets/styles/mobile.css?v=${assetVersion()}`, media: "(max-width: 768px)" }) ]),
process.env.OASIS_MOBILE === '1' ? link({ rel: "stylesheet", href: `/assets/styles/mobile_ux.css?v=${assetVersion()}` }) : null,
+ process.env.OASIS_MOBILE === '1' ? require('./mobile_ui').colorsLink() : null,
link({ rel: "icon", href: "/assets/images/favicon.svg" }),
meta({ charset: "utf-8" }),
meta({ name: "description", content: i18n.oasisDescription }),
diff --git a/src/views/mobile_icons.js b/src/views/mobile_icons.js
index ce067ac..20d44d7 100644
--- a/src/views/mobile_icons.js
+++ b/src/views/mobile_icons.js
@@ -84,6 +84,7 @@ const ICONS = {
data: '',
mic: '',
camera: '',
+ palette: '',
hangup: ''
};
diff --git a/src/views/mobile_settings.js b/src/views/mobile_settings.js
new file mode 100644
index 0000000..bd9f486
--- /dev/null
+++ b/src/views/mobile_settings.js
@@ -0,0 +1,267 @@
+"use strict";
+// Ajustes del movil: los seis accesos de arriba y lo que hay detras de cada uno.
+//
+// Lo que se elige en la bienvenida (identidad, perfil de uso, portada, comunidad y
+// saludo) tiene que poder cambiarse despues sin buscarlo. Arriba de Ajustes va una
+// rejilla con esos cinco y un sexto, Colores; cada acceso baja a su bloque, y cada
+// bloque guarda al tocar, igual que en la bienvenida.
+//
+// Los formularios de identidad y saludo envian a las rutas del dev (/welcome/profile,
+// /welcome/greeting) con un returnTo; fork_routes.js cambia su redireccion a
+// /welcome por la vuelta a Ajustes. Colores es propio: /settings/colors y la hoja
+// /mobile-colors.css, que va la ultima.
+//
+// Los colores son las variables --a26-* del tema Acampada; por eso al elegir colores
+// el aspecto pasa a Acampada (OasisMobile.css es del dev y lleva los suyos fijos).
+
+const crypto = require("crypto");
+const { a, button, details, div, form, img, input, label, p, section, span, summary, textarea, h2 } = require("../server/node_modules/hyperaxe");
+const { iconNode } = require("./mobile_icons");
+
+const t = () => require("./main_views").i18n;
+const cfgNow = () => { try { return require("../configs/config-manager.js").getConfig() || {}; } catch (_) { return {}; } };
+
+// ---------- colores ----------
+
+// [clave, fondo, texto, acento]; la primera es la de Acampada tal cual
+const PALETTES = [
+ ["acampada", "#ffffff", "#000000", "#00e5ff"],
+ ["noche", "#101114", "#f2f2f2", "#00e5ff"],
+ ["oro", "#121212", "#f2f2f2", "#ffd700"],
+ ["bosque", "#f3f7ef", "#13231a", "#3ccf7e"],
+ ["lila", "#faf7ff", "#1d1430", "#b48cff"],
+ ["brasa", "#fff8f2", "#1a0f0a", "#ff6a2b"],
+ ["papel", "#f5efe3", "#2a2420", "#ff4f8b"],
+ ["contraste", "#000000", "#ffffff", "#ffff00"]
+];
+const DEFAULT = PALETTES[0];
+
+const HEX = /^#[0-9a-f]{6}$/i;
+const rgb = (hex) => [1, 3, 5].map(i => parseInt(hex.slice(i, i + 2), 16));
+const toHex = (c) => "#" + c.map(v => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0")).join("");
+const mix = (x, y, k) => { const p = rgb(x), q = rgb(y); return toHex(p.map((v, i) => v + (q[i] - v) * k)); };
+const lum = (hex) => {
+ const [r, g, b] = rgb(hex).map(v => { v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); });
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
+};
+const contrast = (x, y) => { const p = lum(x), q = lum(y); return (Math.max(p, q) + 0.05) / (Math.min(p, q) + 0.05); };
+// negro o blanco, el que mas se lea encima
+const readableOn = (hex) => contrast(hex, "#000000") >= contrast(hex, "#ffffff") ? "#000000" : "#ffffff";
+
+// lo guardado, ya comprobado; sin nada guardado, Acampada
+const colorState = (cfg) => {
+ const c = (cfg || cfgNow()).mobileColors;
+ if (!c || typeof c !== "object" || ![c.bg, c.fg, c.accent].every(v => HEX.test(String(v || "")))) {
+ return { preset: DEFAULT[0], bg: DEFAULT[1], fg: DEFAULT[2], accent: DEFAULT[3], chosen: false, fixed: false };
+ }
+ const preset = c.preset === "custom" || PALETTES.some(x => x[0] === c.preset) ? c.preset : "custom";
+ return { preset, bg: c.bg.toLowerCase(), fg: c.fg.toLowerCase(), accent: c.accent.toLowerCase(), chosen: true, fixed: c.fixed === true };
+};
+
+// lo que llega del formulario -> lo que se guarda. Un texto que no se lee sobre el
+// fondo se cambia por negro o blanco y se avisa (fixed)
+const colorsFromBody = (body) => {
+ const pick = String(body.preset || "");
+ const pal = PALETTES.find(x => x[0] === pick);
+ if (pal) return { preset: pal[0], bg: pal[1], fg: pal[2], accent: pal[3] };
+ const now = colorState();
+ const bg = HEX.test(String(body.bg || "")) ? body.bg.toLowerCase() : now.bg;
+ let fg = HEX.test(String(body.fg || "")) ? body.fg.toLowerCase() : now.fg;
+ const accent = HEX.test(String(body.accent || "")) ? body.accent.toLowerCase() : now.accent;
+ let fixed = false;
+ if (contrast(bg, fg) < 4.5) { fg = readableOn(bg); fixed = true; }
+ return { preset: "custom", bg, fg, accent, fixed };
+};
+
+const PREVIEW_CSS = PALETTES.map(([k, bg, fg, ac]) =>
+ `.pal-${k} .pal-bg{background:${bg}!important}.pal-${k} .pal-fg{background:${fg}!important}.pal-${k} .pal-ac{background:${ac}!important}`).join("\n");
+
+// la hoja que va la ultima: las variables del tema con los colores elegidos y las
+// muestras de las paletas (la CSP no deja estilos en linea)
+const colorsCss = (cfg) => {
+ const c = colorState(cfg);
+ // solo con Acampada: OasisMobile.css es del dev y lleva su paleta fija
+ const onAcampada = ((cfg || cfgNow()).themes || {}).current === "Acampada";
+ const vars = c.chosen && onAcampada ? [
+ `--a26-bg:${c.bg}`, `--a26-fg:${c.fg}`, `--a26-neon:${c.accent}`,
+ `--a26-on-neon:${readableOn(c.accent)}`,
+ `--a26-soft:${mix(c.bg, c.fg, 0.06)}`,
+ `--a26-muted:${mix(c.fg, c.bg, 0.35)}`,
+ `--a26-tint:${mix(c.bg, c.accent, 0.2)}`,
+ `--a26-line:${mix(c.bg, c.fg, 0.25)}`,
+ `color-scheme:${lum(c.bg) < 0.2 ? "dark" : "light"}`
+ ] : [];
+ const custom = `.pal-custom .pal-bg{background:${c.bg}!important}.pal-custom .pal-fg{background:${c.fg}!important}.pal-custom .pal-ac{background:${c.accent}!important}`;
+ return (vars.length ? `:root{${vars.join(";")}}\n` : "") + PREVIEW_CSS + "\n" + custom + "\n";
+};
+const colorsVersion = (cfg) => crypto.createHash("sha1").update(colorsCss(cfg)).digest("hex").slice(0, 10);
+const colorsLink = () => require("../server/node_modules/hyperaxe").link({ rel: "stylesheet", href: `/mobile-colors.css?v=${colorsVersion()}` });
+
+const swatch = (key) => span({ class: `pal-swatch pal-${key}`, "aria-hidden": "true" },
+ span({ class: "pal-bg" }), span({ class: "pal-fg" }), span({ class: "pal-ac" }));
+
+// el selector de colores, el mismo en Ajustes y en la bienvenida
+const colorsPick = (returnTo, withKeep) => {
+ const i18n = t();
+ const c = colorState();
+ const cur = c.chosen ? c.preset : DEFAULT[0];
+ const theme = (cfgNow().themes || {}).current;
+ return div({ class: "color-pick" },
+ form({ method: "POST", action: "/settings/colors", class: "ux-pick" },
+ input({ type: "hidden", name: "returnTo", value: returnTo }),
+ p({ class: "ux-pick-hint" }, i18n.colorsHint || "Tap a palette and it is applied."),
+ theme && theme !== "Acampada" ? p({ class: "form-hint" }, i18n.colorsSkinNote || "Colours go with the Acampada look; choosing one switches to it.") : null,
+ div({ class: "pal-grid" },
+ ...PALETTES.map(([k]) => button(Object.assign({ type: "submit", name: "preset", value: k, class: k === cur ? "pal-card is-current" : "pal-card" }, k === cur ? { "aria-current": "true" } : {}),
+ swatch(k),
+ span({ class: "pal-name" }, i18n[`palette_${k}`] || k),
+ k === cur ? span({ class: "ux-pick-now" }, i18n.uxPickCurrent || "In use") : null
+ ))
+ ),
+ withKeep && !c.chosen ? div({ class: "welcome-action" }, button({ type: "submit", name: "preset", value: DEFAULT[0], class: "welcome-skip-btn" }, i18n.colorsKeep || "Keep these")) : null
+ ),
+ details(Object.assign({ class: "color-custom" }, cur === "custom" ? { open: true } : {}),
+ summary(swatch("custom"), span(i18n.colorsCustom || "Your own")),
+ form({ method: "POST", action: "/settings/colors", class: "color-custom-form" },
+ input({ type: "hidden", name: "returnTo", value: returnTo }),
+ input({ type: "hidden", name: "preset", value: "custom" }),
+ label({ class: "color-field" }, span(i18n.colorsBg || "Background"), input({ type: "color", name: "bg", value: c.bg })),
+ label({ class: "color-field" }, span(i18n.colorsFg || "Text"), input({ type: "color", name: "fg", value: c.fg })),
+ label({ class: "color-field" }, span(i18n.colorsAccent || "Accent"), input({ type: "color", name: "accent", value: c.accent })),
+ c.fixed ? p({ class: "form-hint" }, i18n.colorsFixed || "The text could not be read on that background, so it was set to black or white.") : null,
+ button({ type: "submit", class: "create-button" }, i18n.colorsApply || "Apply")
+ )
+ )
+ );
+};
+
+// ---------- los seis accesos ----------
+
+const QUICK = [
+ { id: "identity", icon: "profile", key: "quick_identity" },
+ { id: "workflows", icon: "modules", key: "quick_workflow" },
+ { id: "ux", icon: "activity", key: "quick_ux" },
+ { id: "colors", icon: "palette", key: "quick_colors" },
+ { id: "community", icon: "invites", key: "quick_community" },
+ { id: "greeting", icon: "publish", key: "quick_greeting" }
+];
+
+// la linea de debajo de cada acceso: lo que hay puesto ahora
+const quickValue = (id, cfg) => {
+ const i18n = t();
+ if (id === "identity") return global.__OASIS_SELF_NAME__ || "";
+ if (id === "workflows") {
+ const wf = (() => { try { return require("../models/workflows_model").currentWorkflow(cfg); } catch (_) { return null; } })();
+ return wf ? (i18n[`workflow_${wf}`] || wf) : "";
+ }
+ if (id === "ux") {
+ const ux = (cfg.ux || {}).current || "blocks";
+ return { blocks: i18n.uxModeMenus, ainav: i18n.uxModeAINav, chats: i18n.uxModeChats, feed: i18n.uxModeFeed, phone: i18n.phoneTitle, rooms: i18n.roomsTitle }[ux] || "";
+ }
+ if (id === "colors") { const c = colorState(cfg); return c.preset === "custom" ? (i18n.colorsCustom || "") : (i18n[`palette_${c.preset}`] || ""); }
+ return "";
+};
+
+const quickGrid = (cfg, present) => {
+ const i18n = t();
+ return section({ id: "quick", class: "quick-settings" },
+ div({ class: "tags-header" }, h2(i18n.quickTitle || "The basics"), p(i18n.quickHint || "What you chose when you started, one tap away.")),
+ div({ class: "quick-grid" },
+ ...QUICK.filter(q => present.includes(q.id)).map(q => a({ href: `#${q.id}`, class: "quick-tile" },
+ iconNode("quick-ico", q.icon),
+ span({ class: "quick-name" }, i18n[q.key] || q.id),
+ span({ class: "quick-now" }, quickValue(q.id, cfg))
+ ))
+ )
+ );
+};
+
+// ---------- bloques nuevos de Ajustes ----------
+
+const RETURN = (id) => `/settings#${id}`;
+
+const identitySection = () => {
+ const i18n = t();
+ const avatar = global.__OASIS_SELF_AVATAR__;
+ return section({ id: "identity" },
+ div({ class: "tags-header" }, h2(i18n.quick_identity || i18n.welcomeStepIdentity), p(i18n.welcomeHintIdentity)),
+ form({ method: "POST", action: "/welcome/profile", enctype: "multipart/form-data", class: "welcome-profile-form" },
+ input({ type: "hidden", name: "returnTo", value: RETURN("identity") }),
+ label({ for: "set-name" }, i18n.profileName),
+ input({ type: "text", id: "set-name", name: "name", maxlength: "80", value: global.__OASIS_SELF_NAME__ || "" }),
+ label({ for: "set-image" }, i18n.profileImage),
+ avatar ? img({ class: "welcome-profile-avatar", src: avatar, alt: "" }) : null,
+ input({ type: "file", id: "set-image", name: "image", accept: "image/*" }),
+ button({ type: "submit" }, i18n.welcomeStepProfileAction)
+ ),
+ a({ href: "/profile/edit", class: "filter-btn" }, i18n.quickProfileMore || "Description and more")
+ );
+};
+
+const colorsSection = () => {
+ const i18n = t();
+ return section({ id: "colors" },
+ div({ class: "tags-header" }, h2(i18n.quick_colors || "Colours")),
+ colorsPick(RETURN("colors"), false)
+ );
+};
+
+const communitySection = () => {
+ const i18n = t();
+ return section({ id: "community" },
+ div({ class: "tags-header" }, h2(i18n.quick_community || i18n.welcomeStepCommunity), p(i18n.welcomeHintCommunity)),
+ div({ class: "quick-links" },
+ a({ href: "/invites", class: "create-button" }, i18n.quickJoinPub || i18n.invites),
+ a({ href: "/peers", class: "filter-btn" }, i18n.quickSeePeers || i18n.peers)
+ )
+ );
+};
+
+const greetingSection = () => {
+ const i18n = t();
+ return section({ id: "greeting" },
+ div({ class: "tags-header" }, h2(i18n.quick_greeting || i18n.welcomeStepIntro), p(i18n.welcomeHintIntro)),
+ form({ method: "POST", action: "/welcome/greeting", class: "welcome-greeting-form" },
+ input({ type: "hidden", name: "returnTo", value: RETURN("greeting") }),
+ textarea({ name: "text", required: true, minlength: "1", maxlength: "280", rows: 3, placeholder: i18n.quickGreetingPlaceholder || "" }),
+ button({ type: "submit" }, i18n.welcomeStepGreetingAction)
+ )
+ );
+};
+
+// recibe las secciones de settings_view (pestaña Oasis) y devuelve la lista del movil:
+// la rejilla arriba, los bloques nuevos metidos en su sitio y el orden de los accesos
+const ORDER = ["quick", "update", "language", "identity", "workflows", "ux", "colors", "theme", "community", "greeting", "home-page"];
+const withQuick = (parts) => {
+ const cfg = cfgNow();
+ const mods = cfg.modules || {};
+ const extra = [
+ identitySection(),
+ colorsSection(),
+ mods.invitesMod !== "off" ? communitySection() : null,
+ mods.feedMod !== "off" ? greetingSection() : null
+ ].filter(Boolean);
+ const list = parts.filter(Boolean).concat(extra);
+ const ids = list.map(el => el && el.id);
+ list.push(quickGrid(cfg, ids));
+ const rank = (el) => { const i = ORDER.indexOf(el && el.id); return i === -1 ? ORDER.length : i; };
+ return list.map((el, i) => ({ el, i })).sort((x, y) => rank(x.el) - rank(y.el) || x.i - y.i).map(x => x.el);
+};
+
+// ---------- bienvenida ----------
+
+// Colores entra como paso, despues del tipo de portada. No es un paso del dev, asi
+// que el "hecho" sale de si ya hay colores guardados
+const welcomeWithColors = (usable) => {
+ if (usable.includes("colors")) return usable;
+ const out = usable.slice();
+ const at = out.indexOf("ux");
+ out.splice(at === -1 ? out.length : at + 1, 0, "colors");
+ return out;
+};
+const welcomeDone = (steps) => Object.assign({}, steps, { colors: colorState().chosen });
+
+module.exports = {
+ PALETTES, colorState, colorsFromBody, colorsCss, colorsLink, colorsPick,
+ withQuick, welcomeWithColors, welcomeDone, contrast
+};
diff --git a/src/views/mobile_ui.js b/src/views/mobile_ui.js
index c3448e3..777669d 100644
--- a/src/views/mobile_ui.js
+++ b/src/views/mobile_ui.js
@@ -107,14 +107,9 @@ const tribeTabs = (tribe, section, sections) => {
// ---------- ajustes ----------
-// primero lo que se toca (idioma, aspecto, tipo de uso, portada) y despues lo tecnico;
-// se reordena por id de seccion, sin tocar las secciones
-const SETTINGS_FIRST = ['update', 'language', 'ux', 'theme', 'workflows', 'home-page'];
-const settingsOrder = (parts) => {
- const list = parts.filter(Boolean);
- const rank = (el) => { const i = SETTINGS_FIRST.indexOf(el && el.id); return i === -1 ? SETTINGS_FIRST.length : i; };
- return list.map((el, i) => ({ el, i })).sort((x, y) => rank(x.el) - rank(y.el) || x.i - y.i).map(x => x.el);
-};
+// primero los seis accesos y lo que se toca (idioma, identidad, uso, portada,
+// colores, aspecto) y despues lo tecnico; ver mobile_settings.js
+const settingsOrder = (parts) => require('./mobile_settings').withQuick(parts);
// cada idioma un boton que se aplica al tocarlo (antes desplegable y otro boton)
const LANGS = [['Español', 'es'], ['Català', 'ca'], ['Euskara', 'eu'], ['Galego', 'gl'], ['English', 'en'], ['Français', 'fr'],
@@ -179,12 +174,14 @@ const workflowPick = (action, current, keys, withHint) => {
// pasos de la bienvenida en el movil: el backend del dev quita el de uso en el movil;
// aqui vuelve, el segundo, justo despues de la identidad (/welcome/workflow es suyo)
+// y Colores despues del tipo de portada (mobile_settings.js)
const welcomeSteps = (usable) => {
- if (usable.includes('workflow')) return usable;
const out = usable.slice();
- const at = out.indexOf('profile');
- out.splice(at === -1 ? 0 : at + 1, 0, 'workflow');
- return out;
+ if (!out.includes('workflow')) {
+ const at = out.indexOf('profile');
+ out.splice(at === -1 ? 0 : at + 1, 0, 'workflow');
+ }
+ return require('./mobile_settings').welcomeWithColors(out);
};
// ---------- acciones de tarjeta ----------
@@ -228,7 +225,14 @@ const welcomeBannerHere = (here) => ['', '/', '/activity'].includes(here);
const welcomeBannerText = () => t().welcomeBannerTextMobile || t().welcomeBannerText;
const welcomeBannerAction = () => t().welcomeBannerActionMobile || t().welcomeBannerAction;
+// colores y Ajustes: viven en mobile_settings.js; aqui se reexportan para que los
+// enganches de las vistas del dev pasen todos por mobile_ui
+const colorsLink = () => require('./mobile_settings').colorsLink();
+const colorsPick = (returnTo, withKeep) => require('./mobile_settings').colorsPick(returnTo, withKeep);
+const welcomeDone = (steps) => require('./mobile_settings').welcomeDone(steps);
+
module.exports = {
+ colorsLink, colorsPick, welcomeDone,
isMobile, VOTE_KINDS, voteKindPicker, voteBars, voteOwnNote, tribeRow, tribeTabs,
settingsOrder, languagePick, uxPick, workflowPick, welcomeSteps, deleteConfirm, footerLine,
welcomeBannerHere, welcomeBannerText, welcomeBannerAction
diff --git a/src/views/welcome_view.js b/src/views/welcome_view.js
index 7b39341..808fa10 100644
--- a/src/views/welcome_view.js
+++ b/src/views/welcome_view.js
@@ -18,6 +18,7 @@ const SECTIONS = {
greeting: ["welcomeStepIntro", "welcomeHintIntro"]
}
if (mobileUi.isMobile()) SECTIONS.workflow = ["welcomeStepUseMobile", "welcomeHintUseMobile"]
+if (mobileUi.isMobile()) SECTIONS.colors = ["quick_colors", "welcomeHintColors"]
const submitRow = (text) => div({ class: "welcome-action" }, button({ type: "submit" }, text))
@@ -63,6 +64,7 @@ const stepAction = (key, profile, status) => {
submitRow(i18n.welcomeStepWorkflowAction)
)
}
+ if (key === "colors") return mobileUi.colorsPick("/welcome#step-colors", true)
if (key === "theme") return form({ method: "POST", action: "/welcome/theme", class: "welcome-ux-form" },
div({ class: "welcome-ux-grid" },
WIZARD_THEMES.map(([value, name]) => pickCard("theme", value, status.theme, name, `/assets/images/theme-${value.toLowerCase()}.png`))
@@ -105,7 +107,7 @@ const stepAction = (key, profile, status) => {
}
exports.welcomeView = async (status, profile = {}) => {
- const steps = status.steps || {}
+ const steps = mobileUi.isMobile() ? mobileUi.welcomeDone(status.steps || {}) : (status.steps || {})
const usable = mobileUi.isMobile() ? mobileUi.welcomeSteps(status.usable || []) : (status.usable || [])
const opened = usable.find(key => !steps[key])
const html = template(