diff --git a/.gitignore b/.gitignore index 3b3e494..535640b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,13 @@ build js/templates.js js/quicknotes-dashboard.js* js/quicknotes-talk.js* +js/vendor +css/vendor node_modules vendor translationfiles/ -translationtool.phar \ No newline at end of file +translationtool.phar + +# Docker dev environment +.env +*.log \ No newline at end of file diff --git a/Makefile b/Makefile index 7d1171e..93933a2 100644 --- a/Makefile +++ b/Makefile @@ -58,26 +58,28 @@ l10n-deps: deps: mkdir -p vendor mkdir -p js/vendor + mkdir -p css/vendor rm -rf js/vendor/* npm i cp node_modules/handlebars/dist/handlebars.js js/vendor/ cp node_modules/isotope-layout/dist/isotope.pkgd.js js/vendor/ cp node_modules/medium-editor/dist/js/medium-editor.js js/vendor/ - cp node_modules/medium-editor/dist/css/medium-editor.css vendor/ + cp node_modules/medium-editor/dist/css/medium-editor.css css/vendor/medium-editor.css cp node_modules/medium-editor-autolist/dist/autolist.js js/vendor/ cp node_modules/lozad/dist/lozad.js js/vendor/ depsmin: mkdir -p vendor mkdir -p js/vendor + mkdir -p css/vendor rm -rf js/vendor/* npm i - cp node_modules/handlebars/dist/handlebars.min.js js/vendor/handlebars.js - cp node_modules/isotope-layout/dist/isotope.pkgd.min.js js/vendor/isotope.pkgd.js + cp node_modules/handlebars/dist/handlebars.min.js js/vendor/handlebars.js + cp node_modules/isotope-layout/dist/isotope.pkgd.min.js js/vendor/isotope.pkgd.js cp node_modules/medium-editor/dist/js/medium-editor.min.js js/vendor/medium-editor.js - cp node_modules/medium-editor/dist/css/medium-editor.min.css vendor/medium-editor.css + cp node_modules/medium-editor/dist/css/medium-editor.min.css css/vendor/medium-editor.css cp node_modules/medium-editor-autolist/dist/autolist.min.js js/vendor/autolist.js - cp node_modules/lozad/dist/lozad.min.js js/vendor/lozad.js + cp node_modules/lozad/dist/lozad.min.js js/vendor/lozad.js # Build Rules diff --git a/docker/.env.example b/docker/.env.example new file mode 100644 index 0000000..883f0de --- /dev/null +++ b/docker/.env.example @@ -0,0 +1,11 @@ +MARIADB_ROOT_PASSWORD=changeme-root +MARIADB_DATABASE=nextcloud +MARIADB_USER=nextcloud +MARIADB_PASSWORD=changeme-db + +NEXTCLOUD_HTTP_PORT=8080 + +NEXTCLOUD_ADMIN_USER=admin +NEXTCLOUD_ADMIN_PASSWORD=changeme-admin +NEXTCLOUD_TRUSTED_DOMAINS=localhost:8080 +NEXTCLOUD_OVERWRITEPROTOCOL=http diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000..5c69eac --- /dev/null +++ b/docker/README.md @@ -0,0 +1,114 @@ +# Entorno de desarrollo local (Docker) + +Levanta un Nextcloud 32 + MariaDB y monta este repositorio dentro de +`custom_apps/quicknotes` para poder probar la app sin instalar Nextcloud +a mano. + +Todo lo necesario para el entorno vive en este directorio: + +``` +docker/ +├── docker-compose.yml +├── .env.example +├── scripts/ +│ ├── up.sh +│ ├── down.sh +│ ├── build.sh +│ └── enable-app.sh +└── README.md ← este archivo +``` + +## Requisitos + +- Docker 20.10+ y Docker Compose v2 (o `docker-compose` v1.29+) +- ~2 GB de RAM libres +- Puertos: `8080` (configurable en `.env`) + +## Arranque rápido + +```bash +# 1) Configurar credenciales (la primera vez) +cp .env.example .env +# editar .env si querés cambiar puertos/contraseñas + +# 2) Levantar y habilitar la app +./docker/scripts/up.sh +``` + +> Nota: el `.env` se mantiene en la raíz del repo para que `docker +> compose` (que busca `.env` relativo al compose) lo encuentre. Si +> preferís tenerlo todo dentro de `docker/`, mové `.env` también a esa +> carpeta y borrá el `--env-file` que pasa `up.sh`. + +El script: +1. Levanta MariaDB y Nextcloud. +2. **Si faltan assets compilados** (`js/templates.js`, `js/vendor/`, + `js/quicknotes-*.js`), corre `./docker/scripts/build.sh` + automáticamente usando el servicio `builder` (Node 20) de + docker-compose. +3. Espera a que Nextcloud termine el primer arranque. +4. Pregunta si querés habilitar Quick notes (responde `s`). + +Una vez listo, abrí y entrá con las credenciales +definidas en `.env` (`NEXTCLOUD_ADMIN_USER` / `NEXTCLOUD_ADMIN_PASSWORD`). + +## Compilar assets manualmente + +Si tocás un `.vue` en `src/components/`, un template Handlebars en +`js/templates/` o un `.js` en `js/`, hay que re-compilar: + +```bash +./docker/scripts/build.sh +``` + +El build se hace dentro del contenedor `quicknotes-builder` (Node 20) +y escribe los artefactos en el host, que ya están montados en +`custom_apps/quicknotes` dentro de Nextcloud. Para que el navegador +tome los cambios, refrescá con Ctrl+Shift+R (Nextcloud cachea JS +agresivamente). + +## Comandos útiles + +```bash +# Re-habilitar la app luego de cambios de código PHP/templates +./docker/scripts/enable-app.sh + +# Re-compilar assets luego de tocar .vue, .handlebars, .js +./docker/scripts/build.sh + +# Entrar al contenedor de Nextcloud +docker exec -it -u www-data quicknotes-app bash + +# Ver logs de Nextcloud +docker logs -f quicknotes-app + +# Ver logs de la DB +docker logs -f quicknotes-db + +# Bajar los contenedores (conservando los datos) +./docker/scripts/down.sh + +# Bajar y borrar TODO (DB + archivos de Nextcloud + caché de npm) +./docker/scripts/down.sh --purge +``` + +## Cómo funciona + +- `docker-compose.yml` define tres servicios: `db` (MariaDB 11.4), + `app` (Nextcloud 32) y `builder` (Node 20, para compilar assets). +- El directorio raíz del repo se monta como volumen en + `/var/www/html/custom_apps/quicknotes` (los volúmenes del compose + usan `../` para salir de `docker/` y montar el proyecto completo). +- Los datos persistentes (DB + `data/`, `config/`, `apps/` no montados) + viven en los volúmenes `quicknotes_db`, `quicknotes_nc` y + `quicknotes_npm_cache`. + +## Limitaciones + +- El `info.xml` declara la app para Nextcloud 32. Si necesitás otra + versión, cambiá el tag de la imagen en `docker-compose.yml` (p. ej. + `nextcloud:31`). +- SELinux en Enforcing requiere el sufijo `:z` en el bind mount (ya + está configurado). Si tu distro no usa SELinux podés sacarlo. +- El bundle Vue de dashboard pesa ~3 MB (warning de webpack). Es + tamaño aceptable para dev; la app oficial lo reduce con code-split. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..84f349f --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,64 @@ +name: quicknotes + +services: + db: + image: mariadb:11.4 + container_name: quicknotes-db + restart: unless-stopped + command: --transaction-isolation=READ-COMMITTED --log-bin=binlog --binlog-format=ROW + environment: + MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD} + MARIADB_DATABASE: ${MARIADB_DATABASE} + MARIADB_USER: ${MARIADB_USER} + MARIADB_PASSWORD: ${MARIADB_PASSWORD} + volumes: + - db:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 10s + timeout: 5s + retries: 10 + + app: + image: nextcloud:32 + container_name: quicknotes-app + restart: unless-stopped + depends_on: + db: + condition: service_healthy + ports: + - "${NEXTCLOUD_HTTP_PORT}:80" + environment: + MYSQL_HOST: db + MYSQL_DATABASE: ${MARIADB_DATABASE} + MYSQL_USER: ${MARIADB_USER} + MYSQL_PASSWORD: ${MARIADB_PASSWORD} + NEXTCLOUD_ADMIN_USER: ${NEXTCLOUD_ADMIN_USER} + NEXTCLOUD_ADMIN_PASSWORD: ${NEXTCLOUD_ADMIN_PASSWORD} + NEXTCLOUD_TRUSTED_DOMAINS: ${NEXTCLOUD_TRUSTED_DOMAINS} + NEXTCLOUD_OVERWRITEPROTOCOL: ${NEXTCLOUD_OVERWRITEPROTOCOL} + volumes: + - nc:/var/www/html + - ../:/var/www/html/custom_apps/quicknotes:z + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost/status.php"] + interval: 30s + timeout: 10s + retries: 5 + + builder: + image: node:20-bookworm + container_name: quicknotes-builder + working_dir: /app + volumes: + - ../:/app:z + - npm_cache:/root/.npm + command: ["sh", "-c", "echo 'Use docker/scripts/build.sh to trigger the build' && sleep infinity"] + +volumes: + db: + name: quicknotes_db + nc: + name: quicknotes_nc + npm_cache: + name: quicknotes_npm_cache diff --git a/docker/scripts/build.sh b/docker/scripts/build.sh new file mode 100755 index 0000000..b90a325 --- /dev/null +++ b/docker/scripts/build.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Compila los assets de la app (vendors JS, templates Handlebars, bundles Vue). +# Requiere el servicio `builder` del docker-compose (node:20). +set -euo pipefail + +cd "$(dirname "$0")/.." # -> docker/ + +if ! docker compose ps builder --status running >/dev/null 2>&1; then + echo ">> El servicio 'builder' no está corriendo. Levantando..." + docker compose up -d builder + echo ">> Esperando a que 'builder' esté listo..." + for i in {1..30}; do + if docker compose ps builder --status running >/dev/null 2>&1; then + break + fi + sleep 1 + done +fi + +echo ">> Ejecutando make build dentro del contenedor builder..." +docker compose exec -T builder sh -c ' + set -e + cd /app + + echo "[1/3] npm install (puede tardar varios minutos la primera vez)..." + npm ci --no-audit --no-fund || npm install --no-audit --no-fund + + echo "[2/3] copy deps (depsmin) + precompilar templates handlebars..." + mkdir -p vendor js/vendor css/vendor + rm -rf js/vendor/* + cp node_modules/handlebars/dist/handlebars.min.js js/vendor/handlebars.js + cp node_modules/isotope-layout/dist/isotope.pkgd.min.js js/vendor/isotope.pkgd.js + cp node_modules/medium-editor/dist/js/medium-editor.min.js js/vendor/medium-editor.js + cp node_modules/medium-editor/dist/css/medium-editor.min.css css/vendor/medium-editor.css + cp node_modules/medium-editor-autolist/dist/autolist.min.js js/vendor/autolist.js + cp node_modules/lozad/dist/lozad.min.js js/vendor/lozad.js + + node_modules/.bin/handlebars js/templates -f js/templates.js + + echo "[3/3] webpack build (vue + js principal)..." + npm run build + + echo "" + echo "Build OK." + ls -la js/templates.js js/quicknotes*.js* 2>/dev/null || true +' diff --git a/docker/scripts/down.sh b/docker/scripts/down.sh new file mode 100755 index 0000000..47c7daa --- /dev/null +++ b/docker/scripts/down.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +# Baja los contenedores y opcionalmente borra volúmenes. +set -euo pipefail + +cd "$(dirname "$0")/.." # -> docker/ + +if [[ "${1:-}" == "--purge" || "${1:-}" == "-p" ]]; then + echo ">> Bajando contenedores y borrando volúmenes..." + docker compose down -v +else + echo ">> Bajando contenedores (los volúmenes se conservan)..." + docker compose down +fi diff --git a/docker/scripts/enable-app.sh b/docker/scripts/enable-app.sh new file mode 100755 index 0000000..b36c43c --- /dev/null +++ b/docker/scripts/enable-app.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# Habilita la app Quick notes dentro del contenedor de Nextcloud. +set -euo pipefail + +cd "$(dirname "$0")/.." # -> docker/ + +echo ">> Verificando que el código de la app esté montado en el contenedor..." +docker exec quicknotes-app test -f /var/www/html/custom_apps/quicknotes/appinfo/info.xml + +echo ">> Ajustando permisos..." +docker exec --user root quicknotes-app chown -R www-data:www-data /var/www/html/custom_apps/quicknotes + +echo ">> Deshabilitando por si estaba activa, luego habilitando..." +docker exec --user www-data quicknotes-app php occ app:disable quicknotes || true +docker exec --user www-data quicknotes-app php occ app:enable quicknotes + +echo ">> Estado:" +docker exec --user www-data quicknotes-app php occ app:list | grep -A1 '^Enabled:' | grep quicknotes || true + +echo "OK." diff --git a/docker/scripts/up.sh b/docker/scripts/up.sh new file mode 100755 index 0000000..58d6a47 --- /dev/null +++ b/docker/scripts/up.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# Arranca Nextcloud 32 + MariaDB y monta este repo como custom_apps/quicknotes. +set -euo pipefail + +cd "$(dirname "$0")/.." # -> docker/ +REPO_ROOT="$(cd .. && pwd)" + +if [[ ! -f "$REPO_ROOT/.env" ]]; then + echo "Falta $REPO_ROOT/.env. Copiá docker/.env.example: cp $REPO_ROOT/.env.example $REPO_ROOT/.env" >&2 + exit 1 +fi + +set -a +# shellcheck disable=SC1091 +source "$REPO_ROOT/.env" +set +a + +echo ">> Levantando contenedores..." +docker compose --env-file "$REPO_ROOT/.env" up -d + +echo +echo ">> Verificando assets compilados de la app..." +if [[ ! -f "$REPO_ROOT/js/templates.js" \ + || ! -d "$REPO_ROOT/js/vendor" \ + || ! -f "$REPO_ROOT/js/quicknotes-dashboard.js" ]]; then + echo " Faltan assets (templates.js / vendor / bundles Vue). Compilando..." + "$(dirname "$0")/build.sh" +else + echo " Assets OK." +fi + +echo +echo ">> Esperando a que Nextcloud termine el primer arranque (puede tardar 1-2 min)..." +for i in {1..60}; do + if docker exec quicknotes-app curl -fsS http://localhost/status.php >/dev/null 2>&1; then + echo " Nextcloud está respondiendo." + break + fi + printf "." + sleep 5 +done +echo + +echo ">> ¿Habilitar Quick notes ahora? [s/N]" +read -r ans +if [[ "${ans:-N}" =~ ^[sSyY]$ ]]; then + "$(dirname "$0")/enable-app.sh" +fi + +echo +echo "Listo. Abrí http://localhost:${NEXTCLOUD_HTTP_PORT:-8080}" +echo "Usuario: ${NEXTCLOUD_ADMIN_USER:-admin}" diff --git a/js/script.js b/js/script.js index 826ba2a..d126ec0 100644 --- a/js/script.js +++ b/js/script.js @@ -995,6 +995,14 @@ View.prototype = { }, _initEditor: function() { var modalcontent = $('#modal-note-div #content-editable'); + if (modalcontent.length === 0) { + console.error('[quicknotes] _initEditor: #content-editable not found in DOM'); + return; + } + if (this._editor) { + this._editor.destroy(); + this._editor = undefined; + } var editor = new MediumEditor(modalcontent, { toolbar: { buttons: [ diff --git a/templates/main.php b/templates/main.php index d00bc36..d0530d0 100644 --- a/templates/main.php +++ b/templates/main.php @@ -10,6 +10,7 @@ \OCP\Util::addScript('quicknotes', 'script'); style('quicknotes', 'not-vue'); style('quicknotes', 'style'); +\OCP\Util::addStyle('quicknotes', 'vendor/medium-editor'); style('quicknotes', 'medium'); style('quicknotes', 'qn-colorpick'); ?>