Files
scrabble-game/ui/src/lib/checkword.ts
T
Ilia Denisov 8e0d7f9e17
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 48s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 56s
feat(ui): external dictionary lookup link on the word-check tool
When a checked word is found, show a 'look it up' text link beside the
complaint button that opens an external reference dictionary in a new tab:
gramota.ru for the Russian variants, scrabblewordfinder.org for English
(word lower-cased and percent-encoded). The link hides for a word that is
not found. Inside Telegram it routes through the Mini App SDK's openLink, so
Telegram opens it directly instead of the WebView's 'open this link?'
confirmation; in a browser the anchor's own target=_blank handles it.

Relabel the complaint button to 'Возражаю' (ru); English stays 'Disagree'.
2026-06-15 18:14:19 +02:00

47 lines
1.9 KiB
TypeScript

// Pure helpers for the in-game "check a word" panel: input sanitising and the gate on
// when a check may be sent. Kept separate from Game.svelte so the constraints (the
// variant alphabet, the length bounds, the answered-word cache and the cool-down
// throttle) are unit-testable and stay in lockstep with the UI.
import type { Variant } from './model';
/** The longest word that fits on a standard 15-cell board line. */
export const MAX_WORD_LEN = 15;
/** The shortest word worth checking. */
export const MIN_WORD_LEN = 2;
/**
* sanitizeCheckWord upper-cases the raw input and keeps only characters of the active
* variant's alphabet, capped at MAX_WORD_LEN — so the field can never hold something the
* dictionary could not contain.
*/
export function sanitizeCheckWord(raw: string, alphabet: string[]): string {
const allowed = new Set(alphabet);
return Array.from(raw.toUpperCase())
.filter((ch) => allowed.has(ch))
.slice(0, MAX_WORD_LEN)
.join('');
}
/**
* canCheckWord gates the Check action: the trimmed word must be of valid length, must not
* have been answered already (cached), and must not fall inside the cool-down window.
*/
export function canCheckWord(word: string, alreadyChecked: boolean, cooling: boolean): boolean {
const w = word.trim();
return w.length >= MIN_WORD_LEN && w.length <= MAX_WORD_LEN && !alreadyChecked && !cooling;
}
/**
* dictionaryLookupUrl returns the external reference-dictionary search URL for a checked word
* in the given variant: gramota.ru for the Russian variants (scrabble_ru, erudit_ru) and
* scrabblewordfinder.org for English. The word is lower-cased and percent-encoded, so a
* Cyrillic query travels safely.
*/
export function dictionaryLookupUrl(word: string, variant: Variant): string {
const w = encodeURIComponent(word.toLowerCase());
return variant.endsWith('_ru')
? `https://gramota.ru/poisk?query=${w}`
: `https://scrabblewordfinder.org/dictionary/${w}`;
}