/** * Schwärzung im Freitext. * * Im Editor markiert man einen Bereich und wählt eine Stufe. Gespeichert wird * als Auszeichnung: * * Der Hinweis kam von [[c4]]Wachmann Perreira, Sektor B[[/c4]] am 27.08. * * Der Server ersetzt den Inhalt für niedrigere Stufen. **Der echte Wert * verlässt den Server nicht** - deshalb gibt diese Datei Segmente zurück und * nicht etwa den vollen Text mit einem Sichtbarkeits-Flag. Was der Client * ausblenden müsste, hat er schon. * * Die Länge wird auf ein festes Raster gerundet. Ein Balken exakter Länge * verriete sonst, wie lang der Name war, und bei kurzen Einschüben ist das * genug, um zu raten. Siehe docs/05-akten-und-vorlagen.md. */ /** Auf dieses Vielfache wird die Länge eines Balkens gerundet. */ const LENGTH_GRID = 8 const MARKUP = /\[\[c([0-5])\]\]([\s\S]*?)\[\[\/c\1\]\]/g export type Segment = | { kind: 'text'; text: string } | { kind: 'redacted'; requiredLevel: number; blockLength: number } /** Auf das Raster gerundete Länge, mindestens ein Block. */ export function barLength(rawLength: number): number { return Math.max(LENGTH_GRID, Math.ceil(rawLength / LENGTH_GRID) * LENGTH_GRID) } /** * Zerlegt einen Text in Segmente und wendet dabei die Stufe des Lesers an. * Bereiche über seiner Stufe kommen ohne ihren Inhalt zurück. */ export function redactForLevel(source: string, viewerLevel: number): Segment[] { const segments: Segment[] = [] let cursor = 0 MARKUP.lastIndex = 0 let match: RegExpExecArray | null while ((match = MARKUP.exec(source)) !== null) { if (match.index > cursor) { segments.push({ kind: 'text', text: source.slice(cursor, match.index) }) } const requiredLevel = Number(match[1]) const content = match[2] ?? '' if (viewerLevel >= requiredLevel) { segments.push({ kind: 'text', text: content }) } else { segments.push({ kind: 'redacted', requiredLevel, blockLength: barLength(content.length), }) } cursor = match.index + match[0].length } if (cursor < source.length) { segments.push({ kind: 'text', text: source.slice(cursor) }) } return segments } /** * Die höchste Stufe, die in einem Text ausgezeichnet ist. Damit lässt sich an * einem Eintrag anzeigen, dass er geschwärzte Stellen enthält, ohne den Text * zu übertragen. */ export function highestRedactionLevel(source: string): number { let highest = 0 MARKUP.lastIndex = 0 let match: RegExpExecArray | null while ((match = MARKUP.exec(source)) !== null) { highest = Math.max(highest, Number(match[1])) } return highest } /** * Entfernt die Auszeichnung, behält den Inhalt. Für Leser, die alles sehen * dürfen, und für den Volltextindex hoher Stufen. */ export function stripMarkup(source: string): string { return source.replace(MARKUP, (_full, _level, content: string) => content) } /** * Entfernt ausgezeichnete Bereiche samt Inhalt. Genau das tut auch die * Funktion `strip_redactions` in der Migration, bevor der Suchvektor gefüllt * wird - beide müssen dasselbe Ergebnis liefern. */ export function stripRedacted(source: string): string { return source.replace(MARKUP, ' ') } /** Fügt eine Auszeichnung um einen markierten Bereich ein. */ export function markRedaction(source: string, start: number, end: number, level: number): string { if (start >= end || level < 0 || level > 5) return source return ( source.slice(0, start) + `[[c${level}]]` + source.slice(start, end) + `[[/c${level}]]` + source.slice(end) ) }