Introduce MiniMessage rendering system for structured chat formatting and interaction
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user