diff --git a/src/App.jsx b/src/App.jsx index 9b5434f..b1c514b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -857,10 +857,40 @@ const App = () => { if (!word || !word.query.trim()) return; updateChunkWord(chunkId, wordId, { loading: true }); try { - const query = encodeURIComponent(word.query.trim()); - const response = await fetch(`https://bolls.life/dictionary-definition/BDBT/${query}/`); - if (!response.ok) throw new Error('Lookup failed.'); - const definitions = await response.json(); + const raw = word.query.trim(); + + // Normalize Strong's numbers to always use the G prefix for Greek. + // A bare number like "4102" would otherwise match Hebrew H4102. + let normalized; + if (/^\d+$/.test(raw)) { + normalized = `G${raw}`; + } else if (/^[gGhH]\d+$/.test(raw)) { + normalized = raw.toUpperCase(); + } else { + normalized = raw; + } + + const isStrongsNumber = /^[GH]\d+$/.test(normalized); + const encoded = encodeURIComponent(normalized); + + let definitions; + if (isStrongsNumber) { + // Direct Strong's number lookup in the combined Greek/Hebrew dictionary. + const response = await fetch(`https://bolls.life/dictionary-definition/BDBT/${encoded}/`); + if (!response.ok) throw new Error('Lookup failed.'); + definitions = await response.json(); + } else { + // English word search — use the search-dictionaries endpoint. + // Filter results to Greek entries only (topic starts with G). + const response = await fetch(`https://bolls.life/search-dictionaries/BDBT/${encoded}/`); + if (!response.ok) throw new Error('Lookup failed.'); + const results = await response.json(); + const greekResults = Array.isArray(results) + ? results.filter((r) => String(r.topic ?? '').startsWith('G')) + : []; + definitions = greekResults.length > 0 ? [greekResults[0]] : []; + } + if (!Array.isArray(definitions) || definitions.length === 0) { updateChunkWord(chunkId, wordId, { strongNumber: '', @@ -872,7 +902,7 @@ const App = () => { const first = definitions[0]; const extractedPartOfSpeech = extractPartOfSpeech(first.definition || ''); updateChunkWord(chunkId, wordId, { - strongNumber: first.topic || word.query, + strongNumber: first.topic || normalized, lexeme: first.lexeme || '', transliteration: first.transliteration || '', partOfSpeech: extractedPartOfSpeech || word.partOfSpeech || '', @@ -1607,7 +1637,7 @@ const App = () => { onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); lookupGreekWord(selectedChunk.id, word.id); } }} - placeholder="G4102 or faith" + placeholder="G4102, 4102, or faith (Greek)" className="mt-2 block w-full rounded-2xl border border-slate-300 bg-slate-50 px-3 py-2 text-slate-900 shadow-sm focus:border-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-200" /> diff --git a/src/App.test.jsx b/src/App.test.jsx index 5b53a03..302dd79 100644 --- a/src/App.test.jsx +++ b/src/App.test.jsx @@ -289,18 +289,18 @@ async function goToStudyAndAddGreekWord(fetchMock) { fireEvent.click(screen.getByRole('button', { name: /begin studying/i })); await screen.findByText(/chunk editor/i); fireEvent.click(screen.getByRole('button', { name: /add greek word/i })); - await screen.findByPlaceholderText(/G4102 or faith/i); + await screen.findByPlaceholderText(/G4102, 4102/i); } describe('Greek word lookup', () => { test('adds a Greek word entry form', async () => { await goToStudyAndAddGreekWord(); - expect(screen.getByPlaceholderText(/G4102 or faith/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/G4102, 4102/i)).toBeInTheDocument(); }); test('populates fields after a successful lookup', async () => { await goToStudyAndAddGreekWord(buildFetchMock({ greekData: mockGreekDefinition })); - fireEvent.change(screen.getByPlaceholderText(/G4102 or faith/i), { target: { value: 'G4102' } }); + fireEvent.change(screen.getByPlaceholderText(/G4102, 4102/i), { target: { value: 'G4102' } }); fireEvent.click(screen.getByRole('button', { name: /look up/i })); await screen.findByDisplayValue('πίστις'); expect(screen.getByDisplayValue('pistis')).toBeInTheDocument(); @@ -309,7 +309,7 @@ describe('Greek word lookup', () => { test('shows "No definition found." when the API returns an empty array', async () => { await goToStudyAndAddGreekWord(buildFetchMock({ greekData: [] })); - fireEvent.change(screen.getByPlaceholderText(/G4102 or faith/i), { target: { value: 'G4102' } }); + fireEvent.change(screen.getByPlaceholderText(/G4102, 4102/i), { target: { value: 'G4102' } }); fireEvent.click(screen.getByRole('button', { name: /look up/i })); await screen.findByDisplayValue('No definition found.'); }); @@ -326,7 +326,7 @@ describe('Greek word lookup', () => { return Promise.reject(new Error('Network error')); }), ); - fireEvent.change(screen.getByPlaceholderText(/G4102 or faith/i), { target: { value: 'G4102' } }); + fireEvent.change(screen.getByPlaceholderText(/G4102, 4102/i), { target: { value: 'G4102' } }); fireEvent.click(screen.getByRole('button', { name: /look up/i })); await screen.findByDisplayValue('Lookup failed.'); }); @@ -343,10 +343,10 @@ describe('Greek word lookup', () => { test('removes a Greek word entry', async () => { await goToStudyAndAddGreekWord(); - expect(screen.getByPlaceholderText(/G4102 or faith/i)).toBeInTheDocument(); + expect(screen.getByPlaceholderText(/G4102, 4102/i)).toBeInTheDocument(); fireEvent.click(screen.getByRole('button', { name: /^delete$/i })); await waitFor(() => { - expect(screen.queryByPlaceholderText(/G4102 or faith/i)).not.toBeInTheDocument(); + expect(screen.queryByPlaceholderText(/G4102, 4102/i)).not.toBeInTheDocument(); }); }); });