From cc68d3f3347057ae426f75988b97bb04a0ab7781 Mon Sep 17 00:00:00 2001 From: Sandro Date: Thu, 29 Oct 2026 22:48:35 +0100 Subject: [PATCH] fix(workspace): keep the cursor in a block that turns into a code block Converting via /code or ``` swapped the text field for the code editor's and left the focus nowhere, so further typing triggered app shortcuts. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ .../src/features/workspace/OutlinerBlock.tsx | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b04f2a1..b7f3d56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,10 @@ Das Format folgt [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), die V ### Added - Eigene Spracherkennung (optional): Mit `STT_URL` schickt MindSift Aufnahmen an einen selbst betriebenen Whisper-Server (OpenAI-kompatibel, z. B. speaches) statt an Google oder Microsoft. Einstellung nur per Umgebungsvariablen (`STT_*`) inklusive Grenzwerten; `docker-compose.yml` bringt den Dienst `whisper` als Profil mit, das Modell lädt MindSift beim ersten Start selbst. Administration → Server & Isolation zeigt den aktiven Modus und bietet eine Testaufnahme. In der Demo bleibt es bei der Browser-Erkennung. - API: `POST /voice/transcribe`, `GET /admin/voice`; `/auth/status` meldet `voice.mode`. +- Code-Blöcke in Notizen: Sprachauswahl (rund 50 Sprachen, bei Bedarf nachgeladen), Farben beim Tippen im Stil von VS Code Dark+/Light+ für alle Themes, „Kopieren“, Tab/Shift+Tab rückt ein und aus. `/code yaml` oder ```` ```yaml ```` legt die Sprache direkt fest. ### Changed +- Workspace-Seitenleiste wie ein Datei-Explorer: angeheftete Notizen oben, darunter Ordner und Notizen ohne Zwischenüberschriften. - Spracheingabe in Quick Dump, im mobilen Erfassen-Sheet und beim Halten des +-Knopfs folgt der Server-Einstellung: mit eigenem Whisper-Server funktioniert sie in allen Browsern (auch Firefox), mit Live-Wellen während der Aufnahme und „Wird erkannt…“ danach. Ohne Unterstützung im Browser oder mit `STT_DISABLED=true` ist das Mikrofon ausgeblendet statt einer Fehlermeldung. ## [0.6.1] - 2026-10-29 diff --git a/frontend/src/features/workspace/OutlinerBlock.tsx b/frontend/src/features/workspace/OutlinerBlock.tsx index eed0e4b..ce9056b 100644 --- a/frontend/src/features/workspace/OutlinerBlock.tsx +++ b/frontend/src/features/workspace/OutlinerBlock.tsx @@ -289,6 +289,20 @@ export const OutlinerBlock: React.FC = ({ adjustHeight(); }, [localContent, block.type, block.level, activeFocus, adjustHeight]); + // Turning the block into another type (slash menu, ``` or markdown shortcut) can swap the text field + // for a new one (e.g. the code editor); the cursor must stay in the block so typing goes on there + const refocusAfterTypeChangeRef = useRef(false); + useLayoutEffect(() => { + if (!refocusAfterTypeChangeRef.current) return; + refocusAfterTypeChangeRef.current = false; + const ta = textareaRef.current; + if (ta && document.activeElement !== ta) { + ta.focus(); + const end = ta.value.length; + ta.setSelectionRange(end, end); + } + }, [block.type]); + useEffect(() => { window.addEventListener('resize', adjustHeight); // The field can be measured while hidden (phone layout shows the editor only after selecting a note); @@ -335,6 +349,7 @@ export const OutlinerBlock: React.FC = ({ : undefined; setLocalContent(clean); localContentRef.current = clean; + if (cmd.type !== block.type) refocusAfterTypeChangeRef.current = true; flushDebounce(clean, { type: cmd.type, ...(langOverride !== undefined ? { lang: langOverride } : {}), @@ -423,6 +438,7 @@ export const OutlinerBlock: React.FC = ({ } if (typeOverride) { + refocusAfterTypeChangeRef.current = true; flushDebounce(val, { type: typeOverride, ...(langOverride !== undefined ? { lang: langOverride } : {}), @@ -578,6 +594,7 @@ export const OutlinerBlock: React.FC = ({ const resolvedLang = rawLang ? resolveLanguageId(rawLang) || rawLang.toLowerCase() : ''; setLocalContent(''); localContentRef.current = ''; + refocusAfterTypeChangeRef.current = true; flushDebounce('', { type: 'code', lang: resolvedLang,