Compare commits
2 Commits
4b6a491919
...
0233958118
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0233958118 | ||
|
|
357948f134 |
|
|
@ -10,6 +10,11 @@
|
||||||
<section class="darkmodeSection">
|
<section class="darkmodeSection">
|
||||||
@for (message of messages(); track message.timestamp) {
|
@for (message of messages(); track message.timestamp) {
|
||||||
<p>
|
<p>
|
||||||
|
@if (message.notBlocked) {
|
||||||
|
[{{ message.server }}]
|
||||||
|
} @else {
|
||||||
|
<span style="color: red">[{{ message.server }}] </span>
|
||||||
|
}
|
||||||
<mini-message [node]="message.messageJson"></mini-message>
|
<mini-message [node]="message.messageJson"></mini-message>
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import {MiniMessageClickEvent, MiniMessageComponent, MiniMessageHoverEvent} from './mini-message.types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What actually arrives over the wire can be looser than the strict MiniMessageComponent
|
||||||
|
* type: some serializers emit bare strings inside `extra` instead of {text: "..."}, and
|
||||||
|
* some use snake_case event keys (hover_event/click_event) with different subfield names
|
||||||
|
* (e.g. click_event.command instead of clickEvent.value). This function tolerates both
|
||||||
|
* and always returns a clean, strictly-typed tree.
|
||||||
|
*
|
||||||
|
* Call this once, right after JSON.parse, before the data enters your app's state:
|
||||||
|
*
|
||||||
|
* messageJson: normalizeComponent(JSON.parse(m.messageJson))
|
||||||
|
*/
|
||||||
|
export function normalizeComponent(input: unknown): MiniMessageComponent {
|
||||||
|
if (typeof input === 'string') {
|
||||||
|
return {text: input};
|
||||||
|
}
|
||||||
|
if (input === null || typeof input !== 'object') {
|
||||||
|
return {text: ''};
|
||||||
|
}
|
||||||
|
|
||||||
|
const raw = input as Record<string, any>;
|
||||||
|
const node: MiniMessageComponent = {...(raw as MiniMessageComponent)};
|
||||||
|
|
||||||
|
node.clickEvent = normalizeClickEvent(raw['clickEvent'] ?? raw['click_event']);
|
||||||
|
node.hoverEvent = normalizeHoverEvent(raw['hoverEvent'] ?? raw['hover_event']);
|
||||||
|
delete node['click_event' as keyof MiniMessageComponent];
|
||||||
|
delete node['hover_event' as keyof MiniMessageComponent];
|
||||||
|
|
||||||
|
if (Array.isArray(raw['extra'])) {
|
||||||
|
node.extra = raw['extra'].map((child: unknown) => normalizeComponent(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
// auditShape(node);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeClickEvent(raw: Record<string, any> | undefined): MiniMessageClickEvent | undefined {
|
||||||
|
if (!raw) return undefined;
|
||||||
|
const value = raw['value'] ?? raw['command'] ?? raw['url'] ?? raw['page'] ?? '';
|
||||||
|
return {action: raw['action'], value: String(value)};
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeHoverEvent(raw: Record<string, any> | undefined): MiniMessageHoverEvent | undefined {
|
||||||
|
if (!raw) return undefined;
|
||||||
|
const source = raw['value'] ?? raw['contents'];
|
||||||
|
return {
|
||||||
|
action: raw['action'],
|
||||||
|
value: typeof source === 'string' ? source : source ? normalizeComponent(source) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function auditShape(node: unknown, path = 'root'): void {
|
||||||
|
if (typeof node === 'string') {
|
||||||
|
console.warn(`Bare string in tree at ${path}:`, node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const n = node as Record<string, unknown>;
|
||||||
|
if (n['hover_event'] || n['click_event']) {
|
||||||
|
console.warn(`snake_case event key at ${path}`, n);
|
||||||
|
}
|
||||||
|
(n['extra'] as unknown[] | undefined)?.forEach((c, i) => auditShape(c, `${path}.extra[${i}]`));
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ import {EventSourcePolyfill} from 'event-source-polyfill';
|
||||||
import {ChatMessage} from '@pages/altitude/chat/objects/chat-message.object';
|
import {ChatMessage} from '@pages/altitude/chat/objects/chat-message.object';
|
||||||
import {MiniMessageComponent} from '@pages/altitude/chat/mini-message/mini-message.types';
|
import {MiniMessageComponent} from '@pages/altitude/chat/mini-message/mini-message.types';
|
||||||
import {RawChatMessage} from '@pages/altitude/chat/objects/raw-chat-message.object';
|
import {RawChatMessage} from '@pages/altitude/chat/objects/raw-chat-message.object';
|
||||||
|
import {normalizeComponent} from '@pages/altitude/chat/mini-message/normalize.util';
|
||||||
|
|
||||||
export interface ChatEvent {
|
export interface ChatEvent {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
@ -45,7 +46,7 @@ export class ChatService implements OnDestroy {
|
||||||
const raw = JSON.parse(event.data) as RawChatMessage[];
|
const raw = JSON.parse(event.data) as RawChatMessage[];
|
||||||
const messages: ChatMessage[] = raw.map((m) => ({
|
const messages: ChatMessage[] = raw.map((m) => ({
|
||||||
...m,
|
...m,
|
||||||
messageJson: JSON.parse(m.messageJson) as MiniMessageComponent,
|
messageJson: normalizeComponent(JSON.parse(m.messageJson)),
|
||||||
}));
|
}));
|
||||||
this._messages.update((old) => [...old, ...messages]);
|
this._messages.update((old) => [...old, ...messages]);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user