Mobile: fix page editor keyboard and interaction issues (#346)

* Mobile: add prebuild hook and submit config for EAS builds

Add prebuildCommand to run copy-editor.js during EAS builds so the
editor.html asset is available for Metro bundling. Also restore the
App Store Connect submit credentials (appleId, appleTeamId, ascAppId)
so builds auto-submit to TestFlight.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Mobile: use eas-build-post-install hook for editor asset

prebuildCommand replaces expo prebuild entirely, causing build failures.
Use eas-build-post-install lifecycle hook instead to run copy-editor.js
after npm install but before the native build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Mobile: fix page editor keyboard and interaction issues

- Disable native WebView scrolling and use CSS overflow to prevent
  scroll-to-top when keyboard opens
- Fix slash command click-through by preventing ProseMirror from
  processing mousedown events immediately after command selection
- Remove keyboard from auto-opening on page load and auto-focusing
  cursor; keyboard now only appears on user tap
- Add eas-build-post-install hook to build editor HTML asset during
  EAS builds
- Improve editor.html iframe sandbox and CSP settings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ylber Gashi
2026-03-15 22:17:36 +01:00
committed by GitHub
parent c945579abd
commit 2be4b599e7
8 changed files with 56 additions and 12 deletions

View File

@@ -3,8 +3,6 @@ import { useCallback, useEffect, useState } from 'react';
import {
AppState,
AppStateStatus,
KeyboardAvoidingView,
Platform,
Pressable,
StyleSheet,
Text,
@@ -143,10 +141,7 @@ export default function PageScreen() {
)}
<View style={styles.headerSpacer} />
</View>
<KeyboardAvoidingView
style={styles.content}
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
>
<View style={styles.content}>
<PageWebView
nodeId={pageId!}
userId={userId}
@@ -159,7 +154,7 @@ export default function PageScreen() {
updates={docUpdates ?? []}
onNavigateNode={handleNavigateNode}
/>
</KeyboardAvoidingView>
</View>
{page && (
<RenameNodeSheet
visible={showRename}

View File

@@ -8,6 +8,7 @@
"scripts": {
"compile": "tsc --noEmit -p tsconfig.json",
"lint": "eslint app src --ext .ts,.tsx --max-warnings 0",
"eas-build-post-install": "node scripts/copy-editor.js",
"prestart": "node scripts/copy-editor.js",
"preios": "node scripts/copy-editor.js",
"preandroid": "node scripts/copy-editor.js",

View File

@@ -337,6 +337,25 @@ export const PageWebView = ({
payload: { message: 'Unhandled rejection: ' + (e.reason && e.reason.message || e.reason || 'unknown') }
}));
});
// Prevent scroll-to-top on focus: save scroll container position and restore it
document.addEventListener('DOMContentLoaded', function() {
var sc = document.getElementById('scroll-container');
if (!sc) return;
var saved = 0;
sc.addEventListener('scroll', function() { saved = sc.scrollTop; }, { passive: true });
document.addEventListener('focusin', function() {
var s = saved;
// Restore after browser/ProseMirror auto-scroll
requestAnimationFrame(function() {
requestAnimationFrame(function() {
if (Math.abs(sc.scrollTop - s) > 50) {
sc.scrollTop = s;
}
});
});
}, true);
});
true;
`}
style={[
@@ -346,9 +365,8 @@ export const PageWebView = ({
]}
javaScriptEnabled
domStorageEnabled
scrollEnabled
scrollEnabled={false}
bounces={false}
keyboardDisplayRequiresUserAction={false}
hideKeyboardAccessoryView
automaticallyAdjustContentInsets={false}
contentMode="mobile"

View File

@@ -56,16 +56,24 @@
html, body {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
background-color: var(--background);
color: var(--foreground);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
-webkit-text-size-adjust: 100%;
}
#scroll-container {
height: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
</style>
</head>
<body>
<div id="editor-root"></div>
<div id="scroll-container">
<div id="editor-root"></div>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@@ -76,6 +76,7 @@ import {
TableCommand,
TodoCommand,
} from '@colanode/ui/editor/commands';
import { isSlashCommandActive } from '@colanode/ui/editor/extensions/commander';
import { MobilePageNode } from './extensions/page';
import { MobileFolderNode } from './extensions/folder';
import { MobileFileNode } from './extensions/file';
@@ -235,6 +236,15 @@ export const DocumentEditor = ({
'prose-lg prose-stone dark:prose-invert prose-headings:font-title font-default focus:outline-none max-w-full text-foreground',
spellCheck: 'true',
},
handleDOMEvents: {
mousedown: (_view, event) => {
if (isSlashCommandActive()) {
event.preventDefault();
return true;
}
return false;
},
},
},
content: buildEditorContent(
node.id,

View File

@@ -180,7 +180,7 @@ export function MobileEditorApp() {
style={{
padding: '0 16px',
paddingBottom: 'env(safe-area-inset-bottom, 20px)',
minHeight: '100vh',
minHeight: '100%',
}}
>
<DocumentEditor

View File

@@ -40,6 +40,7 @@
/* Mobile-specific editor styles */
.ProseMirror {
min-height: 60vh;
min-height: 70vh;
padding-bottom: 40vh;
-webkit-tap-highlight-color: transparent;
}

View File

@@ -38,6 +38,15 @@ interface CommanderOptions {
const navigationKeys = ['ArrowUp', 'ArrowDown', 'Enter'];
// Flag to prevent ProseMirror from handling the mousedown that follows
// after a slash command item is tapped (pointerdown destroys the popup,
// then mousedown lands on the editor underneath).
let slashCommandJustSelected = false;
export function isSlashCommandActive() {
return slashCommandJustSelected;
}
const filterCommands = ({
query,
commands,
@@ -167,6 +176,8 @@ const CommandList = ({
// Added this event handler because the onClick handler was not working
e.preventDefault();
e.stopPropagation();
slashCommandJustSelected = true;
setTimeout(() => { slashCommandJustSelected = false; }, 300);
selectItem(index);
}}
>