From c5e509274d2b63fc528ac3865b09c34eaf28c324 Mon Sep 17 00:00:00 2001 From: arnauda-gh <59512940+arnauda-gh@users.noreply.github.com> Date: Thu, 16 Jul 2026 10:08:17 +0200 Subject: [PATCH] fix(editor): resolve disabled send button and webview scripting on android release builds --- android/app/src/main/assets/editor.html | 266 +++++++++++++ src/components/RichTextEditor.tsx | 475 +++++++++++++----------- src/screens/ComposeScreen.tsx | 11 +- 3 files changed, 542 insertions(+), 210 deletions(-) create mode 100644 android/app/src/main/assets/editor.html diff --git a/android/app/src/main/assets/editor.html b/android/app/src/main/assets/editor.html new file mode 100644 index 00000000..76337c71 --- /dev/null +++ b/android/app/src/main/assets/editor.html @@ -0,0 +1,266 @@ + + +
+ + + + + + + + + diff --git a/src/components/RichTextEditor.tsx b/src/components/RichTextEditor.tsx index 1ab257f9..9a0fe5db 100644 --- a/src/components/RichTextEditor.tsx +++ b/src/components/RichTextEditor.tsx @@ -66,16 +66,251 @@ interface Props { onBlur?: () => void; } +function buildEditorScript(opts: { + initialHtml: string; + placeholder: string; + minHeight: number; + c: ThemePalette; +}): string { + const { initialHtml, placeholder, minHeight, c } = opts; + const initialJson = JSON.stringify(initialHtml); + const placeholderJson = JSON.stringify(placeholder); + return `(function () { + var editor = document.getElementById('editor'); + var lastHtml = null; + var lastHeight = 0; + + function refreshEmpty() { + var html = editor.innerHTML; + var isEmpty = + html === '' || + html === '');
+ reportChange();
+ reportHeight();
+ reportSelection();
+ return;
+ }
+ node = node.parentNode;
+ }
+ }
+ document.execCommand('formatBlock', false, '<' + value.toLowerCase() + '>');
+ } else {
+ document.execCommand(command, false, value);
+ }
+ } catch (e) {}
+ reportChange();
+ reportHeight();
+ reportSelection();
+ },
+ setHtml: function (html) {
+ editor.innerHTML = html || '';
+ lastHtml = editor.innerHTML;
+ refreshEmpty();
+ reportChange();
+ reportHeight();
+ },
+ insertHtml: function (html) {
+ editor.focus();
+ try { document.execCommand('insertHTML', false, html); } catch (e) {}
+ reportChange();
+ reportHeight();
+ },
+ insertLink: function (url, label) {
+ editor.focus();
+ var safeSchemeRe = /^(https?:|mailto:|tel:|sms:|\/|\?|#)/i;
+ var trimmed = (url || '').trim();
+ if (!trimmed || !safeSchemeRe.test(trimmed)) return;
+ var sel = window.getSelection();
+ var hasSelection = sel && sel.rangeCount && !sel.getRangeAt(0).collapsed;
+ if (hasSelection) {
+ try { document.execCommand('createLink', false, trimmed); } catch (e) {}
+ Array.prototype.forEach.call(editor.getElementsByTagName('a'), function (a) {
+ if (a.getAttribute('href') === trimmed) {
+ a.setAttribute('rel', 'noopener noreferrer nofollow');
+ }
+ });
+ } else {
+ var text = label && label.trim() ? label : trimmed;
+ var safe = text.replace(/&/g, '&').replace(//g, '>');
+ var safeUrl = trimmed
+ .replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(//g, '>');
+ var html = '' + safe + '';
+ try { document.execCommand('insertHTML', false, html); } catch (e) {}
+ }
+ reportChange();
+ reportHeight();
+ reportSelection();
+ },
+ unsetLink: function () {
+ editor.focus();
+ try { document.execCommand('unlink', false, null); } catch (e) {}
+ reportChange();
+ reportSelection();
+ },
+ insertImage: function (src, cid, alt) {
+ editor.focus();
+ var safeSrc = (src || '').replace(/"/g, '"');
+ var safeAlt = (alt || '').replace(/&/g, '&').replace(/"/g, '"');
+ var safeCid = cid ? cid.replace(/"/g, '"') : '';
+ var html = '';
+ try { document.execCommand('insertHTML', false, html); } catch (e) {}
+ reportChange();
+ reportHeight();
+ },
+ focus: function () { editor.focus(); },
+ };
+ }
+
+ function reportChange() {
+ refreshEmpty();
+ var html = editor.innerHTML;
+ if (html === lastHtml) return;
+ lastHtml = html;
+ post('change', html);
+ }
+
+ function reportSelection() {
+ function active(name) {
+ try { return document.queryCommandState(name); } catch (e) { return false; }
+ }
+ function inBlock(tag) {
+ var sel = window.getSelection();
+ if (!sel || !sel.rangeCount) return false;
+ var node = sel.anchorNode;
+ while (node && node !== editor) {
+ if (node.nodeType === 1 && node.tagName === tag) return true;
+ node = node.parentNode;
+ }
+ return false;
+ }
+ var state = {
+ bold: active('bold'),
+ italic: active('italic'),
+ underline: active('underline'),
+ strikeThrough: active('strikeThrough'),
+ ul: active('insertUnorderedList'),
+ ol: active('insertOrderedList'),
+ blockquote: inBlock('BLOCKQUOTE'),
+ h1: inBlock('H1'),
+ h2: inBlock('H2'),
+ alignLeft: active('justifyLeft'),
+ alignCenter: active('justifyCenter'),
+ alignRight: active('justifyRight'),
+ link: inBlock('A'),
+ };
+ post('selection', state);
+ }
+
+ var dbg = document.getElementById('debug-log');
+ try {
+ if (!window.__rneStyleAdded) {
+ window.__rneStyleAdded = true;
+ var style = document.createElement('style');
+ style.innerHTML =
+ 'html, body { background: ' + ${JSON.stringify(c.background)} + '; color: ' + ${JSON.stringify(c.text)} + '; }' +
+ '#editor { min-height: ' + ${minHeight} + 'px; }' +
+ '#editor[data-empty="true"]::before { color: ' + ${JSON.stringify(c.textMuted)} + '; }' +
+ '#editor blockquote { border-left: 3px solid ' + ${JSON.stringify(c.border)} + '; color: ' + ${JSON.stringify(c.textSecondary)} + '; }' +
+ '#editor a { color: ' + ${JSON.stringify(c.primary)} + '; }' +
+ '#editor pre, #editor code { background: ' + ${JSON.stringify(c.surfaceActive)} + '; }' +
+ '::selection { background: ' + ${JSON.stringify(c.primary)} + '33; }';
+ document.head.appendChild(style);
+ }
+
+ if (!window.__rneIntervalSet) {
+ window.__rneIntervalSet = true;
+ setInterval(function () {
+ try {
+ var hasRN = typeof window.ReactNativeWebView !== 'undefined';
+ var hasPost = hasRN && typeof window.ReactNativeWebView.postMessage !== 'undefined';
+ dbg.innerHTML = "Webview - RN: " + hasRN + " | Post: " + hasPost + " | innerHTML len: " + editor.innerHTML.length;
+ } catch (e) {
+ dbg.innerHTML = "Interval Error: " + e.message;
+ }
+ }, 300);
+ }
+
+ editor.setAttribute('data-placeholder', ${placeholderJson});
+
+ var initial = ${initialJson};
+ if (initial && typeof initial === 'string') editor.innerHTML = initial;
+ refreshEmpty();
+ lastHtml = editor.innerHTML;
+
+ if (!window.__rneListenersAdded) {
+ window.__rneListenersAdded = true;
+ editor.addEventListener('input', function () {
+ reportChange();
+ reportHeight();
+ });
+ editor.addEventListener('blur', function () { post('blur', null); });
+ editor.addEventListener('focus', function () { post('focus', null); });
+ document.addEventListener('selectionchange', function () {
+ if (document.activeElement === editor) reportSelection();
+ });
+ }
+
+ reportHeight();
+ reportChange();
+
+ window.addEventListener('load', function () {
+ setTimeout(reportHeight, 50);
+ setTimeout(reportHeight, 200);
+ setTimeout(reportHeight, 600);
+ });
+ if (window.ResizeObserver) {
+ new ResizeObserver(reportHeight).observe(editor);
+ }
+ } catch (err) {
+ if (dbg) dbg.innerHTML = "Init Error: " + err.message;
+ }
+})();
+true;`;
+}
+
function buildEditorHtml(opts: {
initialHtml: string;
placeholder: string;
c: ThemePalette;
}): string {
const { initialHtml, placeholder, c } = opts;
- // Inject initial content as a JSON-encoded string so any HTML/quotes inside
- // are safely embedded (no template literal collision with the script body).
- const initialJson = JSON.stringify(initialHtml);
const placeholderJson = JSON.stringify(placeholder);
+ const initialJson = JSON.stringify(initialHtml);
return `
@@ -132,203 +367,6 @@ function buildEditorHtml(opts: {