From b655042b0dc41055623d274257881312b94d6318 Mon Sep 17 00:00:00 2001 From: kieirra Date: Sat, 21 Feb 2026 20:35:13 +0100 Subject: [PATCH 1/6] feat(rules): Allow rule reordering --- src/components/rule-card.tsx | 30 ++++++++++++++++++- .../formatting-rules/formatting-rules.tsx | 7 ++++- .../hooks/use-formatting-rules.ts | 21 +++++++++++++ src/i18n/locales/fr.json | 2 ++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/components/rule-card.tsx b/src/components/rule-card.tsx index 27027ff..dba3e1d 100644 --- a/src/components/rule-card.tsx +++ b/src/components/rule-card.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { FormattingRule } from '../features/settings/formatting-rules/types'; import { Switch } from '@/components/switch'; -import { Trash2, Copy, ChevronDown, ChevronUp, Regex } from 'lucide-react'; +import { Trash2, Copy, ChevronDown, ChevronUp, Regex, ArrowUp, ArrowDown } from 'lucide-react'; import { useTranslation } from '@/i18n'; import { Button } from './button'; import { RuleFormFields } from './rule-form-fields'; @@ -15,6 +15,10 @@ interface RuleCardProps { ) => void; onDelete: (id: string) => void; onDuplicate: (id: string) => void; + onMoveUp: (id: string) => void; + onMoveDown: (id: string) => void; + isFirst: boolean; + isLast: boolean; } export const RuleCard: React.FC = ({ @@ -22,6 +26,10 @@ export const RuleCard: React.FC = ({ onUpdate, onDelete, onDuplicate, + onMoveUp, + onMoveDown, + isFirst, + isLast, }) => { const [isExpanded, setIsExpanded] = useState(false); const { t } = useTranslation(); @@ -63,6 +71,26 @@ export const RuleCard: React.FC = ({
+ + + +
@@ -71,26 +101,6 @@ export const RuleCard: React.FC = ({
- - - -
+ diff --git a/src/features/settings/formatting-rules/formatting-rules.tsx b/src/features/settings/formatting-rules/formatting-rules.tsx index ecaba1c..98e7568 100644 --- a/src/features/settings/formatting-rules/formatting-rules.tsx +++ b/src/features/settings/formatting-rules/formatting-rules.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { Page } from '@/components/page'; import { Typography } from '@/components/typography'; import { Switch } from '@/components/switch'; @@ -14,6 +15,63 @@ import { } from '../../../components/select'; import { NumberInput } from '@/components/number-input'; import { SettingsUI } from '@/components/settings-ui'; +import { + DndContext, + closestCenter, + DragEndEvent, + DragStartEvent, + DragOverlay, + PointerSensor, + KeyboardSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + SortableContext, + verticalListSortingStrategy, + useSortable, + sortableKeyboardCoordinates, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { FormattingRule } from './types'; + +interface SortableRuleCardProps { + rule: FormattingRule; + onUpdate: (id: string, updates: Partial>) => void; + onDelete: (id: string) => void; + onDuplicate: (id: string) => void; +} + +const SortableRuleCard = ({ rule, ...props }: SortableRuleCardProps) => { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id: rule.id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + }; + + return ( +
+ {isDragging ? ( +
+ ) : ( + + )} +
+ ); +}; export const FormattingRules = () => { const { t } = useTranslation(); @@ -25,9 +83,36 @@ export const FormattingRules = () => { updateRule, deleteRule, duplicateRule, - moveRule, + reorderRules, } = useFormattingRules(); + const [activeId, setActiveId] = useState(null); + + const sensors = useSensors( + useSensor(PointerSensor, { + activationConstraint: { distance: 8 }, + }), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }) + ); + + const handleDragStart = (event: DragStartEvent) => { + setActiveId(String(event.active.id)); + }; + + const handleDragEnd = (event: DragEndEvent) => { + setActiveId(null); + const { active, over } = event; + if (over != null && active.id !== over.id) { + reorderRules(String(active.id), String(over.id)); + } + }; + + const activeRule = activeId != null + ? settings.rules.find((rule) => rule.id === activeId) + : undefined; + if (isLoading) { return (
@@ -271,21 +356,40 @@ export const FormattingRules = () => {
{settings.rules.length > 0 && ( -
- {settings.rules.map((rule, index) => ( - moveRule(id, 'up')} - onMoveDown={(id) => moveRule(id, 'down')} - isFirst={index === 0} - isLast={index === settings.rules.length - 1} - /> - ))} -
+ + rule.id)} + strategy={verticalListSortingStrategy} + > +
+ {settings.rules.map((rule) => ( + + ))} +
+
+ + {activeRule != null ? ( + + ) : null} + +
)}
diff --git a/src/features/settings/formatting-rules/hooks/use-formatting-rules.ts b/src/features/settings/formatting-rules/hooks/use-formatting-rules.ts index 1a7e255..2176295 100644 --- a/src/features/settings/formatting-rules/hooks/use-formatting-rules.ts +++ b/src/features/settings/formatting-rules/hooks/use-formatting-rules.ts @@ -134,16 +134,15 @@ export const useFormattingRules = () => { [settings, saveSettings] ); - const moveRule = useCallback( - async (id: string, direction: 'up' | 'down') => { - const index = settings.rules.findIndex((rule) => rule.id === id); - if (index === -1) return; - - const targetIndex = direction === 'up' ? index - 1 : index + 1; - if (targetIndex < 0 || targetIndex >= settings.rules.length) return; + const reorderRules = useCallback( + async (activeId: string, overId: string) => { + const oldIndex = settings.rules.findIndex((rule) => rule.id === activeId); + const newIndex = settings.rules.findIndex((rule) => rule.id === overId); + if (oldIndex === -1 || newIndex === -1) return; const newRules = [...settings.rules]; - [newRules[index], newRules[targetIndex]] = [newRules[targetIndex], newRules[index]]; + const [moved] = newRules.splice(oldIndex, 1); + newRules.splice(newIndex, 0, moved); const newSettings = { ...settings, @@ -162,6 +161,6 @@ export const useFormattingRules = () => { updateRule, deleteRule, duplicateRule, - moveRule, + reorderRules, }; }; diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 08d191f..816443a 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -180,8 +180,7 @@ "Mode Name": "Nom du mode", "Mode name already exists": "Le nom du mode existe déjà", "Model": "Modèle", - "Move down": "Déplacer vers le bas", - "Move up": "Déplacer vers le haut", + "Reorder": "Réordonner", "Murmure": "Murmure", "Murmure removes all audio files after processing and only keeps your five latest transcriptions, stored locally on your device.": "Murmure supprime tous les fichiers audio après traitement et ne conserve que vos cinq dernières transcriptions, stockées localement.", "Murmure use default microphone to record your voice.": "Murmure utilise le microphone par défaut pour enregistrer votre voix.", From f3807408eda5f8f6e44a1b3d3ff4860a6049f7ee Mon Sep 17 00:00:00 2001 From: kieirra Date: Sat, 21 Feb 2026 21:22:21 +0100 Subject: [PATCH 4/6] fix: minor --- src/components/rule-card.tsx | 10 ++++------ src/i18n/locales/fr.json | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/components/rule-card.tsx b/src/components/rule-card.tsx index d8a3316..c3d7358 100644 --- a/src/components/rule-card.tsx +++ b/src/components/rule-card.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import { FormattingRule } from '../features/settings/formatting-rules/types'; import { Switch } from '@/components/switch'; import { Trash2, Copy, ChevronDown, ChevronUp, Regex, GripVertical } from 'lucide-react'; @@ -16,17 +16,15 @@ interface RuleCardProps { onDelete: (id: string) => void; onDuplicate: (id: string) => void; dragHandleProps?: Record; - isDragging?: boolean; } -export const RuleCard: React.FC = ({ +export const RuleCard = ({ rule, onUpdate, onDelete, onDuplicate, dragHandleProps, - isDragging, -}) => { +}: RuleCardProps) => { const [isExpanded, setIsExpanded] = useState(false); const { t } = useTranslation(); @@ -38,7 +36,7 @@ export const RuleCard: React.FC = ({ rule.enabled ? 'border-zinc-700 bg-zinc-800/25' : 'border-zinc-800 bg-zinc-900/50 opacity-60' - } ${isDragging ? 'opacity-50' : ''}`} + }`} data-testid={`rule-card-${rule.id}`} >
diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 816443a..9135c58 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -180,7 +180,6 @@ "Mode Name": "Nom du mode", "Mode name already exists": "Le nom du mode existe déjà", "Model": "Modèle", - "Reorder": "Réordonner", "Murmure": "Murmure", "Murmure removes all audio files after processing and only keeps your five latest transcriptions, stored locally on your device.": "Murmure supprime tous les fichiers audio après traitement et ne conserve que vos cinq dernières transcriptions, stockées localement.", "Murmure use default microphone to record your voice.": "Murmure utilise le microphone par défaut pour enregistrer votre voix.", @@ -227,8 +226,9 @@ "Refresh Models": "Rafraîchir les modèles", "Rename": "Renommer", "Rename Mode": "Renommer le mode", - "Smart": "Intelligent", + "Reorder": "Réordonner", "Report a bug": "Signaler un bug", + "Smart": "Intelligent", "Requires installing Ollama (free & open source)": "Requiert l'installation d'Ollama (gratuit et open source)", "Reset Tutorial": "Réinitialiser le tutoriel", "Save": "Enregistrer", From 082229d853b37b6002e131a3dc28b5fa44d52183 Mon Sep 17 00:00:00 2001 From: kieirra Date: Sat, 21 Feb 2026 21:33:49 +0100 Subject: [PATCH 5/6] fix: sonarQube --- .../settings/formatting-rules/formatting-rules.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/features/settings/formatting-rules/formatting-rules.tsx b/src/features/settings/formatting-rules/formatting-rules.tsx index 98e7568..3618498 100644 --- a/src/features/settings/formatting-rules/formatting-rules.tsx +++ b/src/features/settings/formatting-rules/formatting-rules.tsx @@ -110,8 +110,7 @@ export const FormattingRules = () => { }; const activeRule = activeId != null - ? settings.rules.find((rule) => rule.id === activeId) - : undefined; + && settings.rules.find((rule) => rule.id === activeId) || undefined; if (isLoading) { return ( @@ -380,14 +379,14 @@ export const FormattingRules = () => {
- {activeRule != null ? ( + {activeRule != null && ( - ) : null} + )} )} From 9a38189cfcaf0560635dcb793c51a209f9524f61 Mon Sep 17 00:00:00 2001 From: kieirra Date: Sat, 21 Feb 2026 21:43:31 +0100 Subject: [PATCH 6/6] doc:s update pr --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 728e575..2208562 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Murmure + # Murmure A privacy-first, open-source speech-to-text application that runs entirely on your machine, powered by a neural network via NVIDIA’s [Parakeet TDT 0.6B v3 model](https://huggingface.co/nvidia/parakeet-tdt-0.6b-v3) for fast, local transcription. Murmure turns your voice into text with no internet connection and zero data collection, and supports 25 European languages. @@ -132,9 +132,9 @@ See [CHANGELOG.md](./CHANGELOG.md). - [x] (1.8.0) feat(rules): Improve rules label to make sentences https://github.com/Kieirra/murmure/pull/163 https://github.com/Kieirra/murmure/issues/101 - [x] (1.8.0) feat(rules): Add a “?” helper in the “Replacement text” field (explain natural language input and real line breaks instead of `\n`) https://github.com/Kieirra/murmure/pull/163 - [x] (1.8.0) feat(rules): Short text correction — auto-lowercase and remove trailing punctuation for short transcriptions (1-2 words) +- [x] (1.8.0) feat(rules): Allow rule reordering https://github.com/Kieirra/murmure/pull/170 - [ ] (1.8.0) feat(overlay): Configure overlay size - [ ] (1.8.0) feat(rules): Add auto-send enter (https://github.com/Kieirra/murmure/pull/156) -- [ ] (1.8.0) feat(rules): Allow rule reordering https://github.com/Kieirra/murmure/issues/104 - [ ] (1.8.0) feat(llm): Allow llm mode reordering https://github.com/Kieirra/murmure/issues/104 - [ ] (1.8.0) feat(llm): Allow bypassing onboarding for people which have a remote ollama server - [ ] (1.8.0) feat(about): Improve UI (years, parakeet model name twice, etc.)