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

@ -4,6 +4,7 @@ const { MAX_IMAGES } = require("../models/media_gallery")
const safeArr = (v) => (Array.isArray(v) ? v : [])
const safeText = (v) => String(v == null ? "" : v).trim()
const downloadHref = (src) => `${src}${String(src).includes("?") ? "&" : "?"}download=1`
const blobIdOf = (entry) => {
const value = safeText(entry)
@ -26,20 +27,21 @@ const imagesOf = (item) => {
const videoOf = (item) => safeText(item && item.video)
const renderMediaThumb = (entry, alt = "") => isVideoEntry(entry)
? video({ controls: true, class: "gallery-image", src: blobUrl(blobIdOf(entry)) })
? video({ controls: true, class: "gallery-image", src: blobUrl(blobIdOf(entry)), preload: 'metadata' })
: img({ src: blobUrl(blobIdOf(entry), 256), class: "gallery-image", alt })
const lightboxId = (scope, itemId, index) => `${scope}-photo-${encodeURIComponent(itemId)}-${index}`
let zoomSeq = 0
const renderZoomableImage = (src, { alt = "", imgClass = "" } = {}) => {
const renderZoomableImage = (src, { alt = "", imgClass = "", linkClass = "" } = {}) => {
if (!src) return null
zoomSeq = (zoomSeq + 1) % 1000000
const id = `zoom-${zoomSeq}-${String(src).replace(/[^a-zA-Z0-9]/g, "").slice(-10)}`
return [
a({ href: `#${id}`, class: "zoom-link" }, img({ src, class: imgClass, alt })),
a({ href: `#${id}`, id: `${id}-src`, class: linkClass ? `zoom-link ${linkClass}` : "zoom-link" }, img({ src, class: imgClass, alt })),
div({ id, class: "lightbox" },
a({ href: "#", class: "lightbox-close" }, "×"),
a({ href: downloadHref(src), class: "lightbox-download", title: i18n.fileShareDownload || "Download" }, "⤓"),
a({ href: `#${id}-src`, class: "lightbox-close" }, "×"),
img({ src, class: "lightbox-image", alt })
)
]
@ -56,19 +58,20 @@ const renderPhotoGallery = (item, scope = "media") => {
list.map((entry, i) =>
isVideoEntry(entry)
? span({ class: "gallery-item" }, renderMediaThumb(entry))
: a({ href: `#${lightboxId(scope, id, i)}`, class: "gallery-item" }, renderMediaThumb(entry))
: a({ href: `#${lightboxId(scope, id, i)}`, id: `${lightboxId(scope, id, i)}-src`, class: "gallery-item" }, renderMediaThumb(entry))
)
)
: null,
list.filter(entry => !isVideoEntry(entry)).map((entry) =>
div({ id: lightboxId(scope, id, list.indexOf(entry)), class: "lightbox" },
a({ href: "#", class: "lightbox-close" }, "×"),
a({ href: downloadHref(blobUrl(blobIdOf(entry))), class: "lightbox-download", title: i18n.fileShareDownload || "Download" }, "⤓"),
a({ href: `#${lightboxId(scope, id, list.indexOf(entry))}-src`, class: "lightbox-close" }, "×"),
img({ src: blobUrl(blobIdOf(entry)), class: "lightbox-image", alt: "" })
)
),
clip
? div({ class: "media-video media-video-centered" },
video({ controls: true, class: "media-video-player", src: blobUrl(blobIdOf(clip)) })
video({ controls: true, class: "media-video-player", src: blobUrl(blobIdOf(clip)), preload: 'metadata' })
)
: null
)