From bee420feb371e5028e08161ffd0dcc84477e13f8 Mon Sep 17 00:00:00 2001 From: Silas Date: Sun, 6 Apr 2025 18:14:59 -0400 Subject: [PATCH] more fixes for pad editor, mainly for mobile --- src/lib/components/pad/Editor.svelte | 57 ++++++++++++++++++-- src/lib/components/pad/SuggestionList.svelte | 24 ++++++++- src/lib/utils/cursor.ts | 13 +++++ src/lib/utils/text.ts | 32 ++++++++++- 4 files changed, 120 insertions(+), 6 deletions(-) diff --git a/src/lib/components/pad/Editor.svelte b/src/lib/components/pad/Editor.svelte index 5140eb4..536e222 100644 --- a/src/lib/components/pad/Editor.svelte +++ b/src/lib/components/pad/Editor.svelte @@ -21,8 +21,25 @@ let currentWord = $state(''); let selectedIndex = $state(-1); let cursorPosition = $state({ x: 0, y: 0 }); + let isMobile = $state(false); const debounce = createDebounce(); + // Check for mobile on component mount and resize + function checkMobile() { + isMobile = window.innerWidth < 768; + } + + $effect(() => { + // Add resize listener for responsive behavior + window.addEventListener('resize', checkMobile); + checkMobile(); + + // Cleanup on unmount + return () => { + window.removeEventListener('resize', checkMobile); + }; + }); + $effect(() => { if (editor?.getHTML()) { localStorage.setItem('tipex', editor.getHTML()); @@ -49,7 +66,7 @@ currentWord = ''; } updateCursorPosition(); - }, 300); + }, 1000); const handleSelectionChange = debounce(async () => { const selection = editor?.view.state.selection; @@ -63,6 +80,15 @@ } }, 300); + // Special handler for touch events + function handleTouchEnd(event: TouchEvent) { + // Prevent default only if we have suggestions + if (suggestions.length > 0 || rhymes.length > 0) { + event.preventDefault(); + } + handleSelectionChange(); + } + function clearSuggestions() { suggestions = []; rhymes = []; @@ -129,7 +155,7 @@
0 || rhymes.length > 0}
diff --git a/src/lib/components/pad/SuggestionList.svelte b/src/lib/components/pad/SuggestionList.svelte index f7484b4..f62d7e3 100644 --- a/src/lib/components/pad/SuggestionList.svelte +++ b/src/lib/components/pad/SuggestionList.svelte @@ -10,6 +10,13 @@ selectedIndex: number; onSelect: (word: string) => void; }>(); + + const isMobile = window.innerWidth < 768; + + function handleTouchSelect(event: TouchEvent, word: string) { + event.preventDefault(); + onSelect(word); + }
@@ -17,13 +24,28 @@ {#each suggestions.slice(0, 10) as { word }, i} {/each}
+ + diff --git a/src/lib/utils/cursor.ts b/src/lib/utils/cursor.ts index 7226933..97d9678 100644 --- a/src/lib/utils/cursor.ts +++ b/src/lib/utils/cursor.ts @@ -8,6 +8,19 @@ export function calculateCursorPosition(editor: TipexEditor | undefined) { const rect = range.getBoundingClientRect(); const editorRect = editor?.view.dom.getBoundingClientRect() || { left: 0, top: 0 }; + // Check if on mobile + const isMobile = window.innerWidth < 768; + + if (isMobile) { + // For mobile, position suggestions at the bottom center of the screen + // to avoid issues with virtual keyboard and to make them more accessible + return { + x: window.innerWidth / 2, + y: Math.min(rect.bottom - editorRect.top + window.scrollY, window.innerHeight - 250) + }; + } + + // Desktop positioning const x = Math.min( Math.max(rect.left - editorRect.left + window.scrollX, 100), window.innerWidth - 300 diff --git a/src/lib/utils/text.ts b/src/lib/utils/text.ts index 1491fb7..73cb799 100644 --- a/src/lib/utils/text.ts +++ b/src/lib/utils/text.ts @@ -9,16 +9,44 @@ function extractLastWord(text: string): string { /** * Extracts the current word at cursor position from the document + * Improved to handle different selection scenarios on mobile */ export function extractCurrentWord(doc: any, pos: number): string { - const textBefore = doc.textBetween(Math.max(0, pos - 100), pos); - return extractLastWord(textBefore); + // For mobile, we might need to look further back to find words + // since touch selection can be less precise + const lookbackDistance = window.innerWidth < 768 ? 200 : 100; + const textBefore = doc.textBetween(Math.max(0, pos - lookbackDistance), pos); + + // First try with standard pattern + const word = extractLastWord(textBefore); + + // If standard pattern fails, try a more lenient pattern for mobile + if (!word && window.innerWidth < 768) { + const lenientMatches = textBefore.match(/[a-zA-Z]+(?:\s+[a-zA-Z]+)*\s*$/); + return lenientMatches ? lenientMatches[0].trim().split(/\s+/).pop() || '' : ''; + } + + return word; } /** * Extracts the last word from a selected text range + * Improved to handle imprecise selections on mobile */ export function extractSelectedWord(doc: any, from: number, to: number): string { + // On mobile, selections might include extra spaces + const isMobile = window.innerWidth < 768; const selectedText = doc.textBetween(from, to, ' '); + + if (isMobile && selectedText.trim()) { + // For mobile, take the largest word in the selection + const words = selectedText.trim().split(/\s+/); + if (words.length > 0) { + // Find the longest word in selection (mobile users often select multiple words) + return words.reduce((longest: string, current: string) => + current.length > longest.length ? current : longest, ''); + } + } + return extractLastWord(selectedText); }