mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-05-09 04:23:01 -05:00
Preact: Add tab autocomplete (#2667)
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run
This commit is contained in:
parent
b127c965a4
commit
075ff28166
|
|
@ -824,6 +824,19 @@ export class CopyableURLBox extends preact.Component<{ url: string }> {
|
|||
}
|
||||
}
|
||||
|
||||
interface UserAutoCompleteCandidate {
|
||||
type: "user";
|
||||
userid: string;
|
||||
prefixIndex: number;
|
||||
}
|
||||
|
||||
interface CmdAutoCompleteCandidate {
|
||||
type: "command";
|
||||
command: string;
|
||||
}
|
||||
|
||||
export type AutoCompleteCandidate = UserAutoCompleteCandidate | CmdAutoCompleteCandidate;
|
||||
|
||||
export class ChatTextEntry extends preact.Component<{
|
||||
room: ChatRoom, onMessage: (msg: string, elem: HTMLElement) => void, onKey: (e: KeyboardEvent) => boolean,
|
||||
left?: number, tinyLayout?: boolean,
|
||||
|
|
@ -834,7 +847,7 @@ export class ChatTextEntry extends preact.Component<{
|
|||
history: string[] = [];
|
||||
historyIndex = 0;
|
||||
tabComplete: {
|
||||
candidates: { userid: string, prefixIndex: number }[],
|
||||
candidates: AutoCompleteCandidate[],
|
||||
candidateIndex: number,
|
||||
/** the text left of the cursor before tab completing */
|
||||
prefix: string,
|
||||
|
|
@ -1099,7 +1112,7 @@ export class ChatTextEntry extends preact.Component<{
|
|||
return false;
|
||||
}
|
||||
// TODO - add support for commands tabcomplete
|
||||
handleTabComplete(reverse: boolean) {
|
||||
handleTabComplete(reverse: boolean): boolean {
|
||||
// Don't tab complete at the start of the text box.
|
||||
let { value, start, end } = this.getSelection();
|
||||
if (start !== end || end === 0) return false;
|
||||
|
|
@ -1134,23 +1147,25 @@ export class ChatTextEntry extends preact.Component<{
|
|||
const match2 = /^([\s\S!/]*?)([A-Za-z0-9][^, \n]* [^, ]*)$/.exec(prefix);
|
||||
if (!match1 && !match2) return true;
|
||||
|
||||
const candidates: AutoCompleteCandidate[] = [];
|
||||
const idprefix = (match1 ? toID(match1[2]) : '');
|
||||
let spaceprefix = (match2 ? match2[2].replace(/[^A-Za-z0-9 ]+/g, '').toLowerCase() : '');
|
||||
const candidates: { userid: string, prefixIndex: number }[] = [];
|
||||
if (match2 && (match2[0] === '/' || match2[0] === '!')) spaceprefix = '';
|
||||
for (const userid in users) {
|
||||
if (spaceprefix && users[userid].slice(1).replace(/[^A-Za-z0-9 ]+/g, '')
|
||||
.toLowerCase()
|
||||
.startsWith(spaceprefix)) {
|
||||
if (match2) candidates.push({ userid, prefixIndex: match2[1].length });
|
||||
if (match2) candidates.push({ type: "user", userid, prefixIndex: match2[1].length });
|
||||
} else if (idprefix && userid.startsWith(idprefix)) {
|
||||
if (match1) candidates.push({ userid, prefixIndex: match1[1].length });
|
||||
if (match1) candidates.push({ type: "user", userid, prefixIndex: match1[1].length });
|
||||
}
|
||||
}
|
||||
// Sort by most recent to speak in the chat, or, in the case of a tie,
|
||||
// in alphabetical order.
|
||||
const userActivity = this.props.room.userActivity;
|
||||
candidates.sort((a, b) => {
|
||||
// command autocomplete options aren't added until after the user autocomplete options are sorted.
|
||||
if (a.type !== "user" || b.type !== "user") return 0;
|
||||
if (a.prefixIndex !== b.prefixIndex) {
|
||||
// shorter prefix length comes first
|
||||
return a.prefixIndex - b.prefixIndex;
|
||||
|
|
@ -1163,6 +1178,28 @@ export class ChatTextEntry extends preact.Component<{
|
|||
return (a.userid < b.userid) ? -1 : 1; // alphabetical order
|
||||
});
|
||||
|
||||
const currentLine = prefix.substring(prefix.lastIndexOf('\n') + 1);
|
||||
const isCommandSearch = (currentLine.startsWith('/') && !currentLine.startsWith('//')) || currentLine.startsWith('!');
|
||||
if (isCommandSearch) {
|
||||
PS.mainmenu.makeQuery('cmdsearch', currentLine, true).then((data: string[]) => {
|
||||
const cmds = data.sort((a, b) => a.length < b.length ? 1 : -1);
|
||||
const nextCmd = cmds[cmds.length - 1];
|
||||
const newValue = nextCmd + value.substring(end);
|
||||
this.setValue(newValue, nextCmd.length, nextCmd.length);
|
||||
const currentCandidates = this.tabComplete?.candidates ?? [];
|
||||
for (const cmd of cmds) {
|
||||
currentCandidates.unshift({ type: "command", command: cmd });
|
||||
}
|
||||
this.tabComplete = {
|
||||
candidates: currentCandidates,
|
||||
candidateIndex: 0,
|
||||
prefix: nextCmd,
|
||||
cursor: nextCmd,
|
||||
};
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!candidates.length) {
|
||||
this.tabComplete = null;
|
||||
return true;
|
||||
|
|
@ -1176,13 +1213,22 @@ export class ChatTextEntry extends preact.Component<{
|
|||
}
|
||||
// Substitute in the tab-completed name
|
||||
const candidate = this.tabComplete.candidates[this.tabComplete.candidateIndex];
|
||||
let name = users[candidate.userid];
|
||||
if (!name) return true;
|
||||
if (candidate.type === "user") {
|
||||
let name = users[candidate.userid];
|
||||
if (!name) return true;
|
||||
|
||||
name = Dex.getShortName(name.slice(1)); // Remove rank and busy characters
|
||||
const cursor = this.tabComplete.prefix.slice(0, candidate.prefixIndex) + name;
|
||||
this.setValue(cursor + value.slice(end), cursor.length);
|
||||
this.tabComplete.cursor = cursor;
|
||||
name = Dex.getShortName(name.slice(1)); // Remove rank and busy characters
|
||||
const cursor = this.tabComplete.prefix.slice(0, candidate.prefixIndex) + name;
|
||||
this.setValue(cursor + value.slice(end), cursor.length);
|
||||
this.tabComplete.cursor = cursor;
|
||||
} else {
|
||||
const prefixIndex = prefix.lastIndexOf('\n') + 1;
|
||||
const fullPrefix = prefix.substring(0, prefixIndex) + Dex.getShortName(candidate.command);
|
||||
const newValue = fullPrefix + value.substring(end);
|
||||
this.setValue(newValue, fullPrefix.length, fullPrefix.length);
|
||||
this.tabComplete.cursor = fullPrefix;
|
||||
this.tabComplete.prefix = fullPrefix;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
undoTabComplete() {
|
||||
|
|
|
|||
|
|
@ -381,9 +381,9 @@ export class MainMenuRoom extends PSRoom {
|
|||
* Most queries are still handled hardcoded, so this is only for certain
|
||||
* special queries that need a Promise.
|
||||
*/
|
||||
makeQuery(id: string, param?: string) {
|
||||
makeQuery(id: string, param?: string, excludeParamFromListener?: boolean) {
|
||||
let fullid = id;
|
||||
if (param) fullid += ` ${toID(param)}`;
|
||||
if (param && !excludeParamFromListener) fullid += ` ${toID(param)}`;
|
||||
return new Promise<any>(resolve => {
|
||||
if (!this.listeners[fullid]) {
|
||||
this.listeners[fullid] = [];
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user