Files
AfiS-Aktensystem/web/lib/redaction.ts
T
Harlekin e0b03ea336
Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
Gedankenstriche durch Bindestriche ersetzt
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die
README; an keiner Stelle Bedeutung, nur Zeichensetzung.
2026-09-04 08:54:52 +02:00

114 lines
3.5 KiB
TypeScript

/**
* 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)
)
}