import type { Universe } from '@ephox/boss'; import { Optional } from '@ephox/katamari'; import { Descent } from '@ephox/phoenix'; import { ZoneViewports } from '../api/general/ZoneViewports'; import * as Clustering from '../words/Clustering'; import { WordDecision, type WordDecisionItem } from '../words/WordDecision'; import { LanguageZones } from './LanguageZones'; import * as TextZones from './TextZones'; import type { Zone } from './Zones'; // Since we support these formats both mixed cases and either - and _ we need to normalize the code const normalizeCode = (lang: string): string => lang.toLowerCase().replace(/_/g, '-'); const isEqualCode = (lang: string, onlyLang: string): boolean => normalizeCode(lang) === normalizeCode(onlyLang); // a Text Zone enforces a language, and returns Optional.some only if a single zone was identified // with that language. const filterZone = (zone: Zone, onlyLang: string): Optional> => { return isEqualCode(zone.lang, onlyLang) ? Optional.some(zone) : Optional.none>(); }; const fromBoundedWith = (universe: Universe, left: E, right: E, envLang: string, onlyLang: string, transform: (universe: Universe, item: E) => WordDecisionItem): Optional> => { const output = TextZones.fromBoundedWith(universe, left, right, envLang, transform, ZoneViewports.anything()); const zones = output.zones; return zones.length === 1 ? filterZone(zones[0], onlyLang) : Optional.none>(); }; const fromBounded = (universe: Universe, left: E, right: E, envLang: string, onlyLang: string): Optional> => { return fromBoundedWith(universe, left, right, envLang, onlyLang, WordDecision.detail); }; const fromRange = (universe: Universe, start: E, finish: E, envLang: string, onlyLang: string): Optional> => { const isLanguageBoundary = LanguageZones.strictBounder(envLang, onlyLang); const edges = Clustering.getEdges(universe, start, finish, isLanguageBoundary); const transform = TextZones.transformEdges(edges.left, edges.right); return fromBoundedWith(universe, edges.left.item, edges.right.item, envLang, onlyLang, transform); }; const fromInline = (universe: Universe, element: E, envLang: string, onlyLang: string): Optional> => { const isLanguageBoundary = LanguageZones.strictBounder(envLang, onlyLang); const edges = Clustering.getEdges(universe, element, element, isLanguageBoundary); const transform = TextZones.transformEdges(edges.left, edges.right); return edges.isEmpty ? scour(universe, element, envLang, onlyLang) : fromBoundedWith(universe, edges.left.item, edges.right.item, envLang, onlyLang, transform); }; const scour = (universe: Universe, element: E, envLang: string, onlyLang: string): Optional> => { const lastOffset = universe.property().isText(element) ? universe.property().getText(element).length : universe.property().children(element).length; const left = Descent.toLeaf(universe, element, 0); const right = Descent.toLeaf(universe, element, lastOffset); return fromBounded(universe, left.element, right.element, envLang, onlyLang); }; const empty = (): Optional> => { return Optional.none>(); }; export { fromRange, fromBounded, fromInline, empty };