fix: commands sidebar
This commit is contained in:
parent
21f54d9274
commit
35e34b36f9
@ -26,7 +26,6 @@
|
|||||||
"i18next": "^26.4.0",
|
"i18next": "^26.4.0",
|
||||||
"i18next-browser-languagedetector": "^8.2.1",
|
"i18next-browser-languagedetector": "^8.2.1",
|
||||||
"lucide-react": "^1.37.0",
|
"lucide-react": "^1.37.0",
|
||||||
"or": "^0.2.0",
|
|
||||||
"qrcode.react": "^4.2.0",
|
"qrcode.react": "^4.2.0",
|
||||||
"react": "^19.2.8",
|
"react": "^19.2.8",
|
||||||
"react-dom": "^19.2.8",
|
"react-dom": "^19.2.8",
|
||||||
|
|||||||
@ -202,173 +202,176 @@ const PluginCommandsSidebarWidget = ({ commands }: { commands?: PluginCommand[]
|
|||||||
const visibleCommands = expanded ? commands : commands.slice(0, 4);
|
const visibleCommands = expanded ? commands : commands.slice(0, 4);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sidebar-widget commands-widget" style={{ marginTop: '1.25rem', position: 'static' }}>
|
<>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.85rem' }}>
|
<div className="divider-sm" />
|
||||||
<h3 style={{ margin: 0, display: 'flex', alignItems: 'center', gap: '0.45rem', fontSize: '0.82rem' }}>
|
<div className="commands-widget-section">
|
||||||
<Terminal size={14} color="var(--primary)" />
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.85rem' }}>
|
||||||
{t('plugin.commands.title', 'Commands & Permissions')}
|
<h3 style={{ margin: 0, display: 'flex', alignItems: 'center', gap: '0.45rem', fontSize: '0.82rem' }}>
|
||||||
</h3>
|
<Terminal size={14} color="var(--primary)" />
|
||||||
<span style={{
|
{t('plugin.commands.title', 'Commands & Permissions')}
|
||||||
fontSize: '0.7rem',
|
</h3>
|
||||||
fontWeight: 700,
|
<span style={{
|
||||||
background: 'rgba(255, 255, 255, 0.07)',
|
fontSize: '0.7rem',
|
||||||
padding: '2px 7px',
|
fontWeight: 700,
|
||||||
borderRadius: '999px',
|
background: 'rgba(255, 255, 255, 0.07)',
|
||||||
color: 'var(--text-muted)'
|
padding: '2px 7px',
|
||||||
}}>
|
borderRadius: '999px',
|
||||||
{commands.length}
|
color: 'var(--text-muted)'
|
||||||
</span>
|
}}>
|
||||||
</div>
|
{commands.length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}>
|
||||||
{visibleCommands.map((cmd: any, index: number) => {
|
{visibleCommands.map((cmd: any, index: number) => {
|
||||||
const desc = getLocalizedCommandDescription(cmd.description, activeLang);
|
const desc = getLocalizedCommandDescription(cmd.description, activeLang);
|
||||||
const cmdCopyKey = `cmd-${cmd.id || index}`;
|
const cmdCopyKey = `cmd-${cmd.id || index}`;
|
||||||
const permCopyKey = `perm-${cmd.id || index}`;
|
const permCopyKey = `perm-${cmd.id || index}`;
|
||||||
const formattedName = cmd.name?.startsWith('/') ? cmd.name : `/${cmd.name}`;
|
const formattedName = cmd.name?.startsWith('/') ? cmd.name : `/${cmd.name}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={cmd.id || index}
|
key={cmd.id || index}
|
||||||
style={{
|
style={{
|
||||||
background: 'rgba(0, 0, 0, 0.25)',
|
background: 'rgba(0, 0, 0, 0.25)',
|
||||||
border: '1px solid var(--border)',
|
border: '1px solid var(--border)',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
padding: '0.75rem',
|
padding: '0.75rem',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '0.4rem',
|
gap: '0.4rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Command row */}
|
{/* Command row */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '0.5rem' }}>
|
|
||||||
<code style={{
|
|
||||||
fontFamily: 'var(--font-mono)',
|
|
||||||
fontSize: '0.82rem',
|
|
||||||
fontWeight: 700,
|
|
||||||
color: 'var(--primary, #4ade80)',
|
|
||||||
wordBreak: 'break-all',
|
|
||||||
}}>
|
|
||||||
{formattedName}
|
|
||||||
</code>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => handleCopy(formattedName, cmdCopyKey)}
|
|
||||||
title={t('plugin.commands.copy_command', 'Copy command')}
|
|
||||||
style={{
|
|
||||||
background: copiedId === cmdCopyKey ? 'rgba(74, 222, 128, 0.15)' : 'rgba(255, 255, 255, 0.06)',
|
|
||||||
border: `1px solid ${copiedId === cmdCopyKey ? 'rgba(74, 222, 128, 0.4)' : 'rgba(255, 255, 255, 0.1)'}`,
|
|
||||||
borderRadius: '4px',
|
|
||||||
color: copiedId === cmdCopyKey ? 'var(--success, #4ade80)' : 'var(--text-muted)',
|
|
||||||
cursor: 'pointer',
|
|
||||||
padding: '3px 6px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '3px',
|
|
||||||
fontSize: '0.68rem',
|
|
||||||
fontWeight: 600,
|
|
||||||
transition: 'all 0.15s ease',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{copiedId === cmdCopyKey ? (
|
|
||||||
<>
|
|
||||||
<Check size={11} />
|
|
||||||
<span>{t('plugin.commands.copied', 'Copied')}</span>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Copy size={11} />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Permission row */}
|
|
||||||
{cmd.permission ? (
|
|
||||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '0.5rem' }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '0.5rem' }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.3rem', minWidth: 0 }}>
|
<code style={{
|
||||||
<Key size={10} color="var(--text-muted)" style={{ flexShrink: 0 }} />
|
fontFamily: 'var(--font-mono)',
|
||||||
<span style={{
|
fontSize: '0.82rem',
|
||||||
fontSize: '0.7rem',
|
fontWeight: 700,
|
||||||
fontFamily: 'var(--font-mono)',
|
color: 'var(--primary, #4ade80)',
|
||||||
color: 'var(--text-muted)',
|
wordBreak: 'break-all',
|
||||||
overflow: 'hidden',
|
}}>
|
||||||
textOverflow: 'ellipsis',
|
{formattedName}
|
||||||
whiteSpace: 'nowrap',
|
</code>
|
||||||
}}>
|
|
||||||
{cmd.permission}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleCopy(cmd.permission, permCopyKey)}
|
onClick={() => handleCopy(formattedName, cmdCopyKey)}
|
||||||
title={t('plugin.commands.copy_permission', 'Copy permission')}
|
title={t('plugin.commands.copy_command', 'Copy command')}
|
||||||
style={{
|
style={{
|
||||||
background: 'none',
|
background: copiedId === cmdCopyKey ? 'rgba(74, 222, 128, 0.15)' : 'rgba(255, 255, 255, 0.06)',
|
||||||
border: 'none',
|
border: `1px solid ${copiedId === cmdCopyKey ? 'rgba(74, 222, 128, 0.4)' : 'rgba(255, 255, 255, 0.1)'}`,
|
||||||
color: copiedId === permCopyKey ? 'var(--success, #4ade80)' : 'var(--text-muted)',
|
borderRadius: '4px',
|
||||||
|
color: copiedId === cmdCopyKey ? 'var(--success, #4ade80)' : 'var(--text-muted)',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
padding: '2px',
|
padding: '3px 6px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
flexShrink: 0,
|
gap: '3px',
|
||||||
|
fontSize: '0.68rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
transition: 'all 0.15s ease',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{copiedId === permCopyKey ? <Check size={11} /> : <Copy size={10} />}
|
{copiedId === cmdCopyKey ? (
|
||||||
|
<>
|
||||||
|
<Check size={11} />
|
||||||
|
<span>{t('plugin.commands.copied', 'Copied')}</span>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Copy size={11} />
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
|
||||||
|
|
||||||
{/* Description */}
|
{/* Permission row */}
|
||||||
{desc ? (
|
{cmd.permission ? (
|
||||||
<p style={{
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '0.5rem' }}>
|
||||||
margin: '0.15rem 0 0 0',
|
<div style={{ display: 'flex', alignItems: 'center', gap: '0.3rem', minWidth: 0 }}>
|
||||||
fontSize: '0.78rem',
|
<Key size={10} color="var(--text-muted)" style={{ flexShrink: 0 }} />
|
||||||
color: 'var(--text-2, #94a3b8)',
|
<span style={{
|
||||||
lineHeight: 1.4,
|
fontSize: '0.7rem',
|
||||||
}}>
|
fontFamily: 'var(--font-mono)',
|
||||||
{desc}
|
color: 'var(--text-muted)',
|
||||||
</p>
|
overflow: 'hidden',
|
||||||
) : null}
|
textOverflow: 'ellipsis',
|
||||||
</div>
|
whiteSpace: 'nowrap',
|
||||||
);
|
}}>
|
||||||
})}
|
{cmd.permission}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleCopy(cmd.permission, permCopyKey)}
|
||||||
|
title={t('plugin.commands.copy_permission', 'Copy permission')}
|
||||||
|
style={{
|
||||||
|
background: 'none',
|
||||||
|
border: 'none',
|
||||||
|
color: copiedId === permCopyKey ? 'var(--success, #4ade80)' : 'var(--text-muted)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
padding: '2px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{copiedId === permCopyKey ? <Check size={11} /> : <Copy size={10} />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{/* Description */}
|
||||||
|
{desc ? (
|
||||||
|
<p style={{
|
||||||
|
margin: '0.15rem 0 0 0',
|
||||||
|
fontSize: '0.78rem',
|
||||||
|
color: 'var(--text-2, #94a3b8)',
|
||||||
|
lineHeight: 1.4,
|
||||||
|
}}>
|
||||||
|
{desc}
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{commands.length > 4 && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setExpanded(!expanded)}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
marginTop: '0.75rem',
|
||||||
|
background: 'rgba(255, 255, 255, 0.04)',
|
||||||
|
border: '1px solid var(--border)',
|
||||||
|
borderRadius: '6px',
|
||||||
|
color: 'var(--text)',
|
||||||
|
padding: '6px 10px',
|
||||||
|
fontSize: '0.75rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: '4px',
|
||||||
|
transition: 'background 0.15s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{expanded ? (
|
||||||
|
<>
|
||||||
|
<ChevronUp size={13} />
|
||||||
|
{t('plugin.commands.show_less', 'Show less')}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ChevronDown size={13} />
|
||||||
|
{t('plugin.commands.show_all', 'Show all {{count}} commands', { count: commands.length })}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
{commands.length > 4 && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setExpanded(!expanded)}
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
marginTop: '0.75rem',
|
|
||||||
background: 'rgba(255, 255, 255, 0.04)',
|
|
||||||
border: '1px solid var(--border)',
|
|
||||||
borderRadius: '6px',
|
|
||||||
color: 'var(--text)',
|
|
||||||
padding: '6px 10px',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
fontWeight: 600,
|
|
||||||
cursor: 'pointer',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
gap: '4px',
|
|
||||||
transition: 'background 0.15s ease',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{expanded ? (
|
|
||||||
<>
|
|
||||||
<ChevronUp size={13} />
|
|
||||||
{t('plugin.commands.show_less', 'Show less')}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ChevronDown size={13} />
|
|
||||||
{t('plugin.commands.show_all', 'Show all {{count}} commands', { count: commands.length })}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1503,8 +1506,11 @@ const PluginDetail = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="divider-sm" />
|
<PluginCommandsSidebarWidget commands={plugin.commands} />
|
||||||
|
|
||||||
|
{(user || reportDone) && <div className="divider-sm" />}
|
||||||
{user && !reportDone && (
|
{user && !reportDone && (
|
||||||
<>
|
<>
|
||||||
{!reportOpen ? (
|
{!reportOpen ? (
|
||||||
@ -1562,9 +1568,6 @@ const PluginDetail = () => {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<PluginCommandsSidebarWidget commands={plugin.commands} />
|
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -252,9 +252,10 @@ const AddPlugin = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateCommandField = (index: number, field: 'name' | 'permission', value: string) => {
|
const updateCommandField = (index: number, field: 'name' | 'permission', value: string) => {
|
||||||
|
const cleanValue = field === 'name' ? value.replace(/^\/+/, '') : value;
|
||||||
setCommands(prev => {
|
setCommands(prev => {
|
||||||
const next = [...prev];
|
const next = [...prev];
|
||||||
next[index] = { ...next[index], [field]: value };
|
next[index] = { ...next[index], [field]: cleanValue };
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -460,9 +461,9 @@ const AddPlugin = () => {
|
|||||||
setSubmitting(true);
|
setSubmitting(true);
|
||||||
try {
|
try {
|
||||||
const validCommands = commands
|
const validCommands = commands
|
||||||
.filter(c => c.name.trim().length > 0)
|
.filter(c => c.name.trim().replace(/^\/+/, '').length > 0)
|
||||||
.map((c, idx) => ({
|
.map((c, idx) => ({
|
||||||
name: c.name.trim(),
|
name: c.name.trim().replace(/^\/+/, ''),
|
||||||
permission: c.permission.trim() || undefined,
|
permission: c.permission.trim() || undefined,
|
||||||
description: c.descriptions,
|
description: c.descriptions,
|
||||||
display_order: idx,
|
display_order: idx,
|
||||||
@ -864,8 +865,9 @@ const AddPlugin = () => {
|
|||||||
<input
|
<input
|
||||||
className="mp-input"
|
className="mp-input"
|
||||||
style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', padding: '0.45rem 0.65rem' }}
|
style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', padding: '0.45rem 0.65rem' }}
|
||||||
placeholder="/spawn [player]"
|
placeholder="spawn [player]"
|
||||||
value={cmd.name}
|
value={cmd.name}
|
||||||
|
maxLength={64}
|
||||||
onChange={e => updateCommandField(idx, 'name', e.target.value)}
|
onChange={e => updateCommandField(idx, 'name', e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -879,6 +881,7 @@ const AddPlugin = () => {
|
|||||||
style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', padding: '0.45rem 0.65rem' }}
|
style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', padding: '0.45rem 0.65rem' }}
|
||||||
placeholder="pumpkin.command.spawn"
|
placeholder="pumpkin.command.spawn"
|
||||||
value={cmd.permission}
|
value={cmd.permission}
|
||||||
|
maxLength={128}
|
||||||
onChange={e => updateCommandField(idx, 'permission', e.target.value)}
|
onChange={e => updateCommandField(idx, 'permission', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -234,7 +234,7 @@ const StoreListing = ({ plugin, onSaved }: Props) => {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id: cmd.id,
|
id: cmd.id,
|
||||||
name: cmd.name || '',
|
name: (cmd.name || '').replace(/^\/+/, ''),
|
||||||
permission: cmd.permission || '',
|
permission: cmd.permission || '',
|
||||||
descriptions: descMap,
|
descriptions: descMap,
|
||||||
};
|
};
|
||||||
@ -253,9 +253,10 @@ const StoreListing = ({ plugin, onSaved }: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateCommandField = (index: number, field: 'name' | 'permission', value: string) => {
|
const updateCommandField = (index: number, field: 'name' | 'permission', value: string) => {
|
||||||
|
const cleanValue = field === 'name' ? value.replace(/^\/+/, '') : value;
|
||||||
setCommands(prev => {
|
setCommands(prev => {
|
||||||
const next = [...prev];
|
const next = [...prev];
|
||||||
next[index] = { ...next[index], [field]: value };
|
next[index] = { ...next[index], [field]: cleanValue };
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -311,10 +312,10 @@ const StoreListing = ({ plugin, onSaved }: Props) => {
|
|||||||
setSaveError(null);
|
setSaveError(null);
|
||||||
setSaveSuccess(false);
|
setSaveSuccess(false);
|
||||||
const validCommands = commands
|
const validCommands = commands
|
||||||
.filter(c => c.name.trim().length > 0)
|
.filter(c => c.name.trim().replace(/^\/+/, '').length > 0)
|
||||||
.map((c, idx) => ({
|
.map((c, idx) => ({
|
||||||
id: c.id,
|
id: c.id,
|
||||||
name: c.name.trim(),
|
name: c.name.trim().replace(/^\/+/, ''),
|
||||||
permission: c.permission.trim() || undefined,
|
permission: c.permission.trim() || undefined,
|
||||||
description: c.descriptions,
|
description: c.descriptions,
|
||||||
display_order: idx,
|
display_order: idx,
|
||||||
@ -685,8 +686,9 @@ const StoreListing = ({ plugin, onSaved }: Props) => {
|
|||||||
<input
|
<input
|
||||||
className="mp-input"
|
className="mp-input"
|
||||||
style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', padding: '0.45rem 0.65rem' }}
|
style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', padding: '0.45rem 0.65rem' }}
|
||||||
placeholder="/spawn [player]"
|
placeholder="spawn [player]"
|
||||||
value={cmd.name}
|
value={cmd.name}
|
||||||
|
maxLength={64}
|
||||||
onChange={e => updateCommandField(idx, 'name', e.target.value)}
|
onChange={e => updateCommandField(idx, 'name', e.target.value)}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
@ -700,6 +702,7 @@ const StoreListing = ({ plugin, onSaved }: Props) => {
|
|||||||
style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', padding: '0.45rem 0.65rem' }}
|
style={{ fontFamily: 'var(--font-mono)', fontSize: '0.82rem', padding: '0.45rem 0.65rem' }}
|
||||||
placeholder="pumpkin.command.spawn"
|
placeholder="pumpkin.command.spawn"
|
||||||
value={cmd.permission}
|
value={cmd.permission}
|
||||||
|
maxLength={128}
|
||||||
onChange={e => updateCommandField(idx, 'permission', e.target.value)}
|
onChange={e => updateCommandField(idx, 'permission', e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user