fix(workspace): English slash commands, short texts, wider menu
/image replaces /bild (aliases bild/foto keep working), shorter descriptions for /link, /image, /audio, /diktat; the menu is wider, the command never wraps and long descriptions are cut off. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1f63e08a73
commit
1632dd22c4
3 files changed
+17
-13
No files matched your search
@@ -200,7 +200,7 @@ export const HelpView: React.FC<HelpViewProps> = ({ onBack }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="p-1.5 bg-panel rounded border border-border flex items-center gap-1.5 text-main col-span-2">
|
<div className="p-1.5 bg-panel rounded border border-border flex items-center gap-1.5 text-main col-span-2">
|
||||||
<span className="text-accent font-bold">🖼️</span>
|
<span className="text-accent font-bold">🖼️</span>
|
||||||
<span>/bild (Bild hochladen, fotografieren, einfügen oder per Link; nur im leeren Punkt)</span>
|
<span>/image oder /bild (Bild hochladen, fotografieren, einfügen oder per Link; nur im leeren Punkt)</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="p-1.5 bg-panel rounded border border-border flex items-center gap-1.5 text-main col-span-2">
|
<div className="p-1.5 bg-panel rounded border border-border flex items-center gap-1.5 text-main col-span-2">
|
||||||
<span className="text-accent font-bold">🎙️</span>
|
<span className="text-accent font-bold">🎙️</span>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ interface ImagePickerProps {
|
|||||||
onCancel: () => void;
|
onCancel: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inline box an empty bullet turns into after /bild: pick a file (or the camera on phones), drop or
|
// Inline box an empty bullet turns into after /image: pick a file (or the camera on phones), drop or
|
||||||
// paste an image, or give a link the server copies. Nothing is synced until the upload succeeded.
|
// paste an image, or give a link the server copies. Nothing is synced until the upload succeeded.
|
||||||
export const ImagePicker: React.FC<ImagePickerProps> = ({ docId, onDone, onCancel }) => {
|
export const ImagePicker: React.FC<ImagePickerProps> = ({ docId, onDone, onCancel }) => {
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
|||||||
@@ -45,20 +45,22 @@ export interface SlashCommand {
|
|||||||
icon: React.ReactNode;
|
icon: React.ReactNode;
|
||||||
/** Only offered in an empty bullet; the bullet turns into an inline picker first */
|
/** Only offered in an empty bullet; the bullet turns into an inline picker first */
|
||||||
media?: boolean;
|
media?: boolean;
|
||||||
|
/** Further names that find the command (e.g. German ones) */
|
||||||
|
aliases?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const SLASH_COMMANDS: SlashCommand[] = [
|
const SLASH_COMMANDS: SlashCommand[] = [
|
||||||
{ type: 'todo', label: '/todo', desc: 'Aufgabe mit Checkbox', icon: <ListTodo className="w-3.5 h-3.5" /> },
|
{ type: 'todo', label: '/todo', desc: 'Aufgabe mit Checkbox', icon: <ListTodo className="w-3.5 h-3.5" /> },
|
||||||
{ type: 'link', label: '/link', desc: 'Notiz verknüpfen oder Web-Link einfügen', icon: <LinkIcon className="w-3.5 h-3.5" /> },
|
{ type: 'link', label: '/link', desc: 'Notiz oder Weblink', icon: <LinkIcon className="w-3.5 h-3.5" /> },
|
||||||
{ type: 'h1', label: '/h1', desc: 'Große Überschrift', icon: <Heading1 className="w-3.5 h-3.5" /> },
|
{ type: 'h1', label: '/h1', desc: 'Große Überschrift', icon: <Heading1 className="w-3.5 h-3.5" /> },
|
||||||
{ type: 'h2', label: '/h2', desc: 'Abschnitts-Überschrift', icon: <Heading2 className="w-3.5 h-3.5" /> },
|
{ type: 'h2', label: '/h2', desc: 'Abschnitts-Überschrift', icon: <Heading2 className="w-3.5 h-3.5" /> },
|
||||||
{ type: 'h3', label: '/h3', desc: 'Unterabschnitt', icon: <Heading3 className="w-3.5 h-3.5" /> },
|
{ type: 'h3', label: '/h3', desc: 'Unterabschnitt', icon: <Heading3 className="w-3.5 h-3.5" /> },
|
||||||
{ type: 'code', label: '/code', desc: 'Code-Block mit Syntax', icon: <Code className="w-3.5 h-3.5" /> },
|
{ type: 'code', label: '/code', desc: 'Code-Block mit Syntax', icon: <Code className="w-3.5 h-3.5" /> },
|
||||||
{ type: 'callout', label: '/callout', desc: 'Hinweisbox', icon: <MessageSquare className="w-3.5 h-3.5" /> },
|
{ type: 'callout', label: '/callout', desc: 'Hinweisbox', icon: <MessageSquare className="w-3.5 h-3.5" /> },
|
||||||
{ type: 'divider', label: '/divider', desc: 'Trennlinie', icon: <Minus className="w-3.5 h-3.5" /> },
|
{ type: 'divider', label: '/divider', desc: 'Trennlinie', icon: <Minus className="w-3.5 h-3.5" /> },
|
||||||
{ type: 'image', label: '/bild', desc: 'Bild hochladen, fotografieren oder verlinken (image)', icon: <ImageIcon className="w-3.5 h-3.5" />, media: true },
|
{ type: 'image', label: '/image', desc: 'Bilder hochladen oder verlinken', icon: <ImageIcon className="w-3.5 h-3.5" />, media: true, aliases: ['bild', 'foto'] },
|
||||||
{ type: 'audio', label: '/audio', desc: 'Sprachnotiz aufnehmen oder Audiodatei hochladen', icon: <Mic className="w-3.5 h-3.5" />, media: true },
|
{ type: 'audio', label: '/audio', desc: 'Sprachnotiz oder Audiodatei', icon: <Mic className="w-3.5 h-3.5" />, media: true, aliases: ['sprachnotiz'] },
|
||||||
{ type: 'dictation', label: '/diktat', desc: 'Sprechen statt tippen: Text direkt in den Punkt', icon: <AudioLines className="w-3.5 h-3.5" />, media: true },
|
{ type: 'dictation', label: '/diktat', desc: 'Sprechen statt tippen', icon: <AudioLines className="w-3.5 h-3.5" />, media: true, aliases: ['dictate'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
export interface OutlinerBlockProps {
|
export interface OutlinerBlockProps {
|
||||||
@@ -149,7 +151,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
|||||||
// A block someone else is editing can still get the cursor (read-only), e.g. to press Enter at its end
|
// A block someone else is editing can still get the cursor (read-only), e.g. to press Enter at its end
|
||||||
const canFocus = canEdit || (!!lockedBy && !!canAppendBelow);
|
const canFocus = canEdit || (!!lockedBy && !!canAppendBelow);
|
||||||
const [slashSearch, setSlashSearch] = useState<string | null>(null);
|
const [slashSearch, setSlashSearch] = useState<string | null>(null);
|
||||||
// Inline picker an empty bullet turns into after /bild (local only, nothing synced until done)
|
// Inline picker an empty bullet turns into after /image (local only, nothing synced until done)
|
||||||
const [mediaPicker, setMediaPicker] = useState<'image' | 'audio' | 'dictation' | null>(null);
|
const [mediaPicker, setMediaPicker] = useState<'image' | 'audio' | 'dictation' | null>(null);
|
||||||
const [slashIndex, setSlashIndex] = useState(0);
|
const [slashIndex, setSlashIndex] = useState(0);
|
||||||
const [showLinkModal, setShowLinkModal] = useState<boolean>(false);
|
const [showLinkModal, setShowLinkModal] = useState<boolean>(false);
|
||||||
@@ -352,7 +354,9 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
|||||||
(c) =>
|
(c) =>
|
||||||
(!c.media || emptyBullet) &&
|
(!c.media || emptyBullet) &&
|
||||||
(c.type !== 'dictation' || ['server', 'browser'].includes(currentVoiceConfig().mode)) &&
|
(c.type !== 'dictation' || ['server', 'browser'].includes(currentVoiceConfig().mode)) &&
|
||||||
(c.label.toLowerCase().includes(cmdQuery) || c.desc.toLowerCase().includes(cmdQuery))
|
(c.label.toLowerCase().includes(cmdQuery) ||
|
||||||
|
c.desc.toLowerCase().includes(cmdQuery) ||
|
||||||
|
(c.aliases || []).some((a) => a.includes(cmdQuery)))
|
||||||
);
|
);
|
||||||
}, [slashSearch, localContent]);
|
}, [slashSearch, localContent]);
|
||||||
|
|
||||||
@@ -366,7 +370,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cmd.media) {
|
if (cmd.media) {
|
||||||
// The typed "/bild" must not stay in the synced text; the block type changes only after the upload
|
// The typed "/image" must not stay in the synced text; the block type changes only after the upload
|
||||||
setLocalContent('');
|
setLocalContent('');
|
||||||
localContentRef.current = '';
|
localContentRef.current = '';
|
||||||
flushDebounce('');
|
flushDebounce('');
|
||||||
@@ -1208,7 +1212,7 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
|||||||
|
|
||||||
{/* Floating Slash Commands Menu */}
|
{/* Floating Slash Commands Menu */}
|
||||||
{slashSearch !== null && canEdit && (
|
{slashSearch !== null && canEdit && (
|
||||||
<div className="absolute left-0 top-8 z-40 w-56 panel-surface shadow-panel border border-border rounded-card p-1">
|
<div className="absolute left-0 top-8 z-40 w-72 max-w-[calc(100vw-2rem)] panel-surface shadow-panel border border-border rounded-card p-1">
|
||||||
<div className="px-2 py-1 text-[10px] font-bold uppercase tracking-wider text-faint border-b border-border mb-1">
|
<div className="px-2 py-1 text-[10px] font-bold uppercase tracking-wider text-faint border-b border-border mb-1">
|
||||||
Befehl auswählen
|
Befehl auswählen
|
||||||
</div>
|
</div>
|
||||||
@@ -1227,11 +1231,11 @@ export const OutlinerBlock: React.FC<OutlinerBlockProps> = ({
|
|||||||
: 'text-main hover:bg-panel-elevated'
|
: 'text-main hover:bg-panel-elevated'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5 shrink-0">
|
||||||
{cmd.icon}
|
{cmd.icon}
|
||||||
<span className="font-semibold">{cmd.label}</span>
|
<span className="font-semibold whitespace-nowrap">{cmd.label}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] opacity-75">{cmd.desc}</span>
|
<span className="ml-3 min-w-0 truncate text-right text-[10px] opacity-75">{cmd.desc}</span>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
{filteredSlashCommands.length === 0 && (
|
{filteredSlashCommands.length === 0 && (
|
||||||
|
|||||||
Reference in new issue
Block a user