diff --git a/.githooks/README.md b/.githooks/README.md index ad26b93..88e577b 100644 --- a/.githooks/README.md +++ b/.githooks/README.md @@ -1,28 +1,28 @@ -# .githooks — enforcement de versionado (Atenea) +# .githooks — versioning enforcement -Hooks git-native (POSIX, sin dependencias). Corren para **cualquier** commit, hecho por una -persona o por un agente. Alinean el manual de versionado del equipo. +Git-native hooks (POSIX, no dependencies). They run for **every** commit, whether made by a +person or by an agent, and enforce the team's versioning rules. -- **`commit-msg`** — bloquea el commit si el mensaje no sigue Conventional Commits - (`()(!): descripción`). Avisa (no bloquea) si el tipo no cuadra con el prefijo - de la rama. -- **`pre-commit`** — bloquea commits directos sobre `main` o `beta` (el desarrollo va en ramas - temporales). Escape para promoción de release: `ATENEA_ALLOW_PROTECTED=1 git commit ...`. +- **`commit-msg`** — rejects the commit if the message is not a Conventional Commit + (`()(!): description`). Warns (does not block) when the type does not match the + branch prefix. +- **`pre-commit`** — blocks direct commits on `main` or `beta` (development happens on + temporary branches). Escape for release promotion: `ATENEA_ALLOW_PROTECTED=1 git commit ...`. -## Activación +## Activation -`core.hooksPath` es config local por clon (no se versiona), así que cada clon la activa una vez. -El proyecto lo automatiza con un script `prepare` en `package.json`: +`core.hooksPath` is a per-clone local setting (it is not versioned), so each clone enables it +once. The project automates it with a `prepare` script in `package.json`: ```json "scripts": { "prepare": "git config core.hooksPath .githooks || true" } ``` -Así, en cada `yarn install` queda cableado. Manual, si hiciera falta: +That way every `npm install` wires it up. Manually, if needed: ```bash git config core.hooksPath .githooks chmod +x .githooks/* ``` -Bypass puntual de un hook (no recomendado): `git commit --no-verify`. +One-off bypass of a hook (not recommended): `git commit --no-verify`. diff --git a/.githooks/commit-msg b/.githooks/commit-msg index 7c28129..f72837a 100755 --- a/.githooks/commit-msg +++ b/.githooks/commit-msg @@ -1,21 +1,21 @@ #!/bin/sh # ───────────────────────────────────────────────────────────────────────── -# Atenea · commit-msg · valida Conventional Commits (manual de versionado Venteks) +# Atenea · commit-msg · validates Conventional Commits (Venteks versioning manual) # -# Git-native: corre para CUALQUIER commit (humano o agente), esté o no Claude. -# Bloquea el commit si el mensaje no cumple. Escape puntual: git commit --no-verify. +# Git-native: runs for ANY commit (human or agent), with or without Claude. +# Blocks the commit if the message does not comply. One-off escape: git commit --no-verify. # -# Formato exigido: ()(!): -# tipos: feat fix docs style refactor perf test build ci chore revert -# ejemplos: feat(auth): agregar login por sesión -# fix: corregir cálculo de permisos -# feat(store)!: cambiar shape del estado (BREAKING CHANGE) +# Required format: ()(!): +# types: feat fix docs style refactor perf test build ci chore revert +# examples: feat(auth): add session login +# fix: correct permission calculation +# feat(store)!: change the state shape (BREAKING CHANGE) # ───────────────────────────────────────────────────────────────────────── msg_file="$1" first_line="$(head -n 1 "$msg_file")" -# Saltar commits de merge/revert automáticos y fixup/squash. +# Skip automatic merge/revert commits and fixup/squash. case "$first_line" in "Merge "*|"Revert "*|"fixup!"*|"squash!"*) exit 0 ;; esac @@ -23,31 +23,31 @@ esac pattern='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9._/-]+\))?(!)?: .+' if printf '%s' "$first_line" | grep -Eq "$pattern"; then - # Aviso suave: coherencia tipo ↔ prefijo de rama (no bloquea). - # Una rama feat/* es un contenedor de feature: acumula fix/docs/refactor/chore/ - # test intermedios de forma legítima, así que NO se avisa sobre ella. Solo se - # revisan fix/* y hotfix/*, que son de propósito único. + # Soft warning: type ↔ branch prefix consistency (does not block). + # A feat/* branch is a feature container: it legitimately accumulates intermediate + # fix/docs/refactor/chore/test commits, so it is NOT warned about. Only fix/* and + # hotfix/* are checked, since they have a single purpose. branch="$(git symbolic-ref --short HEAD 2>/dev/null || echo '')" type="$(printf '%s' "$first_line" | sed -E 's/^([a-z]+).*/\1/')" case "$branch" in - fix/*) [ "$type" = "fix" ] || printf '⚠️ Atenea: rama fix/* pero commit "%s" (¿debería ser fix?)\n' "$type" >&2 ;; - hotfix/*) [ "$type" = "fix" ] || printf '⚠️ Atenea: rama hotfix/* pero commit "%s" (¿debería ser fix?)\n' "$type" >&2 ;; + fix/*) [ "$type" = "fix" ] || printf '⚠️ Atenea: fix/* branch but commit "%s" (should it be fix?)\n' "$type" >&2 ;; + hotfix/*) [ "$type" = "fix" ] || printf '⚠️ Atenea: hotfix/* branch but commit "%s" (should it be fix?)\n' "$type" >&2 ;; esac exit 0 fi cat >&2 <<'EOF' -✕ Atenea · commit rechazado: el mensaje no sigue Conventional Commits. +✕ Atenea · commit rejected: the message does not follow Conventional Commits. - Formato: ()(!): - Tipos: feat fix docs style refactor perf test build ci chore revert + Format: ()(!): + Types: feat fix docs style refactor perf test build ci chore revert - Ejemplos válidos: - feat(auth): agregar login por sesión - fix: corregir cálculo de permisos - feat(store)!: cambiar shape del estado + Valid examples: + feat(auth): add session login + fix: correct permission calculation + feat(store)!: change the state shape - Manual de versionado del equipo (plugin Atenea, skill git-flow). - Bypass puntual (no recomendado): git commit --no-verify + Team versioning manual (Atenea plugin, git-flow skill). + One-off bypass (not recommended): git commit --no-verify EOF exit 1 diff --git a/.githooks/pre-commit b/.githooks/pre-commit index b0b0088..a8d1f34 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -1,13 +1,13 @@ #!/bin/sh # ───────────────────────────────────────────────────────────────────────── -# Atenea · pre-commit · protege ramas permanentes (manual de versionado Venteks) +# Atenea · pre-commit · protects the permanent branches (Venteks versioning manual) # -# Regla del md: "nunca desarrolles directo sobre main". El desarrollo va en ramas -# temporales (feat/ fix/ chore/) desde beta; hotfix/ desde main. Este hook -# bloquea commits DIRECTOS sobre main o beta. +# Rule: "never develop directly on main". Development happens on temporary branches +# (feat/ fix/ chore/ docs/) created from beta; hotfix/ from main. This hook blocks +# DIRECT commits on main or beta. # -# Escape para promoción/merge de release: ATENEA_ALLOW_PROTECTED=1 git commit ... -# Bypass total puntual: git commit --no-verify +# Escape for release promotion/merge: ATENEA_ALLOW_PROTECTED=1 git commit ... +# One-off full bypass: git commit --no-verify # ───────────────────────────────────────────────────────────────────────── [ "${ATENEA_ALLOW_PROTECTED:-}" = "1" ] && exit 0 @@ -17,17 +17,17 @@ branch="$(git symbolic-ref --short HEAD 2>/dev/null || echo '')" case "$branch" in main|beta) cat >&2 < · fix/ · chore/ (desde beta) - hotfix/ (desde main) + Development happens on temporary branches: + feat/ · fix/ · chore/ · docs/ (from beta) + hotfix/ (from main) - Crea la rama y muévete a ella: - git switch -c feat/mi-cambio + Create the branch and switch to it: + git switch -c feat/my-change - Promoción de release (merge a $branch): ATENEA_ALLOW_PROTECTED=1 git commit ... - Bypass puntual (no recomendado): git commit --no-verify + Release promotion (merge into $branch): ATENEA_ALLOW_PROTECTED=1 git commit ... + One-off bypass (not recommended): git commit --no-verify EOF exit 1 ;; diff --git a/CHANGELOG.md b/CHANGELOG.md index a14e95e..790f534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,64 +1,69 @@ # Changelog -Todas las notas relevantes de este proyecto se documentan aquí. -Formato basado en [Keep a Changelog](https://keepachangelog.com/es-ES/); versionado [SemVer](https://semver.org/lang/es/). +All notable changes to this project are documented in this file. +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/). -## [Sin liberar] +## [Unreleased] -### Añadido +### Added -- CI en GitHub Actions para cada PR y push a `beta` y `main`: typecheck, lint, formato y tests. -- Hooks de git (`.githooks/`) que exigen Conventional Commits y bloquean commits directos sobre `main` y `beta`, y guía de contribución con el flujo de ramas. +- Screen reader support (VoiceOver and TalkBack): an invisible adjustable control announces the current page ("Page 3 of 12", or "Pages 3 and 4 of 12" in book mode) and turns pages with the increment/decrement actions. Headings are exposed with the `header` role. +- `accessibilityLabels` prop on `DocumentViewer` to override the spoken strings, plus the `ViewerAccessibilityLabels` type and the `DEFAULT_ACCESSIBILITY_LABELS` export. +- GitHub Actions CI for every PR and push to `beta` and `main`: typecheck, lint, formatting and tests. +- Git hooks (`.githooks/`) that require Conventional Commits and block direct commits on `main` and `beta`, and a contributing guide with the branching model. -### Cambiado +### Changed -- El repositorio se mudó de GitLab a GitHub (`acertijosapps/react-native-md-document`): `repository` y `bugs` de `package.json` y `source` del podspec, que ahora usa el tag `vX.Y.Z`. +- Peer dependency ranges now match what the library supports: `react-native` ≥ 0.76, `react` ≥ 18.3.1, `react-native-reanimated` ≥ 3.16 and < 4, `react-native-gesture-handler` ≥ 2.20 and < 3. Reanimated 4 and Gesture Handler 3 are not supported yet. +- The repository moved from GitLab to GitHub (`acertijosapps/react-native-md-document`): `repository` and `bugs` in `package.json` and `source` in the podspec, which now uses the `vX.Y.Z` tag. +- The public documentation (README, changelog, contributing guide) is now in English. -### Corregido +### Fixed -### Eliminado +- The pages stacked behind the current one for the flip animation are hidden from the accessibility tree, so screen readers no longer read them. +- The error thrown when the native speech module is not linked now names the published package (`@acertijosapps/react-native-md-document`) instead of the old `react-native-document`, and is in English. ## [1.2.0] - 2026-10-25 -### Añadido +### Added -- Modo libro: en tablets y plegables en horizontal, el documento se muestra en dos páginas enfrentadas con volteo de página. Props `dualPage` (activo por defecto), `dualPageMinWidth` y `spreadGap` (separación para la bisagra de un plegable). -- `onPageChange` recibe un cuarto argumento, `pagesPerView` (1 o 2). -- Secciones «Modo libro» y «Plegables» en el README, con capturas. +- Book mode: on tablets and foldables in landscape, the document is shown as two facing pages with page-turning. Props `dualPage` (on by default), `dualPageMinWidth` and `spreadGap` (space for a foldable's hinge). +- `onPageChange` receives a fourth argument, `pagesPerView` (1 or 2). +- "Book mode" and "Foldables" sections in the README, with screenshots. -### Corregido +### Fixed -- Paginación en tablets: ya no queda atascada ni deja páginas en blanco por eventos de layout tardíos. -- El modo libro solo se activa en pantallas de tablet o plegable (lado corto de la ventana de al menos 600 dp); un teléfono en horizontal sigue mostrando una página. +- Pagination on tablets: it no longer gets stuck or leaves blank pages because of late layout events. +- Book mode only turns on for tablet- or foldable-sized screens (window short side of at least 600 dp); a phone in landscape still shows a single page. ## [1.1.2] - 2026-10-13 -### Cambiado +### Changed -- `author`, correo y `homepage` de AcertijosApps en `package.json` y el podspec. +- `author`, email and `homepage` of AcertijosApps in `package.json` and the podspec. ## [1.1.1] - 2026-09-19 -### Añadido +### Added -- `homepage`, `repository` y `bugs` en `package.json`, y podspec alineado. +- `homepage`, `repository` and `bugs` in `package.json`, and an aligned podspec. ## [1.1.0] - 2026-09-19 -### Añadido +### Added -- Carga del documento por `source` con estados de carga y error (`onLoad*`, `renderLoading`, `renderError`). +- Loading the document through `source`, with loading and error states (`onLoad*`, `renderLoading`, `renderError`). ## [1.0.1] - 2026-09-19 -### Cambiado +### Changed -- README con el nombre del paquete publicado y las imágenes por URL absoluta, para que rendericen en npm; `media/` se incluye en el paquete. -- Podspec renombrado a `react-native-md-document.podspec`. -- Keywords `android`, `ios` y `zoom`. +- README with the published package name and absolute image URLs, so they render on npm; `media/` is included in the package. +- Podspec renamed to `react-native-md-document.podspec`. +- Keywords `android`, `ios` and `zoom`. ## [1.0.0] - 2026-09-19 -### Añadido +### Added -- Primera versión pública: visor de Markdown como PDF con volteo de página, zoom, resaltados persistentes, lectura en voz alta (TTS) y tokens de color claro/oscuro, con almacenamiento, fuente y voz inyectables. +- First public release: Markdown-as-PDF viewer with page-turning, zoom, persistent highlights, text-to-speech (TTS) and light/dark color tokens, with injectable storage, source and speech engine. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c14c35f..9494f58 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,82 +1,82 @@ -# Contribuir +# Contributing -Las contribuciones son bienvenidas. Mantén los cambios pequeños, enfocados y trazables. +Contributions are welcome. Keep changes small, focused and traceable. -## Flujo de desarrollo +## Development workflow -Usa la versión de Node de [`.nvmrc`](./.nvmrc) e instala las dependencias desde la raíz: +Use the Node.js version in [`.nvmrc`](./.nvmrc) and install the dependencies from the root: ```sh npm install ``` -`npm install` también cablea los hooks de git del repositorio (`core.hooksPath` → `.githooks/`). +`npm install` also wires the repository git hooks (`core.hooksPath` → `.githooks/`). -El proyecto de ejemplo vive en `example/` (Expo) y consume el código de `src/` directamente, así que los cambios en TypeScript se ven con recarga en caliente. +The example app lives in `example/` (Expo) and consumes the code in `src/` directly, so TypeScript changes hot-reload there. -### Comandos +### Commands ```sh -npm run typecheck # tipos con TypeScript -npm run lint # ESLint -npm run format # Prettier (escribe) -npm run format:check # Prettier (solo verifica) -npm test # pruebas unitarias (node:test) +npm run typecheck # type-check with TypeScript +npm run lint # lint with ESLint +npm run format # format with Prettier (writes) +npm run format:check # check formatting with Prettier +npm test # run the unit tests (node:test) ``` -## Modelo de ramas +## Branching model -Dos ramas permanentes, ambas protegidas: +Two permanent branches, both protected: -- **`main`**: producción. Sin pushes directos; los cambios entran solo con un Pull Request aprobado. -- **`beta`**: candidata a release. +- **`main`**: production. No direct pushes; changes land only through an approved Pull Request. +- **`beta`**: release candidate. -Ramas temporales, siempre creadas desde `beta`, en kebab-case: +Temporary branches, always created from `beta`, in kebab-case: -- `feat/` · `fix/` · `chore/` → Pull Request **a `beta`**, se borran al hacer merge. -- `hotfix/` es la única excepción: sale de `main`, va por PR a `main` y luego se integra de vuelta en `beta`. +- `feat/` · `fix/` · `chore/` · `docs/` → Pull Request **into `beta`**, deleted after merge. +- `hotfix/` is the only exception: created from `main`, PR into `main`, then back-merged into `beta`. -Una rama = un cambio enfocado. +One branch = one focused change. ## Commits -[Conventional Commits](https://www.conventionalcommits.org/es/): +Write commit messages and Pull Requests in English, following [Conventional Commits](https://www.conventionalcommits.org/): ``` -()(!): +()(!): ``` -Tipos: `feat` `fix` `docs` `style` `refactor` `perf` `test` `build` `ci` `chore` `revert`. +Types: `feat` `fix` `docs` `style` `refactor` `perf` `test` `build` `ci` `chore` `revert`. -Correspondencia con [SemVer](https://semver.org/lang/es/): `feat` → MINOR, `fix` → PATCH, cambio incompatible (`feat!` o `BREAKING CHANGE:`) → MAJOR. +[SemVer](https://semver.org/) mapping: `feat` → MINOR, `fix` → PATCH, breaking (`feat!` or `BREAKING CHANGE:`) → MAJOR. -Los hooks de [`.githooks/`](./.githooks) corren en cada commit: +The git hooks in [`.githooks/`](./.githooks) run for every commit: -- `commit-msg` rechaza mensajes que no sean Conventional Commits. -- `pre-commit` bloquea commits directos sobre `main` y `beta`. +- `commit-msg` rejects messages that are not Conventional Commits. +- `pre-commit` blocks direct commits on `main` and `beta`. -Bypass puntual (no recomendado): `git commit --no-verify`. +One-off bypass (not recommended): `git commit --no-verify`. ## Changelog -Todo cambio que afecte el comportamiento actualiza [`CHANGELOG.md`](./CHANGELOG.md) ([Keep a Changelog](https://keepachangelog.com/es-ES/)): agrega la entrada bajo `## [Sin liberar]` en la categoría correcta (Añadido · Cambiado · Corregido · Eliminado). +Any change that affects behavior updates [`CHANGELOG.md`](./CHANGELOG.md) ([Keep a Changelog](https://keepachangelog.com/en/1.1.0/)): add the entry under `## [Unreleased]` in the right category (Added · Changed · Fixed · Removed). ## Pull Requests -Al abrir un PR: +When opening a PR: -- Apunta a `beta` (o a `main` solo en `hotfix`). -- Mantenlo pequeño y centrado en un cambio. -- Asigna al menos un revisor; el merge requiere su aprobación. -- Los gates deben estar en verde: `npm run typecheck`, `npm run lint`, `npm run format:check` y `npm test`. El CI ([`.github/workflows/ci.yml`](./.github/workflows/ci.yml)) los corre en cada PR hacia `beta` o `main`. -- Incluye la entrada de `CHANGELOG.md`. +- Target `beta` (or `main` only for `hotfix`). +- Keep it small and focused on one change. +- Assign at least one reviewer; merge requires their approval. +- Gates must be green: `npm run typecheck`, `npm run lint`, `npm run format:check` and `npm test`. CI ([`.github/workflows/ci.yml`](./.github/workflows/ci.yml)) runs them on every PR to `beta` or `main`. +- Include the `CHANGELOG.md` entry. -## Release (`beta` → `main`) +## Releasing (`beta` → `main`) -1. Confirma que los gates están en verde en `beta`. -2. Decide el incremento SemVer según los commits desde el último tag. -3. Actualiza `version` en `package.json`. -4. Renombra la sección `## [Sin liberar]` del changelog a `## [X.Y.Z] - AAAA-MM-DD` y abre una nueva vacía. -5. Abre el PR `beta` → `main` y haz merge. -6. Crea el tag `vX.Y.Z` sobre `main` y súbelo (`git push origin vX.Y.Z`). -7. Publica desde la raíz del repo con `npm publish` (el README y el `package.json` quedan congelados por versión). +1. Confirm the gates are green on `beta`. +2. Decide the SemVer bump from the commits since the last tag. +3. Update `version` in `package.json`. +4. Rename the `## [Unreleased]` section of the changelog to `## [X.Y.Z] - YYYY-MM-DD` and open a fresh empty one. +5. Open the `beta` → `main` PR and merge it. +6. Tag `vX.Y.Z` on `main` and push it (`git push origin vX.Y.Z`). +7. Publish from the repository root with `npm publish` (the README and `package.json` are frozen per version). diff --git a/README.md b/README.md index 72f96b8..bca475a 100644 --- a/README.md +++ b/README.md @@ -2,54 +2,55 @@ > npm: [`@acertijosapps/react-native-md-document`](https://www.npmjs.com/package/@acertijosapps/react-native-md-document) -Visor de documentos **Markdown-como-PDF** para React Native: documento inmutable, con paso -de hoja animado, zoom, subrayado persistente, text-to-speech con resaltado sincronizado y -tema claro/oscuro. Sin librerías de terceros para el contenido (parser y render propios); el -storage, el origen del documento y el motor de voz son inyectables. +**Markdown-as-PDF** document viewer for React Native: an immutable document with animated +page-turning, zoom, persistent highlights, text-to-speech with synchronized word highlighting +and a light/dark theme. No third-party libraries for the content (own parser and renderer); the +storage, the document source and the speech engine are injectable. -## Vista previa +## Preview -Documento de demostración (`example/`) en tema claro y oscuro: banner con tokens de marca, -estilos en línea, resaltado y adaptación de color por tema. +Demo document (`example/`) in light and dark themes: banner with brand tokens, inline styles, +highlights and per-theme color adaptation. -| Claro | Oscuro | +| Light | Dark | |---|---| -| Tema claro | Tema oscuro | - -## Características - -- **Render Markdown** + **HTML embebido**: encabezados, párrafos, listas, blockquote, code, - `hr`, estilos en línea (negrita, cursiva, código, tachado, enlaces), **tablas** y HTML con - estilos inline (color, fondo, centrado, tamaños, anchos de columna). -- **Paso de hoja** animado (Reanimated + Gesture Handler), un gesto = una página. -- **Zoom** por pinch + arrastre, compuesto con el flip y el tap. -- **Subrayado** por palabra, **persistente entre sesiones** (storage inyectable). -- **Text-to-speech** con resaltado de la palabra hablada (incluso en tablas), pausa/reanudar, - velocidad, y auto-avance de página siguiendo la lectura. -- **Tema claro/oscuro** (`auto` sigue el SO) con tokens de color adaptables por tema. -- **Orígenes**: URL remota, ruta local del dispositivo, ruta del proyecto. -- **Orientación** vertical y horizontal (re-pagina al rotar). -- **Modo libro**: en tablets y plegables en horizontal muestra dos páginas lado a lado, con - el volteo de hoja desde el lomo. Activo por defecto; se desactiva con `dualPage={false}`. - -## Requisitos - -| Plataforma | Mínimo | Nota | +| Light theme | Dark theme | + +## Features + +- **Markdown** + **embedded HTML**: headings, paragraphs, lists, blockquotes, code, `hr`, + inline styles (bold, italic, code, strikethrough, links), **tables** and HTML with inline + styles (color, background, centering, sizes, column widths). +- Animated **page turning** (Reanimated + Gesture Handler), one gesture = one page. +- **Zoom** with pinch + drag, composed with the flip and the tap. +- **Per-word highlights**, **persistent across sessions** (injectable storage). +- **Text-to-speech** with the spoken word highlighted (even inside tables), pause/resume, + speed, and automatic page advance that follows the reading. +- **Light/dark theme** (`auto` follows the OS) with color tokens that adapt per theme. +- **Sources**: remote URL, local device path, bundled project file. +- **Portrait and landscape** orientation (re-paginates on rotation). +- **Book mode**: on tablets and foldables in landscape it shows two pages side by side, with + the page turning from the spine. On by default; turn it off with `dualPage={false}`. +- **Accessible**: screen readers (VoiceOver, TalkBack) hear the current page, can turn pages + with a swipe up/down, and skip the pages hidden behind the flip animation. + +## Requirements + +| Platform | Minimum | Note | |---|---|---| -| **iOS** | 13.0 | Deployment target del módulo nativo (`react-native-md-document.podspec`). TTS con resaltado por palabra incluido. | -| **Android** | 7.0 (API 24) | `minSdkVersion` del módulo. El TTS habla en API 24+, pero el **resaltado por palabra** durante la lectura usa `onRangeStart`, que requiere **Android 8.0 (API 26)**. | -| **React Native** | 0.76 | Probado con RN 0.76.5 / Expo SDK 52. | -| **Peer deps** | `react-native-reanimated` ≥ 3.16 · `react-native-gesture-handler` ≥ 2.20 | Únicas dependencias visuales. | +| **iOS** | 13.0 | Deployment target of the native module (`react-native-md-document.podspec`). TTS with per-word highlighting included. | +| **Android** | 7.0 (API 24) | The module's `minSdkVersion`. TTS speaks on API 24+, but the **per-word highlighting** while reading uses `onRangeStart`, which requires **Android 8.0 (API 26)**. | +| **React Native** | 0.76 | Tested with RN 0.76.5 / Expo SDK 52. | +| **Peer deps** | `react-native-reanimated` ≥ 3.16 and < 4 · `react-native-gesture-handler` ≥ 2.20 and < 3 | The only visual dependencies. Reanimated 4 and Gesture Handler 3 are not supported yet. | ### Android 16 KB (page size) -**Compatible.** `react-native-md-document` **no incluye código nativo compilado** -(`.so`): el módulo `DocumentSpeech` es Kotlin puro y se compila al dex de la -app. Al no aportar binarios, no hay librerías desalineadas de nuestra parte — -la librería cumple el requisito de 16 KB de Android 15 (Google Play, `targetSdk -35`) por construcción, en cualquier tamaño de página. +**Compatible.** `react-native-md-document` **ships no compiled native code** (`.so`): the +`DocumentSpeech` module is plain Kotlin and is compiled into the app's dex. Since it adds no +binaries, none of our libraries can be misaligned — the library meets Android 15's 16 KB +requirement (Google Play, `targetSdk 35`) by construction, on any page size. -## Instalación +## Installation ```sh # npm @@ -59,7 +60,7 @@ npm install @acertijosapps/react-native-md-document react-native-gesture-handler yarn add @acertijosapps/react-native-md-document react-native-gesture-handler react-native-reanimated ``` -`react-native-reanimated` requiere su plugin de Babel (último en la lista): +`react-native-reanimated` requires its Babel plugin (last in the list): ```js // babel.config.js @@ -69,7 +70,7 @@ module.exports = { }; ``` -La app debe envolver la raíz en `GestureHandlerRootView`: +The app must wrap its root in `GestureHandlerRootView`: ```tsx import { GestureHandlerRootView } from 'react-native-gesture-handler'; @@ -79,92 +80,91 @@ export default function App() { } ``` -Para el **text-to-speech nativo**, el módulo `DocumentSpeech` se autoenlaza como cualquier -paquete RN: +For **native text-to-speech**, the `DocumentSpeech` module is autolinked like any RN package: ```sh -npx pod-install # iOS (o: cd ios && pod install) -# Android: autolinking por gradle, sin pasos extra +npx pod-install # iOS (or: cd ios && pod install) +# Android: Gradle autolinking, no extra steps ``` -### Dependencias opcionales +### Optional dependencies -- **Almacenamiento del subrayado**: la librería **no** depende de - `@react-native-async-storage/async-storage`. El store es inyectable - (`getItem`/`setItem`, sync o async): sirve AsyncStorage, MMKV, SQLite o el - tuyo. Instálalo **solo si** quieres persistir subrayados con esa opción; para - subrayado en memoria usa `createMemoryHighlightStore()`, y sin subrayado no - necesitas nada. Ver [Subrayado persistente](#subrayado-persistente). +- **Highlight storage**: the library does **not** depend on + `@react-native-async-storage/async-storage`. The store is injectable + (`getItem`/`setItem`, sync or async): AsyncStorage, MMKV, SQLite or your own will do. Install + it **only if** you want to persist highlights with that option; for in-memory highlights use + `createMemoryHighlightStore()`, and if you don't highlight you need nothing. See + [Persistent highlights](#persistent-highlights). -Únicas dependencias obligatorias: `react-native-reanimated` y -`react-native-gesture-handler` (los peers). +The only required dependencies are `react-native-reanimated` and +`react-native-gesture-handler` (the peers). -## Inicio rápido +## Quick start ```tsx import { DocumentViewer, parse } from '@acertijosapps/react-native-md-document'; -const doc = parse('# Hola\n\nUn documento **Markdown**.'); +const doc = parse('# Hello\n\nA **Markdown** document.'); export function Viewer() { return ; } ``` -`DocumentViewer` mide el contenido, lo pagina al tamaño del viewport y habilita flip + zoom. -`parse(source)` devuelve un `MDDocument` inmutable con `plainText` (la proyección de texto que -usan highlight y TTS). +`DocumentViewer` measures the content, paginates it to the viewport size and enables flip + +zoom. `parse(source)` returns an immutable `MDDocument` with `plainText` (the text projection +that highlights and TTS rely on). -## Orígenes del documento +## Document sources -`useDocumentSource` cubre los tres orígenes: +`useDocumentSource` covers the three sources: ```tsx import { DocumentViewer, useDocumentSource } from '@acertijosapps/react-native-md-document'; function Screen() { - // Proyecto (bundled): string o { markdown } + // Project (bundled): string or { markdown } const { document, isLoading, error, reload } = useDocumentSource({ markdown: MD_STRING }); - // URL remota: fetch integrado + // Remote URL: built-in fetch // useDocumentSource({ uri: 'https://example.com/doc.md' }) - // Ruta local del dispositivo + // Local device path // useDocumentSource({ uri: 'file:///…/doc.md' }) if (!document) { - return null; // isLoading / error / reload disponibles + return null; // isLoading / error / reload are available } return ; } ``` -Detección de string: con `http(s)://`, `file:`, `content:`, `asset:` o que empiece con `/` se -trata como URI; cualquier otro string es Markdown crudo. +String detection: a string starting with `http(s)://`, `file:`, `content:`, `asset:` or `/` is +treated as a URI; any other string is raw Markdown. -### Carga gestionada por el visor +### Viewer-managed loading -Si no quieres cablear el hook a mano, pasa `source` directo a `DocumentViewer`: carga solo y -expone los estados de carga. +If you don't want to wire the hook by hand, pass `source` straight to `DocumentViewer`: it +loads on its own and exposes the loading states. ```tsx console.log('listo', doc.length)} + onLoadComplete={(doc) => console.log('ready', doc.length)} onLoadError={(err) => console.warn(err.message)} renderLoading={() => } renderError={(err, retry) => ( {err.message} -