34 lines
1.6 KiB
TypeScript
34 lines
1.6 KiB
TypeScript
import { textIndex, processingPolicy } from "./text-processing.js";
|
|||
|
|
export type VariantSection = { start: number; end: number; text?: string };
|
||
|
|
export type TextVariant = {
|
||
|
|
source: string;
|
||
|
|
sourceRevision: string;
|
||
|
|
difficulty: string;
|
||
|
|
sections: VariantSection[];
|
||
|
|
};
|
||
|
|
/** Canonical ranges never change when an earlier section expands or contracts.
|
||
|
|
* Output offsets are appended in order, so annotations on ready text stay valid. */
|
||
|
|
export function createVariant(source: string, sourceRevision: string, difficulty: string): TextVariant {
|
||
|
|
const policy = processingPolicy({ chunkChars: 1400 });
|
||
|
|
return { source, sourceRevision, difficulty, sections: textIndex(source).chunks(policy.chunkChars) };
|
||
|
|
}
|
||
|
|
export function parseVariant(raw: string): TextVariant | null {
|
||
|
|
const value = JSON.parse(raw);
|
||
|
|
return typeof value.source === "string" && Array.isArray(value.sections) ? value : null;
|
||
|
|
}
|
||
|
|
export function nextSection(variant: TextVariant) {
|
||
|
|
return variant.sections.find((section) => section.text === undefined);
|
||
|
|
}
|
||
|
|
export function applySection(variant: TextVariant, start: number, end: number, text: string) {
|
||
|
|
const section = nextSection(variant);
|
||
|
|
if (!section || section.start !== start || section.end !== end) throw Error("Variant section changed");
|
||
|
|
if (!text.trim() || text.length > 12000) throw Error("Invalid variant text");
|
||
|
|
return { ...variant, sections: variant.sections.map((item) => (item === section ? { ...item, text } : item)) };
|
||
|
|
}
|
||
|
|
export function variantText(variant: TextVariant) {
|
||
|
|
return variant.sections
|
||
|
|
.filter((section) => section.text !== undefined)
|
||
|
|
.map((section) => section.text)
|
||
|
|
.join("\n\n");
|
||
|
|
}
|