From 818c793ce6d5c312272515fe8b344492b03ef69b Mon Sep 17 00:00:00 2001 From: Hermes Date: Fri, 11 Sep 2026 12:28:13 +0000 Subject: [PATCH] Core-clean variant: editor id eurooffice, no text-app patch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - HtmlDirectEditor getId() now returns 'eurooffice' (hard-coded in the iOS/Android editor registry) -> 'Öffnen mit Office' long-press renders HTML - install.sh restores the original TextDirectEditor if a previous install patched it; no core app patch anymore - README rewritten: behavior, variant rationale, files --- README.md | 52 ++++--- install.sh | 51 +++---- patches/htmlviewer/HtmlDirectEditor.php | 5 +- patches/text/TextDirectEditor.php | 171 ------------------------ 4 files changed, 55 insertions(+), 224 deletions(-) delete mode 100644 patches/text/TextDirectEditor.php diff --git a/README.md b/README.md index 676e63b..003a75f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # htmlviewer-directediting -Rendert HTML-Dateien in der **Nextcloud iOS/Android App** statt Quelltext (QuickLook) anzuzeigen. +Rendert HTML-Dateien in der **Nextcloud iOS/Android App** in einem sandboxed +WebView statt als Quelltext (QuickLook). ## Warum das existiert @@ -11,19 +12,20 @@ Editor-Registry** (`NCDirectEditorAdapter` in nextcloud/ios): nur `text`, Server-Editor mit eigener ID (z.B. `htmlviewer`) wird von der App nicht aufgelöst → Fallback auf QuickLook → HTML-Quelltext. -## Lösung (2 Patches) +## Lösung (nur htmlviewer-App, core-clean) -1. **htmlviewer** (custom_apps): registriert einen `IEditor` (`HtmlDirectEditor`) - mit Mimetype `text/html` + Bridge-Klasse, Template mit sandboxed iframe - (`srcdoc` — Inhalt direkt eingebettet, kein Cookie-Problem in mobilen - WebViews) und Content-Controller. -2. **text** (Core-App, gepatcht): - - `open()` leitet `text/html` an die htmlviewer-Bridge um - (`HtmlDirectEditorBridge::openHtml`) — die iOS-App wählt Editor `text` - (registry-bekannt), der Server liefert aber das HTML-Template. - - `text/html` bleibt in der Mimetypeliste der Text-App (wichtig!). +Die htmlviewer-App registriert einen `IEditor` mit der hartcodeten App-Editor-ID +**`eurooffice`** und Mimetype `text/html` + Bridge, Template mit sandboxed +iframe (`srcdoc` — Inhalt direkt eingebettet, kein Cookie-Problem in mobilen +WebViews) und Content-Controller. -Bei Nextcloud-Core selbst (lib/, config) wurde **nichts** geändert. +Die Nextcloud-Text-App und der Core werden **nicht** verändert. + +### Verhalten + +- **Single-Tap** auf HTML: Quelltext-Ansicht (iOS-Standard, Text-App gewinnt + die Editor-Wahl — das ist ohne Text-App-Patch nicht änderbar, siehe unten) +- **Long-Press → "Öffnen mit Office"**: rendert das HTML in der WebView ## Installation @@ -32,25 +34,33 @@ Bei Nextcloud-Core selbst (lib/, config) wurde **nichts** geändert. ``` Idempotent, kann nach jedem htmlviewer-/Nextcloud-Update neu laufen. -Backup des Original-TextDirectEditor landet unter `/tmp/TextDirectEditor.php.bak-htmlproject` im Container. +Stellt ein altes Text-App-Patch-Backup automatisch wieder her, falls vorhanden. ## Nach App-Updates neu nötig -- htmlviewer-App-Update überschreibt `lib/DirectEditing/`, `templates/`, `Application.php`-Registrierung → `install.sh` erneut -- Nextcloud-Update überschreibt `apps/text/.../TextDirectEditor.php` → `install.sh` erneut +- htmlviewer-App-Update überschreibt `lib/DirectEditing/`, `templates/`, + `Application.php`-Registrierung → `install.sh` erneut + +## Varianten + +**Auto-Open bei Single-Tap** wäre nur möglich, wenn die Text-App gepatcht wird +(`open()`-Bridge auf die htmlviewer-Template + `text/html` in deren Mimetypen), +weil die iOS-App im automatischen Loop immer zuerst Editor `text` wählt und die +App-Registry nur die 5 hartcodeten IDs kennt. Das machen wir bewusst NICHT — +Core-App-Patches sind update-anfällig und unübersichtlich. Die Long-Press- +Variante funktioniert ausschließlich mit der installierten htmlviewer-App. ## Dateien -- `patches/htmlviewer/HtmlDirectEditor.php` — IEditor-Implementierung (DirectEditing-Registrierung, für Web-Client/Wiederverwendung) -- `patches/htmlviewer/HtmlDirectEditorBridge.php` — statische Bridge, von TextDirectEditor aufgerufen +- `patches/htmlviewer/HtmlDirectEditor.php` — IEditor-Implementierung, ID `eurooffice` +- `patches/htmlviewer/HtmlDirectEditorBridge.php` — Template-Renderer - `patches/htmlviewer/DirectController.php` — `/apps/htmlviewer/direct/{fileId}` Content-Endpoint (NoAdminRequired, Session) - `patches/htmlviewer/RegisterDirectEditorListener.php` — RegisterDirectEditorEvent-Listener - `patches/htmlviewer/directEditing-template.php` — Template: Fullscreen-iframe mit srcdoc + sandbox - `patches/htmlviewer/patch_app.py` — idempotente Registrierung in Application.php + routes.php -- `patches/text/TextDirectEditor.php` — gepatchte Text-App (open()-Bridge) -- `install.sh` — Installer +- `install.sh` — Installer (core-clean: restores any old text-app backup) ## Status (2026-09-11) -Auf nextcloud1 (Nextcloud 34.0.2, htmlviewer 33.0.0) deployed und iPhone-getestet: -HTML rendert in der iOS-App in sandboxed WebView. \ No newline at end of file +Auf nextcloud1 (Nextcloud 34.0.2, htmlviewer 33.0.0) getestet: +Long-Press → "Öffnen mit Office" rendert HTML in der iOS-App in sandboxed WebView. \ No newline at end of file diff --git a/install.sh b/install.sh index d4845f0..b83f0d0 100755 --- a/install.sh +++ b/install.sh @@ -1,9 +1,16 @@ #!/usr/bin/env bash # htmlviewer-directediting installer # Applies the HTML-in-iOS-App patches to a Nextcloud container. -# Usage: ./install.sh [custom_apps-path-in-container] +# Usage: ./install.sh # default container: nextcloud1-app-1 # Idempotent: safe to re-run after app updates. +# +# This version is CORE-CLEAN: the Nextcloud text app is NOT patched. +# The htmlviewer app registers its editor under the hard-coded app +# editor id "eurooffice" ("Öffnen mit Office" via long press). Plain +# tap on an HTML file still opens the source view (iOS default) — +# single-tap auto-open would require patching the text app, see +# README.md "Varianten". set -euo pipefail CONTAINER="${1:-nextcloud1-app-1}" @@ -11,67 +18,49 @@ APP="/var/www/html/custom_apps/htmlviewer" TEXT="/var/www/html/apps/text/lib/DirectEditing/TextDirectEditor.php" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -echo "=== htmlviewer-directediting installer ===" +echo "=== htmlviewer-directediting installer (core-clean) ===" echo "Container: $CONTAINER" # 0. Sanity docker exec "$CONTAINER" test -d "$APP" || { echo "ERROR: htmlviewer app not found at $APP"; exit 1; } -# 1. Backup text editor (first run only) -if ! docker exec "$CONTAINER" test -f /tmp/TextDirectEditor.php.bak-htmlproject; then - docker exec "$CONTAINER" cp "$TEXT" /tmp/TextDirectEditor.php.bak-htmlproject 2>/dev/null || \ - docker exec "$CONTAINER" bash -c "cp $TEXT /tmp/TextDirectEditor.php.bak-htmlproject" || true +# 1. RESTORE original text app if it was patched by an earlier install +if docker exec "$CONTAINER" test -f /tmp/TextDirectEditor.php.bak-htmlproject; then + echo "--- restoring original TextDirectEditor.php from backup" + docker exec "$CONTAINER" bash -c "cp /tmp/TextDirectEditor.php.bak-htmlproject $TEXT" +else + echo "--- no text-app backup found; nothing to restore (good)" fi -# 2. Deploy htmlviewer files (from repo to container via /tmp) +# 2. Deploy htmlviewer files TMPDIR_ON_HOST=$(mktemp -d) cp patches/htmlviewer/*.php "$TMPDIR_ON_HOST/" -cp patches/text/TextDirectEditor.php "$TMPDIR_ON_HOST/TextDirectEditor-patched.php" - docker cp "$TMPDIR_ON_HOST/HtmlDirectEditor.php" "$CONTAINER:$APP/lib/DirectEditing/HtmlDirectEditor.php" docker cp "$TMPDIR_ON_HOST/HtmlDirectEditorBridge.php" "$CONTAINER:$APP/lib/DirectEditing/HtmlDirectEditorBridge.php" docker cp "$TMPDIR_ON_HOST/DirectController.php" "$CONTAINER:$APP/lib/Controller/DirectController.php" docker cp "$TMPDIR_ON_HOST/RegisterDirectEditorListener.php" "$CONTAINER:$APP/lib/Listeners/RegisterDirectEditorListener.php" docker cp "$TMPDIR_ON_HOST/directEditing-template.php" "$CONTAINER:$APP/templates/directEditing.php" -docker cp "$TMPDIR_ON_HOST/TextDirectEditor-patched.php" "$CONTAINER:/var/www/html/apps/text/lib/DirectEditing/TextDirectEditor.php" docker exec "$CONTAINER" chown www-data:www-data \ "$APP/lib/DirectEditing/HtmlDirectEditor.php" \ "$APP/lib/DirectEditing/HtmlDirectEditorBridge.php" \ "$APP/lib/Controller/DirectController.php" \ "$APP/lib/Listeners/RegisterDirectEditorListener.php" \ - "$APP/templates/directEditing.php" \ - /var/www/html/apps/text/lib/DirectEditing/TextDirectEditor.php + "$APP/templates/directEditing.php" rm -rf "$TMPDIR_ON_HOST" # 3. Register in Application.php + routes.php (idempotent) docker cp patches/htmlviewer/patch_app.py "$CONTAINER:/tmp/patch_app.py" docker exec "$CONTAINER" python3 /tmp/patch_app.py -# 4. Text-app mimic list: remove text/html so iOS picks... (NO — text stays owner) -# NOTE: text/html must STAY in the text editor's mimetypes list so the -# iOS app (which only knows editor id "text") opens HTML via the bridge. -# If it was removed by an older version of this installer, re-add it. -docker exec "$CONTAINER" python3 - <<'PYEOF' -p='/var/www/html/apps/text/lib/DirectEditing/TextDirectEditor.php' -c=open(p).read() -if "'text/html'" not in c: - c=c.replace("\t\t\t'text/css',", "\t\t\t'text/css',\n\t\t\t'text/html',") - open(p,'w').write(c) - print('text/html re-added to TextDirectEditor mimetypes') -else: - print('text/html present in TextDirectEditor') -PYEOF - -# 5. Lint + restart +# 4. Lint + restart for f in "$APP/lib/DirectEditing/HtmlDirectEditor.php" \ "$APP/lib/DirectEditing/HtmlDirectEditorBridge.php" \ "$APP/lib/Controller/DirectController.php" \ "$APP/lib/Listeners/RegisterDirectEditorListener.php" \ - "$APP/templates/directEditing.php" \ - /var/www/html/apps/text/lib/DirectEditing/TextDirectEditor.php; do + "$APP/templates/directEditing.php"; do docker exec "$CONTAINER" php -l "$f" done echo "=== Restarting container ===" docker restart "$CONTAINER" -echo "=== Done. Test: open an .html file in the iOS app (kill app first). ===" \ No newline at end of file +echo "=== Done. iOS: long press HTML file -> 'Öffnen mit Office' renders the page. ===" \ No newline at end of file diff --git a/patches/htmlviewer/HtmlDirectEditor.php b/patches/htmlviewer/HtmlDirectEditor.php index bb178ea..4e0ffc9 100644 --- a/patches/htmlviewer/HtmlDirectEditor.php +++ b/patches/htmlviewer/HtmlDirectEditor.php @@ -34,7 +34,10 @@ class HtmlDirectEditor implements IEditor { #[\Override] public function getId(): string { - return Application::APP_ID; + // "eurooffice" is one of the hard-coded editor IDs the Nextcloud + // iOS/Android apps resolve in their editor registry. The menu + // "Öffnen mit Office" (long press) then opens this editor. + return 'eurooffice'; } #[\Override] diff --git a/patches/text/TextDirectEditor.php b/patches/text/TextDirectEditor.php deleted file mode 100644 index 13f3438..0000000 --- a/patches/text/TextDirectEditor.php +++ /dev/null @@ -1,171 +0,0 @@ -l10n = $l10n; - $this->initialStateProvider = $initialStateProvider; - $this->apiService = $apiService; - $this->appConfig = $appConfig; - } - - /** - * Return a unique identifier for the editor - * - * e.g. richdocuments - * - * @return string - */ - public function getId(): string { - return Application::APP_NAME; - } - - /** - * Return a readable name for the editor - * - * e.g. Collabora Online - * - * @return string - */ - public function getName(): string { - return $this->l10n->t('Nextcloud Text'); - } - - /** - * A list of mimetypes that should open the editor by default - * - * @return string[] - */ - public function getMimetypes(): array { - return [ - 'text/markdown', - 'text/plain', - 'application/cmd', - 'application/x-empty', - 'application/x-msdos-program', - 'application/javascript', - 'application/json', - 'application/x-perl', - 'application/x-php', - 'application/x-tex', - 'application/xml', - 'application/yaml', - 'text/css', - 'text/csv', - - 'text/org', - 'text/x-c', - 'text/x-c++src', - 'text/x-h', - 'text/x-java-source', - 'text/x-ldif', - 'text/x-nfo', - 'text/x-python', - 'text/x-shellscript', - ]; - } - - /** - * A list of mimetypes that can be opened in the editor optionally - * - * @return string[] - */ - public function getMimetypesOptional(): array { - return []; - } - - /** - * Return a list of file creation options to be presented to the user - * - * @return TextDocumentCreator[] - */ - public function getCreators(): array { - return [ - new TextDocumentCreator($this->l10n, $this->appConfig), - ]; - } - - /** - * Return if the view is able to securely view a file without downloading it to the browser - * - * @return bool - */ - public function isSecure(): bool { - return false; - } - - /** - * Return a template response for displaying the editor - * - * open can only be called once when the client requests the editor with a one-time-use token - * For handling editing and later requests, editors need to impelement their own token handling and take care of invalidation - * - * This behavior is similar to the current direct editing implementation in collabora where we generate a one-time token and switch over to the regular wopi token for the actual editing/saving process - * - * @param IToken $token - * @return Response - */ - public function open(IToken $token): Response { - $token->useTokenScope(); - try { - // HTML patch (Hermes Agent 2026-09): route text/html to the - // htmlviewer sandbox template, because the iOS/Android apps - // only know the hard-coded editor id "text" and would - // otherwise show HTML source in QuickLook. - if ($token->getFile()->getMimeType() === 'text/html') { - return \OCA\HtmlViewer\DirectEditing\HtmlDirectEditorBridge::openHtml($token); - } - $session = $this->apiService->create($token->getFile()->getId()); - $this->initialStateProvider->provideFile([ - 'fileId' => $token->getFile()->getId(), - 'mimetype' => $token->getFile()->getMimeType(), - 'session' => \json_encode($session->getData()) - ]); - $this->initialStateProvider->provideDirectEditToken($token->getToken()); - $this->initialStateProvider->provideState(); - Util::addScript('text', 'text-text'); - Util::addStyle('text', 'text-text'); - return new TemplateResponse('text', 'main', [], 'base'); - } catch (InvalidPathException $e) { - } catch (NotFoundException $e) { - } catch (NotPermittedException $e) { - } - return new NotFoundResponse(); - } -}