- Compare translations can highlight the words that differ from the first translation (toggle remembered). - A copy of your notes is kept the first time commentary is shown; "First draft, before commentary" under More shows what changed since. Saving it doesn't count as editing the chunk. - Notes on single verses: a pencil next to each verse in Study, and a note line under each verse in Compare. They merge per verse across devices. - Pasting 15+ words into a note asks "Is this from AI?"; "I wrote it" counts the words as yours again. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
57 lines
2.7 KiB
JavaScript
57 lines
2.7 KiB
JavaScript
// Comparing translations of one chunk: which chapter(s) and verses to fetch, and the verses lined up by number.
|
|
import { getChunkVerseEntries } from './bibleUtils.jsx';
|
|
import { diffVerses } from './textDiff.js';
|
|
|
|
// -> [{ bookAbbrev, book, chapter, from, to }]: one entry per chapter the chunk covers (two when it spills over).
|
|
export function translationSpans(project, chapterIndex, chunk) {
|
|
const spans = [];
|
|
for (const v of getChunkVerseEntries(project, chapterIndex, chunk)) {
|
|
const last = spans[spans.length - 1];
|
|
if (last && last.chapter === v.chapter && last.book === v.book) { last.to = Math.max(last.to, v.number); last.from = Math.min(last.from, v.number); continue; }
|
|
const chapter = project.chapters.find((c) => c.book === v.book && String(c.chapter) === String(v.chapter));
|
|
spans.push({ bookAbbrev: chapter?.bookAbbrev, book: v.book, chapter: v.chapter, from: v.number, to: v.number });
|
|
}
|
|
return spans;
|
|
}
|
|
|
|
// byTranslation: { BSB: { verses: [{ number, text }] }, … } for the chapters in `spans`; `loaded` is keyed by
|
|
// `${chapter}` inside each translation. -> [{ key, label, texts: { BSB: 'text' | null } }] one row per verse, in order.
|
|
export function alignVerses(spans, byTranslation) {
|
|
const rows = [];
|
|
for (const span of spans) {
|
|
for (let n = span.from; n <= span.to; n += 1) {
|
|
const texts = {};
|
|
for (const [id, chapters] of Object.entries(byTranslation)) {
|
|
texts[id] = chapters?.[`${span.book}|${span.chapter}`]?.find((v) => v.number === n)?.text ?? null;
|
|
}
|
|
rows.push({ key: `${span.book}${span.chapter}:${n}`, noteKey: `${span.chapter}:${n}`, label: spans.length > 1 ? `${span.chapter}:${n}` : String(n), texts });
|
|
}
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
// For one verse in several translations: which words in each text the others lack, measured against the first
|
|
// translation (and the first against the second). -> { BSB: [{ start, end }], NET: [...] }
|
|
export function differencesFor(texts, ids) {
|
|
const out = Object.fromEntries(ids.map((id) => [id, []]));
|
|
const present = ids.filter((id) => texts[id]);
|
|
if (present.length < 2) return out;
|
|
const [base, ...rest] = present;
|
|
out[base] = diffVerses(texts[base], texts[rest[0]]).a;
|
|
for (const id of rest) out[id] = diffVerses(texts[base], texts[id]).b;
|
|
return out;
|
|
}
|
|
|
|
// Cuts a text at the ranges -> [{ text, mark }] so a view can wrap the marked parts.
|
|
export function markRanges(text, ranges) {
|
|
const parts = [];
|
|
let at = 0;
|
|
for (const r of [...(ranges ?? [])].sort((a, b) => a.start - b.start)) {
|
|
if (r.start > at) parts.push({ text: text.slice(at, r.start), mark: false });
|
|
parts.push({ text: text.slice(r.start, r.end), mark: true });
|
|
at = r.end;
|
|
}
|
|
if (at < text.length) parts.push({ text: text.slice(at), mark: false });
|
|
return parts;
|
|
}
|