fix(notes): let Shift+Enter break the line, and clear the leftovers around the editor
Shift+Enter did nothing because AcceptsReturn was only set from a style setter and never reached the box. Setting it on the element is half the fix: TextBox.OnKeyDown consumes Enter regardless of modifiers and runs before any bubbling handler, so the split chain would have died in its place. Enter is now taken in the tunnel phase and Shift+Enter is left to the box. Alongside, the notes list stops borrowing furniture it has no use for: no "0 open tasks" subtitle, no show-completed and list-settings buttons, and no details pane beside it — the editor spans that column instead of leaving it empty. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0f04b796f4
commit
b90c5855f4
@@ -808,7 +808,8 @@ public sealed partial class TasksIslandViewModel : ViewModelBase, IDisposable
|
|||||||
var running = Items.Count(i => i.Status == TaskStatus.Running);
|
var running = Items.Count(i => i.Status == TaskStatus.Running);
|
||||||
var review = Items.Count(i => i.Status == TaskStatus.Done && i.Branch != null);
|
var review = Items.Count(i => i.Status == TaskStatus.Done && i.Branch != null);
|
||||||
|
|
||||||
Subtitle = open == 1 ? "1 open task" : $"{open} open tasks";
|
// The notes list holds no tasks — an "0 open tasks" line under its header is just noise.
|
||||||
|
Subtitle = IsNotesList ? "" : open == 1 ? "1 open task" : $"{open} open tasks";
|
||||||
|
|
||||||
if (running > 0 || review > 0)
|
if (running > 0 || review > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -145,7 +145,14 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isWorkerLogVisible;
|
private bool _isWorkerLogVisible;
|
||||||
|
|
||||||
public bool ShowDetails => WindowWidth >= 1100;
|
/// <summary>The notes list has nothing to show a detail pane for, so the pane goes away and
|
||||||
|
/// the editor takes the space instead of leaving an empty column beside it.</summary>
|
||||||
|
public bool ShowDetails => WindowWidth >= 1100 && Tasks?.IsNotesList != true;
|
||||||
|
|
||||||
|
/// <summary>Widens the tasks island over the splitter and the details column when that column
|
||||||
|
/// is hidden — a fixed-width ColumnDefinition keeps its space even with an invisible child.</summary>
|
||||||
|
public int TasksColumnSpan => ShowDetails ? 1 : 3;
|
||||||
|
|
||||||
public bool ShowLists => WindowWidth >= 780;
|
public bool ShowLists => WindowWidth >= 780;
|
||||||
|
|
||||||
private readonly System.Timers.Timer _clearTimer = new(30_000) { AutoReset = false };
|
private readonly System.Timers.Timer _clearTimer = new(30_000) { AutoReset = false };
|
||||||
@@ -168,10 +175,16 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
|||||||
|
|
||||||
partial void OnWindowWidthChanged(double value)
|
partial void OnWindowWidthChanged(double value)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(ShowDetails));
|
NotifyDetailsVisibility();
|
||||||
OnPropertyChanged(nameof(ShowLists));
|
OnPropertyChanged(nameof(ShowLists));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NotifyDetailsVisibility()
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(ShowDetails));
|
||||||
|
OnPropertyChanged(nameof(TasksColumnSpan));
|
||||||
|
}
|
||||||
|
|
||||||
public void OnWorkerLogReceived(WorkerLogEntry entry)
|
public void OnWorkerLogReceived(WorkerLogEntry entry)
|
||||||
{
|
{
|
||||||
var hhmm = entry.TimestampUtc.ToLocalTime().ToString("HH:mm");
|
var hhmm = entry.TimestampUtc.ToLocalTime().ToString("HH:mm");
|
||||||
@@ -341,6 +354,10 @@ public sealed partial class IslandsShellViewModel : ViewModelBase, IDisposable
|
|||||||
Lists.SelectionChanged += (_, _) => _ = RefreshStaleWorkerCheckAsync();
|
Lists.SelectionChanged += (_, _) => _ = RefreshStaleWorkerCheckAsync();
|
||||||
Tasks.SelectionChanged += (_, _) => Details.Bind(Tasks.SelectedTask, Tasks.SelectionSource);
|
Tasks.SelectionChanged += (_, _) => Details.Bind(Tasks.SelectedTask, Tasks.SelectionSource);
|
||||||
Tasks.PrepRequested += () => Details.ShowPrep();
|
Tasks.PrepRequested += () => Details.ShowPrep();
|
||||||
|
Tasks.PropertyChanged += (_, e) =>
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(TasksIslandViewModel.IsNotesList)) NotifyDetailsVisibility();
|
||||||
|
};
|
||||||
Tasks.ErrorReported += FlashFooterError;
|
Tasks.ErrorReported += FlashFooterError;
|
||||||
Lists.ErrorReported += FlashFooterError;
|
Lists.ErrorReported += FlashFooterError;
|
||||||
Lists.SuccessReported += FlashFooterSuccess;
|
Lists.SuccessReported += FlashFooterSuccess;
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
<Setter Property="BorderThickness" Value="0"/>
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
<Setter Property="Padding" Value="4,3"/>
|
<Setter Property="Padding" Value="4,3"/>
|
||||||
<Setter Property="MinHeight" Value="0"/>
|
<Setter Property="MinHeight" Value="0"/>
|
||||||
<Setter Property="AcceptsReturn" Value="True"/>
|
|
||||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
|
||||||
</Style>
|
</Style>
|
||||||
<Style Selector="TextBox.note /template/ Border#PART_BorderElement">
|
<Style Selector="TextBox.note /template/ Border#PART_BorderElement">
|
||||||
<Setter Property="Background" Value="Transparent"/>
|
<Setter Property="Background" Value="Transparent"/>
|
||||||
@@ -77,8 +75,13 @@
|
|||||||
<Ellipse Grid.Column="0" Width="4" Height="4" Margin="0,0,10,0"
|
<Ellipse Grid.Column="0" Width="4" Height="4" Margin="0,0,10,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Fill="{DynamicResource TextFaintBrush}"/>
|
Fill="{DynamicResource TextFaintBrush}"/>
|
||||||
|
<!-- AcceptsReturn/TextWrapping are set here, not in the style above: TextBox
|
||||||
|
reads AcceptsReturn in its own OnKeyDown, which runs before any handler a
|
||||||
|
style or the XAML could attach. Enter is intercepted in the tunnel phase
|
||||||
|
(see code-behind) so the split still wins over the newline. -->
|
||||||
<TextBox Grid.Column="1" Classes="note" Text="{Binding Text, Mode=TwoWay}"
|
<TextBox Grid.Column="1" Classes="note" Text="{Binding Text, Mode=TwoWay}"
|
||||||
LostFocus="OnBulletLostFocus" KeyDown="OnBulletKeyDown"/>
|
AcceptsReturn="True" TextWrapping="Wrap"
|
||||||
|
LostFocus="OnBulletLostFocus"/>
|
||||||
<PathIcon Grid.Column="2" Width="12" Height="12" Margin="6,0"
|
<PathIcon Grid.Column="2" Width="12" Height="12" Margin="6,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsVisible="{Binding FromAgent}"
|
IsVisible="{Binding FromAgent}"
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ public partial class NotesEditorView : UserControl
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContextChanged += OnDataContextChanged;
|
DataContextChanged += OnDataContextChanged;
|
||||||
|
// Tunnel, not bubble: with AcceptsReturn on, TextBox.OnKeyDown consumes Enter (any
|
||||||
|
// modifier) before a bubbling handler ever sees it. Taking Enter in the tunnel phase
|
||||||
|
// keeps the split chain, and letting Shift+Enter through leaves the newline to the box.
|
||||||
|
AddHandler(KeyDownEvent, OnBulletKeyDown, RoutingStrategies.Tunnel);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDataContextChanged(object? sender, EventArgs e)
|
private void OnDataContextChanged(object? sender, EventArgs e)
|
||||||
@@ -33,7 +37,9 @@ public partial class NotesEditorView : UserControl
|
|||||||
|
|
||||||
private void OnBulletKeyDown(object? sender, KeyEventArgs e)
|
private void OnBulletKeyDown(object? sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is not TextBox { DataContext: NoteBulletViewModel bullet } box
|
// Source, not sender: the tunnel handler is attached to the whole view, so this also sees
|
||||||
|
// keys from the capture box at the top — which has no bullet behind it and is skipped.
|
||||||
|
if (e.Source is not TextBox { DataContext: NoteBulletViewModel bullet } box
|
||||||
|| DataContext is not NotesEditorViewModel vm) return;
|
|| DataContext is not NotesEditorViewModel vm) return;
|
||||||
|
|
||||||
// Shift+Enter falls through to the TextBox and breaks the line — a note can be several.
|
// Shift+Enter falls through to the TextBox and breaks the line — a note can be several.
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
<TextBlock Text="{Binding StatusPill}"/>
|
<TextBlock Text="{Binding StatusPill}"/>
|
||||||
</Border>
|
</Border>
|
||||||
<Button Classes="icon-btn" Classes.active="{Binding IsShowingCompleted}"
|
<Button Classes="icon-btn" Classes.active="{Binding IsShowingCompleted}"
|
||||||
|
IsVisible="{Binding !IsNotesList}"
|
||||||
Command="{Binding ToggleShowCompletedCommand}"
|
Command="{Binding ToggleShowCompletedCommand}"
|
||||||
ToolTip.Tip="{loc:Tr tasks.showCompletedTip}">
|
ToolTip.Tip="{loc:Tr tasks.showCompletedTip}">
|
||||||
<PathIcon Width="15" Height="15" Data="{StaticResource Icon.Eye}"/>
|
<PathIcon Width="15" Height="15" Data="{StaticResource Icon.Eye}"/>
|
||||||
@@ -60,7 +61,8 @@
|
|||||||
<Path Classes="icon-stroke" Data="{StaticResource Icon.PlanDay}"/>
|
<Path Classes="icon-stroke" Data="{StaticResource Icon.PlanDay}"/>
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Classes="icon-btn" Command="{Binding OpenListSettingsCommand}" ToolTip.Tip="{loc:Tr tasks.listSettingsTip}">
|
<Button Classes="icon-btn" IsVisible="{Binding !IsNotesList}"
|
||||||
|
Command="{Binding OpenListSettingsCommand}" ToolTip.Tip="{loc:Tr tasks.listSettingsTip}">
|
||||||
<PathIcon Width="15" Height="15" Data="{StaticResource Icon.Settings}"/>
|
<PathIcon Width="15" Height="15" Data="{StaticResource Icon.Settings}"/>
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -144,7 +144,7 @@
|
|||||||
ResizeDirection="Columns"
|
ResizeDirection="Columns"
|
||||||
ResizeBehavior="PreviousAndNext"/>
|
ResizeBehavior="PreviousAndNext"/>
|
||||||
|
|
||||||
<Border Grid.Column="2" Classes="island" Margin="3">
|
<Border Grid.Column="2" Grid.ColumnSpan="{Binding TasksColumnSpan}" Classes="island" Margin="3">
|
||||||
<islands:TasksIslandView DataContext="{Binding Tasks}"/>
|
<islands:TasksIslandView DataContext="{Binding Tasks}"/>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user