diff --git a/LICENSE b/LICENSE index 169e9a6..b5eab0b 100644 --- a/LICENSE +++ b/LICENSE @@ -634,7 +634,7 @@ state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - Copyright (C) 2024 Ormentia Markus Contributors + Copyright (C) 2024 Ormentia Markle Contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -654,7 +654,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Ormentia Markus Copyright (C) 2024 Ormentia Markus Contributors + Ormentia Markle Copyright (C) 2024 Ormentia Markle Contributors This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/src/App.axaml b/src/App.axaml index 497ab9b..abd938d 100644 --- a/src/App.axaml +++ b/src/App.axaml @@ -2,7 +2,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" x:Class="MarkdownEditor.App" xmlns:local="using:MarkdownEditor" - Name="Ormentia Markus" + Name="Ormentia Markle" RequestedThemeVariant="Default"> diff --git a/src/App.axaml.cs b/src/App.axaml.cs index c98d7b3..df56265 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -34,9 +34,9 @@ public partial class App : Application var appMenu = NativeMenu.GetMenu(this); if (appMenu != null) { - // About Markus - var aboutItem = new NativeMenuItem { Header = "About Markus" }; - aboutItem.Click += AboutMarkus_Click; + // About Markle + var aboutItem = new NativeMenuItem { Header = "About Markle" }; + aboutItem.Click += AboutMarkle_Click; appMenu.Items.Add(aboutItem); // Separator @@ -48,10 +48,10 @@ public partial class App : Application // Separator appMenu.Items.Add(new NativeMenuItemSeparator()); - // Hide Ormentia Markus + // Hide Ormentia Markle appMenu.Items.Add(new NativeMenuItem { - Header = "Hide Ormentia Markus", + Header = "Hide Ormentia Markle", Gesture = KeyGesture.Parse("Cmd+H") }); @@ -71,7 +71,7 @@ public partial class App : Application // Quit appMenu.Items.Add(new NativeMenuItem { - Header = "Quit Ormentia Markus", + Header = "Quit Ormentia Markle", Gesture = KeyGesture.Parse("Cmd+Q") }); } @@ -125,9 +125,9 @@ public partial class App : Application } /// - /// Handles the About Markus menu item click event. + /// Handles the About Markle menu item click event. /// - private void AboutMarkus_Click(object? sender, EventArgs e) + private void AboutMarkle_Click(object? sender, EventArgs e) { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop && desktop.MainWindow != null) diff --git a/src/Assets/markus-logo.png b/src/Assets/markle-logo.png similarity index 100% rename from src/Assets/markus-logo.png rename to src/Assets/markle-logo.png diff --git a/src/Constants/AppConstants.cs b/src/Constants/AppConstants.cs index cefa83e..825e3ac 100644 --- a/src/Constants/AppConstants.cs +++ b/src/Constants/AppConstants.cs @@ -76,7 +76,7 @@ public static class AppConstants public static class Messages { public const string DefaultTabTitle = "Untitled"; - public const string DefaultEditorContent = "# Welcome to Markdown Editor\n\nStart editing..."; + public const string DefaultEditorContent = "# Welcome to Markle\n\nStart editing..."; public const string ErrorRenderingMarkdown = "Error rendering markdown"; public const string OpenFileDialogTitle = "Open Markdown File"; public const string SaveFileDialogTitle = "Save Markdown File"; diff --git a/src/Info.plist b/src/Info.plist index 5e0faec..bbc76d6 100644 --- a/src/Info.plist +++ b/src/Info.plist @@ -3,11 +3,11 @@ CFBundleName - Ormentia Markus + Ormentia Markle CFBundleDisplayName - Ormentia Markus + Ormentia Markle CFBundleIdentifier - com.ormentia.markus + com.ormentia.markle CFBundleVersion 1.0.0 CFBundlePackageType @@ -15,7 +15,7 @@ CFBundleSignature ???? CFBundleExecutable - OrmentiaMarkus + OrmentiaMarkle NSHighResolutionCapable NSPrincipalClass @@ -25,7 +25,7 @@ NSHumanReadableCopyright © 2025 Ormentia. All rights reserved. CFBundleGetInfoString - Markus 1.0.0 - A Simple Markdown Editor + Markle 1.0.0 - A Simple Markdown Editor CFBundleIconFile AppIcon CFBundleDocumentTypes diff --git a/src/MarkdownEditor.csproj b/src/MarkdownEditor.csproj index 55a6a56..3a733b1 100644 --- a/src/MarkdownEditor.csproj +++ b/src/MarkdownEditor.csproj @@ -6,13 +6,13 @@ true app.manifest true - Ormentia Markus - OrmentiaMarkus - Ormentia Markus + Ormentia Markle + OrmentiaMarkle + Ormentia Markle Assets\AppIcon.ico - Ormentia Markus - Ormentia Markus - com.ormentia.markus + Ormentia Markle + Ormentia Markle + com.ormentia.markle 1.0.0 1.0.0 diff --git a/src/Services/SessionService.cs b/src/Services/SessionService.cs index b535bd8..7d64d04 100644 --- a/src/Services/SessionService.cs +++ b/src/Services/SessionService.cs @@ -33,7 +33,7 @@ public class SessionService : ISessionService /// The path to the app data directory. private static string GetAppDataDirectory() { - var appName = "OrmentiaMarkus"; + var appName = "OrmentiaMarkle"; // Use LocalApplicationData for cross-platform compatibility // On Windows: %LOCALAPPDATA% diff --git a/src/ViewModels/MainWindowViewModel.cs b/src/ViewModels/MainWindowViewModel.cs index d8c687e..3f4da24 100644 --- a/src/ViewModels/MainWindowViewModel.cs +++ b/src/ViewModels/MainWindowViewModel.cs @@ -83,6 +83,15 @@ public partial class MainWindowViewModel : ViewModelBase SelectedTab = newTab; } + /// + /// Shows the find panel for the currently selected tab. + /// + [RelayCommand] + private void ShowFindPanel() + { + SelectedTab?.ShowFindPanelCommand.Execute(null); + } + /// /// Closes the specified tab. /// Maintains at least one open tab and updates selection appropriately. diff --git a/src/ViewModels/MarkdownTabViewModel.cs b/src/ViewModels/MarkdownTabViewModel.cs index c3959b6..a839299 100644 --- a/src/ViewModels/MarkdownTabViewModel.cs +++ b/src/ViewModels/MarkdownTabViewModel.cs @@ -31,6 +31,23 @@ public partial class MarkdownTabViewModel : ViewModelBase [ObservableProperty] private bool _isDirty = false; + [ObservableProperty] + private bool _isFindPanelVisible = false; + + [ObservableProperty] + private string _searchText = string.Empty; + + [ObservableProperty] + private string _searchMatchInfo = string.Empty; + + private int _currentMatchIndex = -1; + private System.Collections.Generic.List _searchMatches = new(); + + /// + /// Event raised when search should be performed in the view. + /// + public event EventHandler? SearchRequested; + /// /// Gets the text selection used for formatting operations. /// @@ -117,6 +134,22 @@ public partial class MarkdownTabViewModel : ViewModelBase OnPropertyChanged(nameof(DisplayTitle)); } + partial void OnSearchTextChanged(string value) + { + PerformSearch(); + } + + partial void OnIsPrettyViewChanged(bool value) + { + // Reset search when switching views + if (_currentMatchIndex >= 0) + { + _currentMatchIndex = -1; + _searchMatches.Clear(); + UpdateSearchMatchInfo(); + } + } + /// /// Loads a markdown file from disk. /// @@ -293,4 +326,189 @@ public partial class MarkdownTabViewModel : ViewModelBase get => Selection.End; set => Selection.End = value; } + + // Find/Search commands + + [RelayCommand] + private void ShowFindPanel() + { + IsFindPanelVisible = true; + if (string.IsNullOrEmpty(SearchText) && Selection.Length > 0) + { + // If there's selected text, use it as the search term + SearchText = _document.GetText(Selection.Start, Selection.Length); + } + } + + [RelayCommand] + private void CloseFindPanel() + { + IsFindPanelVisible = false; + SearchText = string.Empty; + _currentMatchIndex = -1; + _searchMatches.Clear(); + UpdateSearchMatchInfo(); + SearchRequested?.Invoke(this, new SearchEventArgs { ClearHighlight = true }); + } + + [RelayCommand] + private void FindNext() + { + if (string.IsNullOrWhiteSpace(SearchText)) + { + PerformSearch(); + return; + } + + if (_searchMatches.Count == 0) + { + PerformSearch(); + } + + if (_searchMatches.Count > 0) + { + _currentMatchIndex = (_currentMatchIndex + 1) % _searchMatches.Count; + NavigateToMatch(_currentMatchIndex); + } + } + + [RelayCommand] + private void FindPrevious() + { + if (string.IsNullOrWhiteSpace(SearchText)) + { + PerformSearch(); + return; + } + + if (_searchMatches.Count == 0) + { + PerformSearch(); + } + + if (_searchMatches.Count > 0) + { + _currentMatchIndex = (_currentMatchIndex - 1 + _searchMatches.Count) % _searchMatches.Count; + NavigateToMatch(_currentMatchIndex); + } + } + + private void PerformSearch() + { + if (string.IsNullOrWhiteSpace(SearchText)) + { + _searchMatches.Clear(); + _currentMatchIndex = -1; + UpdateSearchMatchInfo(); + SearchRequested?.Invoke(this, new SearchEventArgs { ClearHighlight = true }); + return; + } + + var searchText = SearchText; + var content = IsPrettyView ? SafeMarkdownContent : _document.Text; + + _searchMatches.Clear(); + _currentMatchIndex = -1; + + if (string.IsNullOrEmpty(content)) + { + UpdateSearchMatchInfo(); + return; + } + + // Case-insensitive search + var comparison = StringComparison.OrdinalIgnoreCase; + int index = 0; + + while ((index = content.IndexOf(searchText, index, comparison)) != -1) + { + _searchMatches.Add(index); + index += searchText.Length; + } + + UpdateSearchMatchInfo(); + + if (_searchMatches.Count > 0) + { + _currentMatchIndex = 0; + NavigateToMatch(0); + } + else + { + SearchRequested?.Invoke(this, new SearchEventArgs + { + SearchText = searchText, + Matches = _searchMatches, + CurrentMatchIndex = -1 + }); + } + } + + private void NavigateToMatch(int matchIndex) + { + if (matchIndex < 0 || matchIndex >= _searchMatches.Count) + return; + + var matchPosition = _searchMatches[matchIndex]; + var searchText = SearchText; + + if (IsPrettyView) + { + // For pretty view, notify the view to highlight the match + SearchRequested?.Invoke(this, new SearchEventArgs + { + SearchText = searchText, + Matches = _searchMatches, + CurrentMatchIndex = matchIndex, + MatchPosition = matchPosition + }); + } + else + { + // For raw view, set selection in the document + Selection.Start = matchPosition; + Selection.End = matchPosition + searchText.Length; + + // Notify the view to scroll to selection + SearchRequested?.Invoke(this, new SearchEventArgs + { + SearchText = searchText, + Matches = _searchMatches, + CurrentMatchIndex = matchIndex, + MatchPosition = matchPosition, + ScrollToSelection = true + }); + } + + UpdateSearchMatchInfo(); + } + + private void UpdateSearchMatchInfo() + { + if (string.IsNullOrWhiteSpace(SearchText)) + { + SearchMatchInfo = string.Empty; + } + else if (_searchMatches.Count == 0) + { + SearchMatchInfo = "No results"; + } + else + { + SearchMatchInfo = $"{_currentMatchIndex + 1} of {_searchMatches.Count}"; + } + } +} + +/// +/// Event arguments for search operations. +/// +public class SearchEventArgs : EventArgs +{ + public string SearchText { get; set; } = string.Empty; + public System.Collections.Generic.List Matches { get; set; } = new(); + public int CurrentMatchIndex { get; set; } = -1; + public int MatchPosition { get; set; } = -1; + public bool ScrollToSelection { get; set; } = false; + public bool ClearHighlight { get; set; } = false; } diff --git a/src/Views/AboutWindow.axaml b/src/Views/AboutWindow.axaml index 4bfec6e..45e27d6 100644 --- a/src/Views/AboutWindow.axaml +++ b/src/Views/AboutWindow.axaml @@ -4,7 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="500" x:Class="MarkdownEditor.Views.AboutWindow" - Title="About Markus" + Title="About Markle" Width="600" Height="500" WindowStartupLocation="CenterOwner" CanResize="False" @@ -17,12 +17,12 @@ Margin="0,40,0,40"> - - + + + + + + + + + + + + +