mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
🆙 Mention Now in UI-Config and UITexts and not hardcoded
This commit is contained in:
@@ -640,6 +640,8 @@
|
|||||||
'wheel.extra': 'Extra spins: %count%',
|
'wheel.extra': 'Extra spins: %count%',
|
||||||
'wheel.spin': 'SPIN',
|
'wheel.spin': 'SPIN',
|
||||||
'wheel.buy': 'Buy spin',
|
'wheel.buy': 'Buy spin',
|
||||||
|
'wheel.settings': 'Settings',
|
||||||
|
'wheel.settings.title': 'Wheel of Fortune Settings',
|
||||||
'wheel.winners': 'Latest winners',
|
'wheel.winners': 'Latest winners',
|
||||||
'wheel.winners.empty': 'No winners yet',
|
'wheel.winners.empty': 'No winners yet',
|
||||||
'wheel.win.title': 'You won!',
|
'wheel.win.title': 'You won!',
|
||||||
@@ -707,24 +709,6 @@
|
|||||||
'chatcmd.client.info': 'Client info',
|
'chatcmd.client.info': 'Client info',
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Mentions
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
'mentions.window.title': 'Mentions',
|
|
||||||
'mentions.window.empty': 'No mentions',
|
|
||||||
'mentions.window.markall': 'Mark all as read',
|
|
||||||
'mentions.tab.title': 'Mentions',
|
|
||||||
'mentions.notification': '%sender% mentioned you in %room%',
|
|
||||||
'mentions.filter.all': 'All',
|
|
||||||
'mentions.filter.unread': 'Unread',
|
|
||||||
'mentions.filter.direct': 'Direct',
|
|
||||||
'mentions.filter.room': 'Room',
|
|
||||||
'mentions.group.today': 'Today',
|
|
||||||
'mentions.group.yesterday': 'Yesterday',
|
|
||||||
'mentions.group.older': 'Earlier',
|
|
||||||
'mentions.type.direct': 'Direct mention',
|
|
||||||
'mentions.type.room': 'Room mention',
|
|
||||||
'mentions.action.goto': 'Go to room',
|
|
||||||
'mentions.action.remove': 'Remove',
|
|
||||||
// Me-menu settings + User account settings window
|
// Me-menu settings + User account settings window
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
'usersettings.tab.general': "General",
|
'usersettings.tab.general': "General",
|
||||||
@@ -790,4 +774,27 @@
|
|||||||
'usersettings.success.password': "Password updated successfully.",
|
'usersettings.success.password': "Password updated successfully.",
|
||||||
'usersettings.success.email': "Email updated successfully.",
|
'usersettings.success.email': "Email updated successfully.",
|
||||||
'usersettings.success.username': "Username updated. Please log in again with your new name.",
|
'usersettings.success.username': "Username updated. Please log in again with your new name.",
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// @-mention autocomplete (chat input)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
'mentions.window.title': "Mentions",
|
||||||
|
'mentions.window.empty': "No mentions",
|
||||||
|
'mentions.window.markall': "Mark all as read",
|
||||||
|
'mentions.tab.title': "Mentions",
|
||||||
|
'mentions.notification': "%sender% mentioned you in %room%",
|
||||||
|
'mentions.filter.all': "All",
|
||||||
|
'mentions.filter.unread': "Unread",
|
||||||
|
'mentions.filter.direct': "Direct",
|
||||||
|
'mentions.filter.room': "Room",
|
||||||
|
'mentions.group.today': "Today",
|
||||||
|
'mentions.group.yesterday': "Yesterday",
|
||||||
|
'mentions.group.older': "Earlier",
|
||||||
|
'mentions.type.direct': "Direct mention",
|
||||||
|
'mentions.type.room': "Room mention",
|
||||||
|
'mentions.action.goto': "Go to room",
|
||||||
|
'mentions.action.remove': "Remove",
|
||||||
|
'mentions.alias.description.everyone': "Everyone in the hotel",
|
||||||
|
'mentions.alias.description.friends': "Your online friends",
|
||||||
|
'mentions.alias.description.room': "Everyone in this room",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -640,6 +640,8 @@
|
|||||||
'wheel.extra': 'Giri extra: %count%',
|
'wheel.extra': 'Giri extra: %count%',
|
||||||
'wheel.spin': 'GIRA',
|
'wheel.spin': 'GIRA',
|
||||||
'wheel.buy': 'Acquista giro',
|
'wheel.buy': 'Acquista giro',
|
||||||
|
'wheel.settings': 'Configurações',
|
||||||
|
'wheel.settings.title': 'Configuração de Sistema da Roleta',
|
||||||
'wheel.winners': 'Ultimi vincitori',
|
'wheel.winners': 'Ultimi vincitori',
|
||||||
'wheel.winners.empty': 'Ancora nessun vincitore',
|
'wheel.winners.empty': 'Ancora nessun vincitore',
|
||||||
'wheel.win.title': 'Hai vinto!',
|
'wheel.win.title': 'Hai vinto!',
|
||||||
@@ -707,24 +709,6 @@
|
|||||||
'chatcmd.client.info': 'Info client',
|
'chatcmd.client.info': 'Info client',
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Mentions
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
'mentions.window.title': 'Menzioni',
|
|
||||||
'mentions.window.empty': 'Nessuna menzione',
|
|
||||||
'mentions.window.markall': 'Segna tutte come lette',
|
|
||||||
'mentions.tab.title': 'Menzioni',
|
|
||||||
'mentions.notification': '%sender% ti ha menzionato in %room%',
|
|
||||||
'mentions.filter.all': 'Tutte',
|
|
||||||
'mentions.filter.unread': 'Non lette',
|
|
||||||
'mentions.filter.direct': 'Dirette',
|
|
||||||
'mentions.filter.room': 'Stanza',
|
|
||||||
'mentions.group.today': 'Oggi',
|
|
||||||
'mentions.group.yesterday': 'Ieri',
|
|
||||||
'mentions.group.older': 'Precedenti',
|
|
||||||
'mentions.type.direct': 'Menzione diretta',
|
|
||||||
'mentions.type.room': 'Menzione di stanza',
|
|
||||||
'mentions.action.goto': 'Vai alla stanza',
|
|
||||||
'mentions.action.remove': 'Rimuovi',
|
|
||||||
// Me-menu settings + User account settings window
|
// Me-menu settings + User account settings window
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
'usersettings.tab.general': "Generale",
|
'usersettings.tab.general': "Generale",
|
||||||
@@ -790,4 +774,27 @@
|
|||||||
'usersettings.success.password': "Password aggiornata con successo.",
|
'usersettings.success.password': "Password aggiornata con successo.",
|
||||||
'usersettings.success.email': "Email aggiornata con successo.",
|
'usersettings.success.email': "Email aggiornata con successo.",
|
||||||
'usersettings.success.username': "Nome utente aggiornato. Accedi di nuovo con il tuo nuovo nome.",
|
'usersettings.success.username': "Nome utente aggiornato. Accedi di nuovo con il tuo nuovo nome.",
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// @-mention autocomplete (chat input)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
'mentions.window.title': "Menzioni",
|
||||||
|
'mentions.window.empty': "Nessuna menzione",
|
||||||
|
'mentions.window.markall': "Segna tutte come lette",
|
||||||
|
'mentions.tab.title': "Menzioni",
|
||||||
|
'mentions.notification': "%sender% ti ha menzionato in %room%",
|
||||||
|
'mentions.filter.all': "Tutte",
|
||||||
|
'mentions.filter.unread': "Non lette",
|
||||||
|
'mentions.filter.direct': "Dirette",
|
||||||
|
'mentions.filter.room': "Stanza",
|
||||||
|
'mentions.group.today': "Oggi",
|
||||||
|
'mentions.group.yesterday': "Ieri",
|
||||||
|
'mentions.group.older': "Precedenti",
|
||||||
|
'mentions.type.direct': "Menzione diretta",
|
||||||
|
'mentions.type.room': "Menzione di stanza",
|
||||||
|
'mentions.action.goto': "Vai alla stanza",
|
||||||
|
'mentions.action.remove': "Rimuovi",
|
||||||
|
'mentions.alias.description.everyone': "Tutti nell'hotel",
|
||||||
|
'mentions.alias.description.friends': "I tuoi amici online",
|
||||||
|
'mentions.alias.description.room': "Tutti in questa stanza",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -709,24 +709,6 @@
|
|||||||
'chatcmd.client.info': 'Client info',
|
'chatcmd.client.info': 'Client info',
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// Mentions
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
'mentions.window.title': 'Vermeldingen',
|
|
||||||
'mentions.window.empty': 'Geen vermeldingen',
|
|
||||||
'mentions.window.markall': 'Alles als gelezen markeren',
|
|
||||||
'mentions.tab.title': 'Vermeldingen',
|
|
||||||
'mentions.notification': '%sender% heeft je genoemd in %room%',
|
|
||||||
'mentions.filter.all': 'Alle',
|
|
||||||
'mentions.filter.unread': 'Ongelezen',
|
|
||||||
'mentions.filter.direct': 'Direct',
|
|
||||||
'mentions.filter.room': 'Kamer',
|
|
||||||
'mentions.group.today': 'Vandaag',
|
|
||||||
'mentions.group.yesterday': 'Gisteren',
|
|
||||||
'mentions.group.older': 'Eerder',
|
|
||||||
'mentions.type.direct': 'Directe vermelding',
|
|
||||||
'mentions.type.room': 'Kamervermelding',
|
|
||||||
'mentions.action.goto': 'Ga naar kamer',
|
|
||||||
'mentions.action.remove': 'Verwijderen',
|
|
||||||
// Me-menu settings + User account settings window
|
// Me-menu settings + User account settings window
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
'usersettings.tab.general': "Algemeen",
|
'usersettings.tab.general': "Algemeen",
|
||||||
@@ -792,4 +774,27 @@
|
|||||||
'usersettings.success.password': "Wachtwoord succesvol bijgewerkt.",
|
'usersettings.success.password': "Wachtwoord succesvol bijgewerkt.",
|
||||||
'usersettings.success.email': "E-mail succesvol bijgewerkt.",
|
'usersettings.success.email': "E-mail succesvol bijgewerkt.",
|
||||||
'usersettings.success.username': "Gebruikersnaam bijgewerkt. Log opnieuw in met je nieuwe naam.",
|
'usersettings.success.username': "Gebruikersnaam bijgewerkt. Log opnieuw in met je nieuwe naam.",
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// @-mention autocomplete (chat input)
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
'mentions.window.title': "Vermeldingen",
|
||||||
|
'mentions.window.empty': "Geen vermeldingen",
|
||||||
|
'mentions.window.markall': "Markeer alles als gelezen",
|
||||||
|
'mentions.tab.title': "Vermeldingen",
|
||||||
|
'mentions.notification': "%sender% noemde je in %room%",
|
||||||
|
'mentions.filter.all': "Alles",
|
||||||
|
'mentions.filter.unread': "Ongelezen",
|
||||||
|
'mentions.filter.direct': "Direct",
|
||||||
|
'mentions.filter.room': "Kamer",
|
||||||
|
'mentions.group.today': "Vandaag",
|
||||||
|
'mentions.group.yesterday': "Gisteren",
|
||||||
|
'mentions.group.older': "Eerder",
|
||||||
|
'mentions.type.direct': "Directe vermelding",
|
||||||
|
'mentions.type.room': "Kamervermelding",
|
||||||
|
'mentions.action.goto': "Ga naar kamer",
|
||||||
|
'mentions.action.remove': "Verwijderen",
|
||||||
|
'mentions.alias.description.everyone': "Iedereen in het hotel",
|
||||||
|
'mentions.alias.description.friends': "Je vrienden online",
|
||||||
|
'mentions.alias.description.room': "Iedereen in deze kamer",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { LoginView } from './components/login/LoginView';
|
|||||||
import { MainView } from './components/MainView';
|
import { MainView } from './components/MainView';
|
||||||
import { ReconnectView } from './components/reconnect/ReconnectView';
|
import { ReconnectView } from './components/reconnect/ReconnectView';
|
||||||
import { useMessageEvent, useNitroEvent } from './hooks';
|
import { useMessageEvent, useNitroEvent } from './hooks';
|
||||||
import { ensureChatCommandListener } from './hooks/rooms/widgets/useChatCommandSelector';
|
|
||||||
|
|
||||||
NitroVersion.UI_VERSION = GetUIVersion();
|
NitroVersion.UI_VERSION = GetUIVersion();
|
||||||
|
|
||||||
@@ -563,9 +562,7 @@ export const App: FC<{}> = props =>
|
|||||||
bumpProgress(85, taskLabel('loading.task.rooms', 'Loading rooms...'));
|
bumpProgress(85, taskLabel('loading.task.rooms', 'Loading rooms...'));
|
||||||
await GetRoomEngine().init();
|
await GetRoomEngine().init();
|
||||||
bumpProgress(92, taskLabel('loading.task.engine', 'Loading graphics engine...'));
|
bumpProgress(92, taskLabel('loading.task.engine', 'Loading graphics engine...'));
|
||||||
ensureChatCommandListener();
|
|
||||||
await GetCommunication().init();
|
await GetCommunication().init();
|
||||||
ensureChatCommandListener();
|
|
||||||
bumpProgress(98, taskLabel('generic.reconnecting', 'Connecting to server...'));
|
bumpProgress(98, taskLabel('generic.reconnecting', 'Connecting to server...'));
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ export class RoomWidgetUpdateChatInputContentEvent extends RoomWidgetUpdateEvent
|
|||||||
public static CHAT_INPUT_CONTENT: string = 'RWUCICE_CHAT_INPUT_CONTENT';
|
public static CHAT_INPUT_CONTENT: string = 'RWUCICE_CHAT_INPUT_CONTENT';
|
||||||
public static WHISPER: string = 'whisper';
|
public static WHISPER: string = 'whisper';
|
||||||
public static SHOUT: string = 'shout';
|
public static SHOUT: string = 'shout';
|
||||||
public static TEXT: string = 'text';
|
|
||||||
|
|
||||||
private _chatMode: string = '';
|
private _chatMode: string = '';
|
||||||
private _userName: string = '';
|
private _userName: string = '';
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { FC, useEffect, useRef } from 'react';
|
import { FC, useEffect, useRef } from 'react';
|
||||||
import type { CommandDefinition } from '../../../../api';
|
import { CommandDefinition } from '../../../../api';
|
||||||
import type { RankedCommandDefinition } from '../../../../hooks/rooms/widgets/useChatCommandSelector.helpers';
|
|
||||||
|
|
||||||
interface ChatInputCommandSelectorViewProps
|
interface ChatInputCommandSelectorViewProps
|
||||||
{
|
{
|
||||||
commands: RankedCommandDefinition[];
|
commands: CommandDefinition[];
|
||||||
selectedIndex: number;
|
selectedIndex: number;
|
||||||
onSelect: (command: CommandDefinition) => void;
|
onSelect: (command: CommandDefinition) => void;
|
||||||
onHover: (index: number) => void;
|
onHover: (index: number) => void;
|
||||||
@@ -25,18 +24,17 @@ export const ChatInputCommandSelectorView: FC<ChatInputCommandSelectorViewProps>
|
|||||||
}, [ selectedIndex ]);
|
}, [ selectedIndex ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ listRef } className="absolute bottom-full left-0 z-[1070] max-h-[238px] w-full overflow-y-auto rounded-t-[8px] border-2 border-b-0 border-black bg-[#f2f2eb] shadow-[0_-4px_14px_rgba(0,0,0,0.22)]">
|
<div ref={ listRef } className="absolute bottom-full left-0 w-full bg-[#e8e8e8] border-2 border-black border-b-0 rounded-t-lg max-h-[240px] overflow-y-auto z-[1070]">
|
||||||
{ commands.map((cmd, index) => (
|
{ commands.map((cmd, index) => (
|
||||||
<button
|
<div
|
||||||
key={ cmd.key }
|
key={ cmd.key }
|
||||||
className={ `flex min-h-[34px] w-full cursor-pointer items-center gap-2 border-b border-[#c6c6bd] px-3 py-1.5 text-left last:border-b-0 ${ index === selectedIndex ? 'bg-[#255d72] text-white' : 'text-black hover:bg-[#dceaf0]' }` }
|
className={ `px-3 py-1.5 cursor-pointer text-sm flex items-center gap-2 ${ index === selectedIndex ? 'bg-[#283F5D] text-white' : 'hover:bg-gray-300' }` }
|
||||||
type="button"
|
|
||||||
onClick={ () => onSelect(cmd) }
|
onClick={ () => onSelect(cmd) }
|
||||||
onMouseEnter={ () => onHover(index) }
|
onMouseEnter={ () => onHover(index) }
|
||||||
>
|
>
|
||||||
<span className={ `shrink-0 rounded-[4px] border px-1.5 py-[1px] font-bold ${ index === selectedIndex ? 'border-white/60 bg-white/15' : 'border-[#8ca6b1] bg-white text-[#123b4c]' }` }>:{ cmd.key }</span>
|
<span className="font-bold">:{ cmd.key }</span>
|
||||||
<span className={ `min-w-0 flex-1 truncate text-[12px] ${ index === selectedIndex ? 'text-white/85' : 'text-[#525252]' }` }>{ cmd.description }</span>
|
<span className={ `text-xs ${ index === selectedIndex ? 'text-gray-300' : 'text-gray-500' }` }>{ cmd.description }</span>
|
||||||
</button>
|
</div>
|
||||||
)) }
|
)) }
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,15 +12,40 @@ import { ChatInputStyleSelectorView } from './ChatInputStyleSelectorView';
|
|||||||
|
|
||||||
const USER_TYPE_REAL_USER = 1;
|
const USER_TYPE_REAL_USER = 1;
|
||||||
const MAX_MENTION_SUGGESTIONS = 8;
|
const MAX_MENTION_SUGGESTIONS = 8;
|
||||||
const MENTION_ALIASES: ReadonlyArray<{ key: string; label: string; description?: string }> = [
|
|
||||||
{ key: 'all', label: 'all', description: 'Everyone in the hotel' },
|
type MentionAliasScope = 'everyone' | 'friends' | 'room';
|
||||||
{ key: 'everyone', label: 'everyone', description: 'Everyone in the hotel' },
|
|
||||||
{ key: 'tutti', label: 'tutti', description: 'Everyone in the hotel' },
|
const MENTION_ALIAS_CONFIG_KEY: Record<MentionAliasScope, string> = {
|
||||||
{ key: 'friends', label: 'friends', description: 'Your online friends' },
|
everyone: 'mentions_ui.aliases.everyone',
|
||||||
{ key: 'amici', label: 'amici', description: 'Your online friends' },
|
friends: 'mentions_ui.aliases.friends',
|
||||||
{ key: 'room', label: 'room', description: 'Everyone in this room' },
|
room: 'mentions_ui.aliases.room'
|
||||||
{ key: 'stanza', label: 'stanza', description: 'Everyone in this room' }
|
};
|
||||||
];
|
|
||||||
|
const MENTION_ALIAS_DEFAULTS: Record<MentionAliasScope, string[]> = {
|
||||||
|
everyone: [ 'all', 'everyone', 'tutti' ],
|
||||||
|
friends: [ 'friends', 'amici' ],
|
||||||
|
room: [ 'room', 'stanza' ]
|
||||||
|
};
|
||||||
|
|
||||||
|
const MENTION_ALIAS_DESCRIPTION_KEY: Record<MentionAliasScope, string> = {
|
||||||
|
everyone: 'mentions.alias.description.everyone',
|
||||||
|
friends: 'mentions.alias.description.friends',
|
||||||
|
room: 'mentions.alias.description.room'
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanitizeAliasList = (raw: unknown, fallback: string[]): string[] =>
|
||||||
|
{
|
||||||
|
if(!Array.isArray(raw)) return fallback;
|
||||||
|
const out: string[] = [];
|
||||||
|
for(const entry of raw)
|
||||||
|
{
|
||||||
|
if(typeof entry !== 'string') continue;
|
||||||
|
const trimmed = entry.trim();
|
||||||
|
if(!trimmed) continue;
|
||||||
|
out.push(trimmed);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
export const ChatInputView: FC<{}> = props =>
|
export const ChatInputView: FC<{}> = props =>
|
||||||
{
|
{
|
||||||
@@ -30,6 +55,7 @@ export const ChatInputView: FC<{}> = props =>
|
|||||||
const { roomSession = null } = useRoom();
|
const { roomSession = null } = useRoom();
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const { isVisible: commandSelectorVisible, filteredCommands, selectedIndex, setSelectedIndex, moveUp, moveDown, selectCurrent, close: closeCommandSelector } = useChatCommandSelector(chatValue);
|
const { isVisible: commandSelectorVisible, filteredCommands, selectedIndex, setSelectedIndex, moveUp, moveDown, selectCurrent, close: closeCommandSelector } = useChatCommandSelector(chatValue);
|
||||||
|
|
||||||
const roomUserList = useRoomUserListSnapshot();
|
const roomUserList = useRoomUserListSnapshot();
|
||||||
const [ mentionSelectedIndex, setMentionSelectedIndex ] = useState<number>(0);
|
const [ mentionSelectedIndex, setMentionSelectedIndex ] = useState<number>(0);
|
||||||
|
|
||||||
@@ -46,12 +72,38 @@ export const ChatInputView: FC<{}> = props =>
|
|||||||
if(at > 0 && !/\s/.test(upToCaret.charAt(at - 1))) return null;
|
if(at > 0 && !/\s/.test(upToCaret.charAt(at - 1))) return null;
|
||||||
|
|
||||||
const query = upToCaret.slice(at + 1);
|
const query = upToCaret.slice(at + 1);
|
||||||
|
|
||||||
if(/\s/.test(query)) return null;
|
if(/\s/.test(query)) return null;
|
||||||
|
|
||||||
return { atIndex: at, replaceFrom: at, replaceTo: caret, query };
|
return { atIndex: at, replaceFrom: at, replaceTo: caret, query };
|
||||||
}, [ chatValue, commandSelectorVisible ]);
|
}, [ chatValue, commandSelectorVisible ]);
|
||||||
|
|
||||||
|
const mentionAliases = useMemo<ReadonlyArray<{ key: string; scope: MentionAliasScope; description: string }>>(() =>
|
||||||
|
{
|
||||||
|
const out: { key: string; scope: MentionAliasScope; description: string }[] = [];
|
||||||
|
const seen = new Set<string>();
|
||||||
|
|
||||||
|
const scopes: MentionAliasScope[] = [ 'everyone', 'friends', 'room' ];
|
||||||
|
for(const scope of scopes)
|
||||||
|
{
|
||||||
|
const list = sanitizeAliasList(
|
||||||
|
GetConfigurationValue<unknown>(MENTION_ALIAS_CONFIG_KEY[scope], MENTION_ALIAS_DEFAULTS[scope]),
|
||||||
|
MENTION_ALIAS_DEFAULTS[scope]
|
||||||
|
);
|
||||||
|
|
||||||
|
for(const key of list)
|
||||||
|
{
|
||||||
|
const lower = key.toLowerCase();
|
||||||
|
|
||||||
|
if(seen.has(lower)) continue;
|
||||||
|
seen.add(lower);
|
||||||
|
|
||||||
|
out.push({ key, scope, description: LocalizeText(MENTION_ALIAS_DESCRIPTION_KEY[scope]) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const mentionSuggestions = useMemo<MentionSuggestion[]>(() =>
|
const mentionSuggestions = useMemo<MentionSuggestion[]>(() =>
|
||||||
{
|
{
|
||||||
if(!mentionContext) return [];
|
if(!mentionContext) return [];
|
||||||
@@ -76,14 +128,14 @@ export const ChatInputView: FC<{}> = props =>
|
|||||||
if(out.length >= MAX_MENTION_SUGGESTIONS) break;
|
if(out.length >= MAX_MENTION_SUGGESTIONS) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const alias of MENTION_ALIASES)
|
for(const alias of mentionAliases)
|
||||||
{
|
{
|
||||||
if(query.length > 0 && !alias.key.toLowerCase().startsWith(query)) continue;
|
if(query.length > 0 && !alias.key.toLowerCase().startsWith(query)) continue;
|
||||||
|
|
||||||
out.push({
|
out.push({
|
||||||
key: `alias:${ alias.key }`,
|
key: `alias:${ alias.key }`,
|
||||||
kind: 'alias',
|
kind: 'alias',
|
||||||
name: alias.label,
|
name: alias.key,
|
||||||
insertToken: alias.key,
|
insertToken: alias.key,
|
||||||
description: alias.description
|
description: alias.description
|
||||||
});
|
});
|
||||||
@@ -92,7 +144,7 @@ export const ChatInputView: FC<{}> = props =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}, [ mentionContext, roomUserList ]);
|
}, [ mentionContext, roomUserList, mentionAliases ]);
|
||||||
|
|
||||||
const mentionSelectorVisible = mentionSuggestions.length > 0;
|
const mentionSelectorVisible = mentionSuggestions.length > 0;
|
||||||
|
|
||||||
@@ -148,23 +200,6 @@ export const ChatInputView: FC<{}> = props =>
|
|||||||
inputRef.current.setSelectionRange((inputRef.current.value.length * 2), (inputRef.current.value.length * 2));
|
inputRef.current.setSelectionRange((inputRef.current.value.length * 2), (inputRef.current.value.length * 2));
|
||||||
}, [ inputRef ]);
|
}, [ inputRef ]);
|
||||||
|
|
||||||
const setChatInputValue = useCallback((value: string, markTyping: boolean = true) =>
|
|
||||||
{
|
|
||||||
setChatValue(value);
|
|
||||||
|
|
||||||
if(markTyping)
|
|
||||||
{
|
|
||||||
setIsTyping(!!value.length);
|
|
||||||
setIsIdle(!!value.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(() =>
|
|
||||||
{
|
|
||||||
inputRef.current?.focus();
|
|
||||||
inputRef.current?.setSelectionRange(value.length, value.length);
|
|
||||||
});
|
|
||||||
}, [ setIsTyping, setIsIdle ]);
|
|
||||||
|
|
||||||
const checkSpecialKeywordForInput = useCallback(() =>
|
const checkSpecialKeywordForInput = useCallback(() =>
|
||||||
{
|
{
|
||||||
setChatValue(prevValue =>
|
setChatValue(prevValue =>
|
||||||
@@ -279,7 +314,7 @@ export const ChatInputView: FC<{}> = props =>
|
|||||||
if(selected)
|
if(selected)
|
||||||
{
|
{
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setChatInputValue(':' + selected.key + ' ');
|
setChatValue(':' + selected.key + ' ');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -361,9 +396,6 @@ export const ChatInputView: FC<{}> = props =>
|
|||||||
{
|
{
|
||||||
switch(event.chatMode)
|
switch(event.chatMode)
|
||||||
{
|
{
|
||||||
case RoomWidgetUpdateChatInputContentEvent.TEXT:
|
|
||||||
setChatInputValue(event.userName);
|
|
||||||
return;
|
|
||||||
case RoomWidgetUpdateChatInputContentEvent.WHISPER: {
|
case RoomWidgetUpdateChatInputContentEvent.WHISPER: {
|
||||||
setChatValue(`${ chatModeIdWhisper } ${ event.userName } `);
|
setChatValue(`${ chatModeIdWhisper } ${ event.userName } `);
|
||||||
return;
|
return;
|
||||||
@@ -450,7 +482,7 @@ export const ChatInputView: FC<{}> = props =>
|
|||||||
selectedIndex={ selectedIndex }
|
selectedIndex={ selectedIndex }
|
||||||
onSelect={ (cmd) =>
|
onSelect={ (cmd) =>
|
||||||
{
|
{
|
||||||
setChatInputValue(':' + cmd.key + ' ');
|
setChatValue(':' + cmd.key + ' '); inputRef.current?.focus();
|
||||||
} }
|
} }
|
||||||
onHover={ setSelectedIndex }
|
onHover={ setSelectedIndex }
|
||||||
/> }
|
/> }
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import { CommandDefinition, LocalizeText } from '../../../api';
|
import { CommandDefinition, LocalizeText } from '../../../api';
|
||||||
import { createNitroStore } from '../../../state/createNitroStore';
|
import { createNitroStore } from '../../../state/createNitroStore';
|
||||||
import { useMessageEvent } from '../../events';
|
import { useMessageEvent } from '../../events';
|
||||||
import { getChatCommandQuery, getRankedCommandSuggestions } from './useChatCommandSelector.helpers';
|
|
||||||
|
|
||||||
const MAX_VISIBLE_COMMANDS = 8;
|
|
||||||
|
|
||||||
// Client-only commands are static; safe to keep at module scope. The
|
// Client-only commands are static; safe to keep at module scope. The
|
||||||
// `descriptionKey` is a LocalizeText slot resolved at merge time so
|
// `descriptionKey` is a LocalizeText slot resolved at merge time so
|
||||||
@@ -65,7 +62,7 @@ const useChatCommandStore = createNitroStore<ChatCommandStore>()((set) => ({
|
|||||||
markListenerRegistered: () => set({ isListenerRegistered: true })
|
markListenerRegistered: () => set({ isListenerRegistered: true })
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const ensureChatCommandListener = (): void =>
|
const ensureGlobalListener = (): void =>
|
||||||
{
|
{
|
||||||
if(useChatCommandStore.getState().isListenerRegistered) return;
|
if(useChatCommandStore.getState().isListenerRegistered) return;
|
||||||
|
|
||||||
@@ -89,20 +86,20 @@ export const ensureChatCommandListener = (): void =>
|
|||||||
|
|
||||||
// Try once at module load so the server's response landing before any
|
// Try once at module load so the server's response landing before any
|
||||||
// React mount still hits the cache.
|
// React mount still hits the cache.
|
||||||
ensureChatCommandListener();
|
ensureGlobalListener();
|
||||||
|
|
||||||
export const useChatCommandSelector = (chatValue: string) =>
|
export const useChatCommandSelector = (chatValue: string) =>
|
||||||
{
|
{
|
||||||
const serverCommands = useChatCommandStore(s => s.serverCommands);
|
const serverCommands = useChatCommandStore(s => s.serverCommands);
|
||||||
const setServerCommands = useChatCommandStore(s => s.setServerCommands);
|
const setServerCommands = useChatCommandStore(s => s.setServerCommands);
|
||||||
const [ selectedIndex, setSelectedIndex ] = useState(0);
|
const [ selectedIndex, setSelectedIndex ] = useState(0);
|
||||||
const [ dismissedQuery, setDismissedQuery ] = useState<string | null>(null);
|
const [ dismissed, setDismissed ] = useState(false);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
// Cover the case where the module-level registration failed
|
// Cover the case where the module-level registration failed
|
||||||
// because GetCommunication() wasn't ready at import time.
|
// because GetCommunication() wasn't ready at import time.
|
||||||
ensureChatCommandListener();
|
ensureGlobalListener();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Late updates (rank change, etc.) — go through the store so all
|
// Late updates (rank change, etc.) — go through the store so all
|
||||||
@@ -126,55 +123,61 @@ export const useChatCommandSelector = (chatValue: string) =>
|
|||||||
return merged.sort((a, b) => a.key.localeCompare(b.key));
|
return merged.sort((a, b) => a.key.localeCompare(b.key));
|
||||||
}, [ serverCommands ]);
|
}, [ serverCommands ]);
|
||||||
|
|
||||||
const filterText = useMemo(() => getChatCommandQuery(chatValue), [ chatValue ]);
|
const filterText = useMemo(() =>
|
||||||
|
{
|
||||||
|
if(!chatValue.startsWith(':') || chatValue.includes(' ')) return '';
|
||||||
|
|
||||||
|
return chatValue.slice(1).toLowerCase();
|
||||||
|
}, [ chatValue ]);
|
||||||
|
|
||||||
const filteredCommands = useMemo(() =>
|
const filteredCommands = useMemo(() =>
|
||||||
{
|
{
|
||||||
if(filterText === null) return [];
|
if(!filterText && !chatValue.startsWith(':')) return [];
|
||||||
|
|
||||||
return getRankedCommandSuggestions(allCommands, filterText, MAX_VISIBLE_COMMANDS);
|
return allCommands.filter(cmd => cmd.key.toLowerCase().startsWith(filterText));
|
||||||
}, [ allCommands, filterText ]);
|
}, [ allCommands, filterText, chatValue ]);
|
||||||
|
|
||||||
const isVisible = useMemo(() =>
|
const isVisible = useMemo(() =>
|
||||||
{
|
{
|
||||||
return filterText !== null && filteredCommands.length > 0 && dismissedQuery !== filterText;
|
return chatValue.startsWith(':') && !chatValue.includes(' ') && filteredCommands.length > 0 && !dismissed;
|
||||||
}, [ filterText, filteredCommands, dismissedQuery ]);
|
}, [ chatValue, filteredCommands, dismissed ]);
|
||||||
|
|
||||||
const boundedSelectedIndex = useMemo(() =>
|
|
||||||
{
|
|
||||||
if(!filteredCommands.length) return 0;
|
|
||||||
|
|
||||||
return Math.min(selectedIndex, filteredCommands.length - 1);
|
|
||||||
}, [ filteredCommands.length, selectedIndex ]);
|
|
||||||
|
|
||||||
const moveUp = useCallback(() =>
|
const moveUp = useCallback(() =>
|
||||||
{
|
{
|
||||||
if(!filteredCommands.length) return;
|
setSelectedIndex(prev => (prev <= 0 ? filteredCommands.length - 1 : prev - 1));
|
||||||
|
|
||||||
setSelectedIndex(prev => ((prev <= 0 || prev >= filteredCommands.length) ? filteredCommands.length - 1 : prev - 1));
|
|
||||||
}, [ filteredCommands.length ]);
|
}, [ filteredCommands.length ]);
|
||||||
|
|
||||||
const moveDown = useCallback(() =>
|
const moveDown = useCallback(() =>
|
||||||
{
|
{
|
||||||
if(!filteredCommands.length) return;
|
|
||||||
|
|
||||||
setSelectedIndex(prev => (prev >= filteredCommands.length - 1 ? 0 : prev + 1));
|
setSelectedIndex(prev => (prev >= filteredCommands.length - 1 ? 0 : prev + 1));
|
||||||
}, [ filteredCommands.length ]);
|
}, [ filteredCommands.length ]);
|
||||||
|
|
||||||
const selectCurrent = useCallback((): CommandDefinition | null =>
|
const selectCurrent = useCallback((): CommandDefinition | null =>
|
||||||
{
|
{
|
||||||
if(boundedSelectedIndex >= 0 && boundedSelectedIndex < filteredCommands.length)
|
if(selectedIndex >= 0 && selectedIndex < filteredCommands.length)
|
||||||
{
|
{
|
||||||
return filteredCommands[boundedSelectedIndex];
|
return filteredCommands[selectedIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}, [ boundedSelectedIndex, filteredCommands ]);
|
}, [ selectedIndex, filteredCommands ]);
|
||||||
|
|
||||||
const close = useCallback(() =>
|
const close = useCallback(() =>
|
||||||
{
|
{
|
||||||
setDismissedQuery(filterText);
|
setDismissed(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Reset dismissed when chatValue changes to a new command start
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
if(chatValue === ':' || chatValue === '') setDismissed(false);
|
||||||
|
}, [ chatValue ]);
|
||||||
|
|
||||||
|
// Reset selectedIndex when filtered list changes
|
||||||
|
useEffect(() =>
|
||||||
|
{
|
||||||
|
setSelectedIndex(0);
|
||||||
}, [ filterText ]);
|
}, [ filterText ]);
|
||||||
|
|
||||||
return { isVisible, filteredCommands, selectedIndex: boundedSelectedIndex, setSelectedIndex, moveUp, moveDown, selectCurrent, close };
|
return { isVisible, filteredCommands, selectedIndex, setSelectedIndex, moveUp, moveDown, selectCurrent, close };
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user