66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
import { Directive, ElementRef, Input, OnChanges, OnDestroy } from '@angular/core';
|
|
|
|
const OBFUSCATION_CHARS =
|
|
'!@#$%^&*()_+-=[]{}|;:,.<>?/~`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
|
/**
|
|
* Apply as `[mmObfuscated]="style.obfuscated"` on an element whose textContent is the
|
|
* text to obfuscate. While active, cycles every character (except spaces) through random
|
|
* glyphs, mimicking Minecraft's "magic" formatting. The real text is preserved in an
|
|
* aria-label so it stays accessible to screen readers, and is restored to the DOM when
|
|
* obfuscation is turned off.
|
|
*/
|
|
@Directive({
|
|
selector: '[mmObfuscated]',
|
|
standalone: true,
|
|
})
|
|
export class ObfuscatedDirective implements OnChanges, OnDestroy {
|
|
@Input('mmObfuscated') active = false;
|
|
|
|
private original: string | null = null;
|
|
private intervalId: ReturnType<typeof setInterval> | undefined;
|
|
|
|
constructor(private readonly el: ElementRef<HTMLElement>) {}
|
|
|
|
ngOnChanges(): void {
|
|
if (this.original === null) {
|
|
this.original = this.el.nativeElement.textContent ?? '';
|
|
this.el.nativeElement.setAttribute('aria-label', this.original);
|
|
}
|
|
this.sync();
|
|
}
|
|
|
|
ngOnDestroy(): void {
|
|
this.stop();
|
|
}
|
|
|
|
private sync(): void {
|
|
if (this.active && !this.intervalId) {
|
|
this.start();
|
|
} else if (!this.active && this.intervalId) {
|
|
this.stop();
|
|
}
|
|
}
|
|
|
|
private start(): void {
|
|
this.intervalId = setInterval(() => {
|
|
const text = this.original ?? '';
|
|
const scrambled = text
|
|
.split('')
|
|
.map((ch) => (ch === ' ' ? ' ' : OBFUSCATION_CHARS[Math.floor(Math.random() * OBFUSCATION_CHARS.length)]))
|
|
.join('');
|
|
this.el.nativeElement.textContent = scrambled;
|
|
}, 50);
|
|
}
|
|
|
|
private stop(): void {
|
|
if (this.intervalId) {
|
|
clearInterval(this.intervalId);
|
|
this.intervalId = undefined;
|
|
}
|
|
if (this.original !== null) {
|
|
this.el.nativeElement.textContent = this.original;
|
|
}
|
|
}
|
|
}
|