fix/commandPalette (#547)

* CMD on MacOS fix

* misc improvements
This commit is contained in:
Jake 2025-12-09 16:56:38 +00:00 • committed by GitHub
parent f3b133ee78
commit 91c9d4a4a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 92 additions and 106 deletions

View file

@ -5,10 +5,11 @@
*/
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { BaseText } from "@components/BaseText";
import { Button } from "@components/Button";
import { EquicordDevs, IS_MAC } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Button, Text, useEffect, useRef, useState } from "@webpack/common";
import { useEffect, useState } from "@webpack/common";
import { cleanupCommandPaletteRuntime, registerBuiltInCommands, wrapChatBarChildren } from "./registry";
import { CommandPaletteSettingsPanel } from "./settingsPanel";
@ -16,7 +17,6 @@ import { openCommandPalette } from "./ui";
const DEFAULT_KEYS = IS_MAC ? ["Meta", "Shift", "P"] : ["Control", "Shift", "P"];
const cl = classNameFactory("vc-cp-");
const isRecordingGlobal = false;
let openScheduled = false;
@ -37,7 +37,6 @@ function formatKeybind(keybind: string | string[]): string {
function KeybindRecorder() {
const [isListening, setIsListening] = useState(false);
const [error, setError] = useState<string | null>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
const currentKeybind = settings.use(["hotkey"]).hotkey;
useEffect(() => {
@ -52,11 +51,11 @@ function KeybindRecorder() {
}
const keys: string[] = [];
if (IS_MAC && event.ctrlKey) {
if (event.ctrlKey) keys.push("CONTROL");
if (event.metaKey) keys.push("CTRL");
} else if (event.ctrlKey) {
keys.push("CTRL");
if (event.metaKey) {
keys.push("META");
}
if (event.ctrlKey) {
keys.push(IS_MAC ? "CONTROL" : "CTRL");
}
if (event.shiftKey) keys.push("SHIFT");
if (event.altKey) keys.push("ALT");
@ -79,8 +78,6 @@ function KeybindRecorder() {
document.addEventListener("keydown", handleKeyDown, true);
window.addEventListener("blur", handleBlur);
buttonRef.current?.focus();
return () => {
document.removeEventListener("keydown", handleKeyDown, true);
window.removeEventListener("blur", handleBlur);
@ -93,31 +90,34 @@ function KeybindRecorder() {
};
return (
<div className="vc-cp-keybind-input">
<div className="vc-cp-keybind-info">
<Text variant="text-md/semibold">Command Palette Hotkey</Text>
<Text variant="text-sm/normal" style={{ color: "var(--text-muted)" }}>
<div className="vc-command-palette-keybind-input">
<div className="vc-command-palette-keybind-info">
<BaseText size="md" weight="semibold">Command Palette Hotkey</BaseText>
<BaseText size="sm" weight="normal" style={{ color: "var(--text-muted)" }}>
Hotkey used to open the command palette
</Text>
</BaseText>
{error && (
<Text variant="text-xs/normal" className="vc-cp-keybind-conflict">
<BaseText size="xs" weight="normal" className="vc-command-palette-keybind-conflict">
{error}
</Text>
</BaseText>
)}
</div>
<div className="vc-cp-keybind-controls">
<button
ref={buttonRef}
className={`vc-cp-keybind-button ${isListening ? "listening" : ""}`}
<div className="vc-command-palette-keybind-controls">
<Button
type="button"
variant="secondary"
className={`vc-command-palette-keybind-button ${isListening ? "listening" : ""}`}
onClick={() => setIsListening(true)}
>
{isListening ? "Press any key..." : formatKeybind(currentKeybind)}
</button>
<Button
size={Button.Sizes.SMALL}
color={Button.Colors.PRIMARY}
onClick={handleReset}
>
{isListening ? (
<BaseText size="sm" weight="normal" style={{ color: "var(--white)", opacity: 0.8 }}>
Press any key...
</BaseText>
) : (
formatKeybind(currentKeybind)
)}
</Button>
<Button size="small" variant="secondary" onClick={handleReset}>
Reset
</Button>
</div>

View file

@ -4,8 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Button, TextButton } from "@components/Button";
import { Heading } from "@components/Heading";
import { Paragraph } from "@components/Paragraph";
import { Switch } from "@components/Switch";
import { Button, Forms, React, Select, TextInput } from "@webpack/common";
import { React, Select, TextInput } from "@webpack/common";
import { openCommandPicker } from "./CommandPicker";
import {
@ -169,7 +172,7 @@ function renderActionFields(command: CustomCommandDefinition, onChange: (action:
onChange={value => onChange({ type: "command", commandId: value })}
/>
<Button
size={Button.Sizes.SMALL}
size="small"
onClick={() => openCommandPicker({
initialQuery: action.commandId,
onSelect: command => onChange({ type: "command", commandId: command.id })
@ -187,11 +190,11 @@ function renderActionFields(command: CustomCommandDefinition, onChange: (action:
return (
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<Forms.FormText style={{ color: "var(--text-muted, #a5a6ab)" }}>
<Paragraph size="sm" style={{ color: "var(--text-muted, #a5a6ab)" }}>
Current page: {currentSummary}
</Forms.FormText>
</Paragraph>
<Button
size={Button.Sizes.SMALL}
size="small"
onClick={() => openCommandPicker({
initialSelectedIds: selectedSettings ? [selectedSettings.id] : undefined,
filter: entry => entry.categoryId === "discord-settings",
@ -232,7 +235,7 @@ function renderActionFields(command: CustomCommandDefinition, onChange: (action:
checked={Boolean(action.openExternal)}
onChange={value => onChange({ type: "url", url: action.url, openExternal: value })}
/>
<Forms.FormText>Open externally</Forms.FormText>
<Paragraph size="sm">Open externally</Paragraph>
</div>
</div>
);
@ -248,7 +251,7 @@ function renderActionFields(command: CustomCommandDefinition, onChange: (action:
onChange={value => onChange({ type: "macro", steps: parseListInput(value) })}
/>
<Button
size={Button.Sizes.SMALL}
size="small"
onClick={() => openCommandPicker({
allowMultiple: true,
initialSelectedIds: action.steps,
@ -263,25 +266,24 @@ function renderActionFields(command: CustomCommandDefinition, onChange: (action:
{action.steps.length > 0 && (
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
{action.steps.map((step, index) => (
<Button
<TextButton
key={`${step}-${index}`}
size={Button.Sizes.MIN}
variant="secondary"
onClick={() => {
const next = [...action.steps];
next.splice(index, 1);
onChange({ type: "macro", steps: next });
}}
look={Button.Looks.LINK}
>
{step} ✕
</Button>
</TextButton>
))}
</div>
)}
{invalidSteps.length > 0 && (
<Forms.FormText style={{ color: "var(--status-danger-text, #f04747)" }}>
<Paragraph size="sm" style={{ color: "var(--status-danger-text, #f04747)" }}>
Unknown command IDs: {invalidSteps.join(", ")}
</Forms.FormText>
</Paragraph>
)}
</div>
);
@ -350,11 +352,11 @@ export function CommandPaletteSettingsPanel() {
return (
<section>
<Forms.FormTitle tag="h4" style={{ color: "var(--text-normal, #dcddde)" }}>Custom Commands</Forms.FormTitle>
<Forms.FormText className="vc-cp-settings-help">
<Heading tag="h4" style={{ color: "var(--text-normal, #dcddde)" }}>Custom Commands</Heading>
<Paragraph size="sm" className="vc-command-palette-settings-help">
{/* Hank, don't abbreviate cyberpunk hank */}
1) Name the command · 2) Add optional description/keywords/tags/category · 3) Choose an action and fill its details (IDs should match existing palette commands for aliases and macros).
</Forms.FormText>
</Paragraph>
<div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
{commands.map(command => {
const manualAdvanced = advancedOpen[command.id];
@ -370,14 +372,15 @@ export function CommandPaletteSettingsPanel() {
return (
<div key={command.id} style={{ padding: 14, borderRadius: 8, background: "var(--background-primary)", border: "1px solid var(--background-base-low)", display: "flex", flexDirection: "column", gap: 12 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<Forms.FormTitle tag="h5" style={{ color: "var(--text-normal, #dcddde)" }}>{command.label || "Untitled Command"}</Forms.FormTitle>
<Button
size={Button.Sizes.MIN}
look={Button.Looks.LINK}
<Heading tag="h5" style={{ color: "var(--text-normal, #dcddde)", marginBottom: 0 }}>
{command.label || "Untitled Command"}
</Heading>
<TextButton
variant="secondary"
onClick={() => setCollapsed(prev => ({ ...prev, [command.id]: !isCollapsed }))}
>
{isCollapsed ? "Expand" : "Collapse"}
</Button>
</TextButton>
</div>
{!isCollapsed && (
@ -416,20 +419,19 @@ export function CommandPaletteSettingsPanel() {
serialize={option => option.value}
/>
</div>
<Forms.FormText style={{ color: "var(--text-muted, #a5a6ab)", fontSize: 12 }}>
<Paragraph size="xs" style={{ color: "var(--text-muted, #a5a6ab)", fontSize: 12 }}>
{ACTION_HELP_TEXT[command.action.type]}
</Forms.FormText>
</Paragraph>
</div>
{renderActionFields(command, action => handleUpdate(command.id, prev => duplicateWithUpdates(prev, { action })))}
<Button
size={Button.Sizes.SMALL}
look={Button.Looks.LINK}
<TextButton
variant="secondary"
onClick={() => setAdvancedOpen(prev => ({ ...prev, [command.id]: !(showAdvanced ?? false) }))}
>
{showAdvanced ? "Hide advanced options" : "Show advanced options"}
</Button>
</TextButton>
{showAdvanced && (
<div style={{ display: "flex", flexDirection: "column", gap: 12, background: "var(--background-secondary-alt)", borderRadius: 8, padding: 12 }}>
@ -454,9 +456,9 @@ export function CommandPaletteSettingsPanel() {
/>
{tagMeta.length > 0 && (
<div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
<Forms.FormText style={{ color: "var(--text-muted, #a5a6ab)", fontSize: 12 }}>
<Paragraph size="xs" style={{ color: "var(--text-muted, #a5a6ab)", fontSize: 12 }}>
Suggestions
</Forms.FormText>
</Paragraph>
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
{tagMeta
.filter(meta => !(command.tags ?? []).some(tag => normalizeTag(tag) === meta.id))
@ -464,8 +466,8 @@ export function CommandPaletteSettingsPanel() {
.map(meta => (
<Button
key={meta.id}
size={Button.Sizes.MIN}
look={Button.Looks.LINK}
size="min"
variant="secondary"
onClick={() => handleUpdate(command.id, prev => duplicateWithUpdates(prev, {
tags: normalizeTagList([...(prev.tags ?? []), meta.label])
}))}
@ -484,9 +486,9 @@ export function CommandPaletteSettingsPanel() {
select={value => handleUpdate(command.id, prev => duplicateWithUpdates(prev, { categoryId: value || undefined }))}
serialize={option => option.value}
/>
<Forms.FormText style={{ color: "var(--text-muted, #a5a6ab)", fontSize: 12 }}>
<Paragraph size="xs" style={{ color: "var(--text-muted, #a5a6ab)", fontSize: 12 }}>
Choose where this command appears in the palette.
</Forms.FormText>
</Paragraph>
{categoryId && (
<div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
<span style={{ display: "inline-flex", alignItems: "center", gap: 6, borderRadius: 999, padding: "2px 10px", fontSize: 12, background: "var(--background-base-low)", color: "var(--text-normal, #dcddde)" }}>
@ -501,7 +503,7 @@ export function CommandPaletteSettingsPanel() {
checked={Boolean(command.danger)}
onChange={value => handleUpdate(command.id, prev => duplicateWithUpdates(prev, { danger: value }))}
/>
<Forms.FormText>Show as dangerous</Forms.FormText>
<Paragraph size="sm">Show as dangerous</Paragraph>
</div>
</div>
)}
@ -509,8 +511,8 @@ export function CommandPaletteSettingsPanel() {
)}
<Button
color={Button.Colors.RED}
size={Button.Sizes.SMALL}
variant="dangerPrimary"
size="small"
onClick={() => handleRemove(command.id)}
>
Remove Command
@ -519,11 +521,7 @@ export function CommandPaletteSettingsPanel() {
);
})}
</div>
<Button
color={Button.Colors.BRAND}
style={{ marginTop: 16 }}
onClick={handleAdd}
>
<Button variant="primary" style={{ marginTop: 16 }} onClick={handleAdd}>
Add Command
</Button>
</section>

View file

@ -682,37 +682,32 @@
}
/* Keybind recorder styles */
.vc-cp-keybind-input {
.vc-command-palette-keybind-input {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px;
background-color: var(--background-secondary-alt);
padding: 12px 0;
border-radius: 8px;
transition: background-color 0.2s ease;
}
.vc-cp-keybind-input:hover {
background-color: var(--background-secondary-alt);
}
.vc-cp-keybind-info {
.vc-command-palette-keybind-info {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.vc-cp-keybind-controls {
.vc-command-palette-keybind-controls {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
}
.vc-cp-keybind-button {
.vc-command-palette-keybind-button {
padding: 8px 16px;
background-color: var(--background-base-low);
border: 2px solid var(--input-border);
@ -728,55 +723,48 @@
box-shadow: 0 0 0 1px var(--background-modifier-accent);
}
.vc-cp-keybind-button:hover:not(:disabled) {
.vc-command-palette-keybind-button:hover:not(:disabled) {
background-color: var(--background-modifier-hover);
border-color: var(--brand-experiment);
transform: translateY(-1px);
box-shadow: 0 2px 8px rgb(0 0 0 / 20%);
}
.vc-cp-keybind-button:active:not(:disabled) {
.vc-command-palette-keybind-button.listening:hover:not(:disabled) {
background: linear-gradient(135deg, var(--brand-experiment) 0%, var(--brand-experiment-560) 100%);
border-color: var(--status-danger, #f04747);
box-shadow: none;
transform: translateY(-1px);
}
.vc-command-palette-keybind-button:active:not(:disabled) {
transform: translateY(0);
box-shadow: 0 1px 4px rgb(0 0 0 / 20%);
}
.vc-cp-keybind-button:disabled {
.vc-command-palette-keybind-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.vc-cp-keybind-button.listening {
.vc-command-palette-keybind-button.listening {
background: linear-gradient(135deg, var(--brand-experiment) 0%, var(--brand-experiment-560) 100%);
color: var(--white);
border-color: var(--brand-experiment);
animation: vc-cp-keybind-pulse 1.5s ease-in-out infinite;
box-shadow: 0 0 12px var(--brand-experiment);
border-color: var(--status-danger, #f04747);
animation: none;
transform: translateY(-1px);
box-shadow: 0 2px 8px rgb(var(--status-danger-rgb, 240 71 71) / 60%);
}
@keyframes vc-cp-keybind-pulse {
/* stylelint-disable-next-line rule-empty-line-before */
0%,
100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.85;
transform: scale(1.02);
}
}
.vc-cp-keybind-conflict {
.vc-command-palette-keybind-conflict {
color: var(--text-feedback-critical);
font-size: 12px;
font-weight: 500;
margin-top: 2px;
animation: vc-cp-keybind-shake 0.3s ease;
animation: vc-command-palette-keybind-shake 0.3s ease;
}
@keyframes vc-cp-keybind-shake {
@keyframes vc-command-palette-keybind-shake {
/* stylelint-disable-next-line rule-empty-line-before */
0%,
@ -795,4 +783,4 @@
/* stylelint-enable no-descending-specificity */
/* fuck you css */
/* fuck you css */