Preact: Add highlight, receivepopup etc (#2406)
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run

This commit is contained in:
Aurastic 2025-05-07 14:40:03 +05:30 committed by GitHub
parent 45f8880807
commit 33f78c413f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 181 additions and 24 deletions

View File

@ -53,7 +53,7 @@ export class BattleLog {
* * 1 = player 2: "Red sent out Pikachu!" "Eevee used Tackle!"
*/
perspective: -1 | 0 | 1 = -1;
getHighlight: ((message: string, name: string) => boolean) | null = null;
getHighlight: ((line: Args) => boolean) | null = null;
constructor(elem: HTMLDivElement, scene?: BattleScene | null, innerElem?: HTMLDivElement) {
this.elem = elem;
@ -177,7 +177,7 @@ export class BattleLog {
}
timestampHtml = `<small class="gray">[${components.map(x => x < 10 ? `0${x}` : x).join(':')}] </small>`;
}
const isHighlighted = window.app?.rooms?.[battle!.roomid].getHighlight(message) || this.getHighlight?.(message, name);
const isHighlighted = window.app?.rooms?.[battle!.roomid].getHighlight(message) || this.getHighlight?.(args);
[divClass, divHTML, noNotify] = this.parseChatMessage(message, name, timestampHtml, isHighlighted);
if (!noNotify && isHighlighted) {
const notifyTitle = "Mentioned by " + name + " in " + (battle?.roomid || '');
@ -266,6 +266,7 @@ export class BattleLog {
case 'unlink': {
// |unlink| is deprecated in favor of |hidelines|
if (window.PS.prefs.nounlink) return;
const user = toID(args[2]) || toID(args[1]);
this.unlinkChatFrom(user);
if (args[2]) {
@ -276,6 +277,7 @@ export class BattleLog {
}
case 'hidelines': {
if (window.PS.prefs.nounlink) return;
const user = toID(args[2]);
this.unlinkChatFrom(user);
if (args[1] !== 'unlink') {

View File

@ -12,7 +12,7 @@
import { PSConnection, PSLoginServer } from './client-connection';
import { PSModel, PSStreamModel } from './client-core';
import type { PSRoomPanel, PSRouter } from './panels';
import type { ChatRoom } from './panel-chat';
import { ChatRoom } from './panel-chat';
import type { MainMenuRoom } from './panel-mainmenu';
import { Dex, toID, type ID } from './battle-dex';
import { BattleTextParser, type Args } from './battle-text-parser';
@ -78,6 +78,7 @@ class PSPrefs extends PSStreamModel<string | null> {
hidelinks: false,
hideinterstice: true,
};
nounlink: boolean | null = null;
/* Battle preferences */
ignorenicks: boolean | null = null;
@ -118,6 +119,9 @@ class PSPrefs extends PSStreamModel<string | null> {
afd: boolean | 'sprites' = false;
highlights: Record<string, string[]> | null = null;
logtimes: Record<string, { [roomid: RoomID]: number }> | null = null;
// PREFS END HERE
storageEngine: 'localStorage' | 'iframeLocalStorage' | '' = '';
@ -830,6 +834,14 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
PS.update();
}
autoDismissNotifications() {
let room = PS.rooms[this.id] as ChatRoom;
if (room.lastMessageTime) {
// Mark chat messages as read to avoid double-notifying on reload
let lastMessageDates = PS.prefs.logtimes || {};
if (!lastMessageDates[PS.server.id]) lastMessageDates[PS.server.id] = {};
lastMessageDates[PS.server.id][room.id] = room.lastMessageTime || 0;
PS.prefs.set('logtimes', lastMessageDates);
}
this.notifications = this.notifications.filter(notification => notification.noAutoDismiss);
this.isSubtleNotifying = false;
}
@ -926,6 +938,9 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
this.send(target);
PS.leave(this.id);
},
'receivepopup'(target) {
PS.alert(target);
},
'inopener,inparent'(target) {
// do this command in the popup opener
let room = this.getParent();
@ -1187,10 +1202,103 @@ export class PSRoom extends PSStreamModel<Args | null> implements RoomOptions {
}
this.add("||All PM windows cleared and closed.");
},
'unpackhidden'() {
PS.prefs.set('nounlink', true);
this.add('||Locked/banned users\' chat messages: ON');
},
'packhidden'() {
PS.prefs.set('nounlink', false);
this.add('||Locked/banned users\' chat messages: HIDDEN');
},
'hl,highlight'(target) {
let highlights = PS.prefs.highlights || {};
if (target.includes(' ')) {
let targets = target.split(' ');
let subCmd = targets[0];
targets = targets.slice(1).join(' ').match(/([^,]+?({\d*,\d*})?)+/g) as string[];
// trim the targets to be safe
for (let i = 0, len = targets.length; i < len; i++) {
targets[i] = targets[i].replace(/\n/g, '').trim();
}
switch (subCmd) {
case 'add': case 'roomadd': {
let key = subCmd === 'roomadd' ? (PS.server.id + '#' + this.id) : 'global';
let highlightList = highlights[key] || [];
for (let i = 0, len = targets.length; i < len; i++) {
if (!targets[i]) continue;
if (/[\\^$*+?()|{}[\]]/.test(targets[i])) {
// Catch any errors thrown by newly added regular expressions so they don't break the entire highlight list
try {
new RegExp(targets[i]);
} catch (e: any) {
return this.add(`|error|${(e.message.substr(0, 28) === 'Invalid regular expression: ' ? e.message : 'Invalid regular expression: /' + targets[i] + '/: ' + e.message)}`);
}
}
if (highlightList.includes(targets[i])) {
return this.add(`|error|${targets[i]} is already on your highlights list.`);
}
}
highlights[key] = highlightList.concat(targets);
this.add(`||Now highlighting on ${(key === 'global' ? "(everywhere): " : "(in " + key + "): ")} ${highlights[key].join(', ')}`);
// We update the regex
ChatRoom.updateHighlightRegExp(highlights);
break;
}
case 'delete': case 'roomdelete': {
let key = subCmd === 'roomdelete' ? (PS.server.id + '#' + this.id) : 'global';
let highlightList = highlights[key] || [];
let newHls: string[] = [];
for (let i = 0, len = highlightList.length; i < len; i++) {
if (!targets.includes(highlightList[i])) {
newHls.push(highlightList[i]);
}
}
highlights[key] = newHls;
this.add(`||Now highlighting on ${(key === 'global' ? "(everywhere): " : "(in " + key + "): ")} ${highlights[key].join(', ')}`);
// We update the regex
ChatRoom.updateHighlightRegExp(highlights);
break;
}
default:
// Wrong command
this.add('|error|Invalid /highlight command.');
this.handleSend('/help highlight'); // show help
return false;
}
PS.prefs.set('highlights', highlights);
} else {
if (['clear', 'roomclear', 'clearall'].includes(target)) {
let key = (target === 'roomclear' ? (PS.server.id + '#' + this.id) : (target === 'clearall' ? '' : 'global'));
if (key) {
highlights[key] = [];
this.add(`||All highlights (${(key === 'global' ? "everywhere" : "in " + key)}) cleared.`);
ChatRoom.updateHighlightRegExp(highlights);
} else {
PS.prefs.set('highlights', null);
this.add("||All highlights (in all rooms and globally) cleared.");
ChatRoom.updateHighlightRegExp({});
}
} else if (['show', 'list', 'roomshow', 'roomlist'].includes(target)) {
// Shows a list of the current highlighting words
let key = target.startsWith('room') ? (PS.server.id + '#' + this.id) : 'global';
if (highlights[key] && highlights[key].length > 0) {
this.add(`||Current highlight list ${(key === 'global' ? "(everywhere): " : "(in " + key + "): ")}${highlights[key].join(", ")}`);
} else {
this.add(`||Your highlight list${(key === 'global' ? '' : ' in ' + key)} is empty.`);
}
} else {
// Wrong command
this.add('|error|Invalid /highlight command.');
this.handleSend('/help highlight'); // show help
return false;
}
}
return false;
},
'senddirect'(target) {
this.sendDirect(target);
},
'help'(target) {
'h,help'(target) {
switch (toID(target)) {
case 'chal':
case 'chall':

View File

@ -47,11 +47,13 @@ export class ChatRoom extends PSRoom {
log: BattleLog | null = null;
tour: ChatTournament | null = null;
lastMessage: Args | null = null;
lastMessageTime: number | null = null;
joinLeave: { join: string[], leave: string[], messageId: string } | null = null;
/** in order from least to most recent */
userActivity: string[] = [];
timeOffset = 0;
static highlightRegExp: Record<string, RegExp | null> | null = null;
constructor(options: RoomOptions) {
super(options);
@ -179,21 +181,20 @@ export class ChatRoom extends PSRoom {
this.title = `[DM] ${nameWithGroup.trim()}`;
}
}
handleHighlight = (message: string, name: string) => {
if (!PS.prefs.noselfhighlight && PS.user.nameRegExp?.test(message)) {
this.notify({
title: `Mentioned by ${name} in ${this.id}`,
body: `"${message}"`,
id: 'highlight',
});
return true;
static getHighlight(message: string, roomid: string) {
let highlights = PS.prefs.highlights || {};
if (Array.isArray(highlights)) {
highlights = { global: highlights };
// Migrate from the old highlight system
PS.prefs.set('highlights', highlights);
}
if (!PS.prefs.noselfhighlight && PS.user.nameRegExp) {
if (PS.user.nameRegExp?.test(message)) return true;
}
/*
// TODO!
if (!this.highlightRegExp) {
try {
//this.updateHighlightRegExp(highlights);
} catch (e) {
this.updateHighlightRegExp(highlights);
} catch {
// If the expression above is not a regexp, we'll get here.
// Don't throw an exception because that would prevent the chat
// message from showing up, or, when the lobby is initialising,
@ -201,14 +202,60 @@ export class ChatRoom extends PSRoom {
return false;
}
}
var id = PS.server.id + '#' + this.id;
var globalHighlightsRegExp = this.highlightRegExp['global'];
var roomHighlightsRegExp = this.highlightRegExp[id];
return (((globalHighlightsRegExp &&
globalHighlightsRegExp.test(message)) ||
(roomHighlightsRegExp && roomHighlightsRegExp.test(message))));
*/
const id = PS.server.id + '#' + roomid;
const globalHighlightsRegExp = this.highlightRegExp?.['global'];
const roomHighlightsRegExp = this.highlightRegExp?.[id];
return (((globalHighlightsRegExp?.test(message)) || (roomHighlightsRegExp?.test(message))));
}
static updateHighlightRegExp(highlights: Record<string, string[]>) {
// Enforce boundary for match sides, if a letter on match side is
// a word character. For example, regular expression "a" matches
// "a", but not "abc", while regular expression "!" matches
// "!" and "!abc".
this.highlightRegExp = {};
for (let i in highlights) {
if (!highlights[i].length) {
this.highlightRegExp[i] = null;
continue;
}
this.highlightRegExp[i] = new RegExp('(?:\\b|(?!\\w))(?:' + highlights[i].join('|') + ')(?:\\b|(?!\\w))', 'i');
}
}
handleHighlight = (args: Args) => {
let name;
let message;
let msgTime = 0;
if (args[0] === 'c:') {
msgTime = parseInt(args[1]);
name = args[2];
message = args[3];
} else {
name = args[1];
message = args[2];
}
let lastMessageDates = Dex.prefs('logtimes') || (PS.prefs.set('logtimes', {}), Dex.prefs('logtimes'));
if (!lastMessageDates[PS.server.id]) lastMessageDates[PS.server.id] = {};
let lastMessageDate = lastMessageDates[PS.server.id][this.id] || 0;
// because the time offset to the server can vary slightly, subtract it to not have it affect comparisons between dates
let serverMsgTime = msgTime - (this.timeOffset || 0);
let mayNotify = serverMsgTime > lastMessageDate && name !== PS.user.userid;
if (PS.isVisible(this)) {
this.lastMessageTime = null;
lastMessageDates[PS.server.id][this.id] = serverMsgTime;
PS.prefs.set('logtimes', lastMessageDates);
} else {
// To be saved on focus
let lastMessageTime = this.lastMessageTime || 0;
if (lastMessageTime < serverMsgTime) this.lastMessageTime = serverMsgTime;
}
if (ChatRoom.getHighlight(message, this.id)) {
if (mayNotify) this.notify({
title: `Mentioned by ${name} in ${this.id}`,
body: `"${message}"`,
id: 'highlight',
});
return true;
}
return false;
};
override clientCommands = this.parseClientCommands({