From 236ad7fab7b055d942609cfda6970918be11830b Mon Sep 17 00:00:00 2001 From: nmemmert Date: Thu, 28 May 2026 12:54:15 -0400 Subject: [PATCH] Replace auto-add with word picker modal for Greek suggest Instead of adding all passage words automatically, the suggest button now opens a modal listing every Greek word from the verse range as checkboxes. Content words (nouns, verbs, adjectives) are pre-checked; function words (articles, prepositions, conjunctions, common pronouns) are pre-unchecked. Select all / Select none shortcuts included. Words already in the chunk are excluded from the list entirely. Co-Authored-By: Claude Sonnet 4.6 --- src/App.jsx | 157 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 136 insertions(+), 21 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index d98b6e2..b6d3b2b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -532,6 +532,9 @@ const App = () => { const [remoteOnlyProjects, setRemoteOnlyProjects] = useState([]); // projects on server not in localStorage const [staleLocalProjects, setStaleLocalProjects] = useState([]); // projects where server is newer const [suggestingGreekForChunkId, setSuggestingGreekForChunkId] = useState(null); + // suggestModal: null | { chunkId, words: [{ strongKey, lexeme, translit, def }] } + const [suggestModal, setSuggestModal] = useState(null); + const [suggestSelection, setSuggestSelection] = useState(new Set()); // --------------------------------------------------------------------------- // Startup: migrate old keys and load index @@ -927,6 +930,21 @@ useEffect(() => { return _concordanceLoadingRef.current; }; + // Common Greek NT function words to pre-uncheck in the picker + // (articles, prepositions, conjunctions, particles, common pronouns) + const GREEK_FUNCTION_WORDS = new Set([ + 'G3588','G3739','G3748', // ὁ (the), ὅς (who/which), ὅστις + 'G2532','G1161','G1063','G235','G3767','G2443','G5037','G3303', // καί, δέ, γάρ, ἀλλά, οὖν, ἵνα, τέ, μέν + 'G1487','G1437','G3754','G5613','G2531','G5618', // εἰ, ἐάν, ὅτι, ὡς, καθώς, ὥσπερ + 'G1722','G1519','G1537','G575','G4314','G3326','G2596','G1223', // ἐν, εἰς, ἐκ, ἀπό, πρός, μετά, κατά, διά + 'G1909','G5228','G5259','G4862','G3844','G4253','G4012','G1722', // ἐπί, ὑπέρ, ὑπό, σύν, παρά, πρό, περί + 'G3756','G3361','G3780','G3762','G3763', // οὐ, μή, οὐχί, οὐδείς, οὐδέποτε + 'G846','G1473','G4771','G2249','G5210', // αὐτός, ἐγώ, σύ, ἡμεῖς, ὑμεῖς + 'G3778','G1565','G3588', // οὗτος, ἐκεῖνος + 'G5100','G5101','G3956','G3745','G3748', // τις, τίς, πᾶς, ὅσος + 'G3779','G3568','G5119','G1161','G3767', // οὕτως, νῦν, τότε + ]); + const suggestGreekWordsForChunk = async (chunkId) => { const chunk = allChunks.find((c) => c.id === chunkId); const chapter = project?.chapters.find((ch) => @@ -944,11 +962,9 @@ useEffect(() => { const bookData = concordance[chapter.bookAbbrev] ?? {}; const chapterData = bookData[chapter.chapter] ?? {}; - // Collect unique Strong's numbers across the verse range const strongsInRange = new Set(); for (let v = chunk.startVerse; v <= chunk.endVerse; v++) { - const verseStrongs = chapterData[String(v)] ?? []; - for (const s of verseStrongs) strongsInRange.add(s); + for (const s of chapterData[String(v)] ?? []) strongsInRange.add(s); } if (strongsInRange.size === 0) { @@ -960,27 +976,59 @@ useEffect(() => { const existingNumbers = new Set( chunk.greekWords.map((w) => w.strongNumber.toUpperCase()).filter(Boolean) ); - const newWords = []; + + const modalWords = []; for (const strongKey of strongsInRange) { if (existingNumbers.has(strongKey)) continue; const entry = dict[strongKey]; - newWords.push({ - id: makeId(), - query: strongKey, - strongNumber: strongKey, + modalWords.push({ + strongKey, lexeme: entry?.lemma ?? '', - transliteration: entry?.translit ?? '', - partOfSpeech: '', - shortDefinition: entry?.kjv_def ?? 'No definition found.', - definitionHtml: entry ? buildGreekDefinitionHtml(strongKey, entry) : '', - loading: false, + translit: entry?.translit ?? '', + def: entry?.kjv_def ?? 'No definition found.', + entry, }); } - if (newWords.length === 0) { + + if (modalWords.length === 0) { setStatusMessage('All words from this passage are already added.'); window.setTimeout(() => setStatusMessage(''), 2000); return; } + + // Pre-select content words; pre-uncheck known function words + const preSelected = new Set( + modalWords + .filter((w) => !GREEK_FUNCTION_WORDS.has(w.strongKey)) + .map((w) => w.strongKey) + ); + setSuggestModal({ chunkId, words: modalWords }); + setSuggestSelection(preSelected); + } catch (err) { + setStatusMessage(`Suggest failed: ${err.message}`); + window.setTimeout(() => setStatusMessage(''), 3000); + } finally { + setSuggestingGreekForChunkId(null); + } + }; + + const confirmSuggestWords = () => { + if (!suggestModal) return; + const { chunkId, words } = suggestModal; + const newWords = words + .filter((w) => suggestSelection.has(w.strongKey)) + .map((w) => ({ + id: makeId(), + query: w.strongKey, + strongNumber: w.strongKey, + lexeme: w.lexeme, + transliteration: w.translit, + partOfSpeech: '', + shortDefinition: w.def, + definitionHtml: w.entry ? buildGreekDefinitionHtml(w.strongKey, w.entry) : '', + loading: false, + })); + if (newWords.length > 0) { updateProject((current) => ({ ...current, chapters: current.chapters.map((ch) => ({ @@ -990,14 +1038,11 @@ useEffect(() => { ), })), })); - setStatusMessage(`Added ${newWords.length} Greek word${newWords.length > 1 ? 's' : ''} from this passage.`); - window.setTimeout(() => setStatusMessage(''), 2500); - } catch (err) { - setStatusMessage(`Suggest failed: ${err.message}`); - window.setTimeout(() => setStatusMessage(''), 3000); - } finally { - setSuggestingGreekForChunkId(null); + setStatusMessage(`Added ${newWords.length} Greek word${newWords.length > 1 ? 's' : ''}.`); + window.setTimeout(() => setStatusMessage(''), 2000); } + setSuggestModal(null); + setSuggestSelection(new Set()); }; const isGreekStrongNumber = (query) => /^G\d+$/i.test(query.trim()); @@ -2065,6 +2110,76 @@ const restoreRemoteProject = async (id) => { + + {/* Greek word picker modal */} + {suggestModal && ( +
{ if (e.target === e.currentTarget) { setSuggestModal(null); setSuggestSelection(new Set()); } }} + > +
+
+
+

Select Greek words to add

+

Content words are pre-checked. Uncheck any you don't need.

+
+ +
+
+ + · + + {suggestSelection.size} selected +
+
    + {suggestModal.words.map((w) => ( +
  • + +
  • + ))} +
+
+ + +
+
+
+ )} ); };