diff --git a/source/Generic/PlayNotes/Models/ExistingNotesImporter.cs b/source/Generic/PlayNotes/Models/ExistingNotesImporter.cs new file mode 100644 index 0000000000..1eba061ff5 --- /dev/null +++ b/source/Generic/PlayNotes/Models/ExistingNotesImporter.cs @@ -0,0 +1,163 @@ +using HtmlAgilityPack; +using Playnite.SDK; +using ReverseMarkdown; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using Markdig; + +namespace PlayNotes.Models +{ + public class ExistingNotesImporter : ObservableObject + { + private static readonly ILogger logger = LogManager.GetLogger(); + private readonly IPlayniteAPI _playniteApi; + private bool clearBaseNotes = false; + public bool ClearBaseNotes { get => clearBaseNotes; set => SetValue(ref clearBaseNotes, value); } + private bool useOverride = false; + public bool UseOverride { get => useOverride; set => SetValue(ref useOverride, value); } + private string overrideFilePath; + public string OverrideFilePath { get => overrideFilePath; set => SetValue(ref overrideFilePath, value); } + private PlayNotesSettings _settings; + + public ExistingNotesImporter(IPlayniteAPI playniteApi, PlayNotesSettings settings) + { + _playniteApi = playniteApi; + _settings = settings; + } + + public void ResetValues() + { + ClearBaseNotes = false; + } + + public bool ImportExistingNotes(MarkdownDatabaseItem databaseItem, CancellationToken cancelToken) + { + if (_playniteApi.MainView.SelectedGames?.Any() != true) + { + return false; + } + + string gameName = _playniteApi.MainView.SelectedGames.First().Name; + gameName = Regex.Replace(gameName, @"[^a-zA-Z0-9\s]", "").Trim(); + + string path; + if (!UseOverride) + { + path = Path.Combine(_settings.ExistingNotesFolderPath, gameName + ".md").Trim(); + } + else + { + path = OverrideFilePath; + } + + if (string.IsNullOrEmpty(path) || + !File.Exists(path)) + { + _playniteApi.Dialogs.ShowErrorMessage( + $"Could not find the notes file, {path}", + "Invalid file path"); + return false; + } + + if (!Path.GetExtension(path).Equals(".md", StringComparison.OrdinalIgnoreCase)) + { + _playniteApi.Dialogs.ShowErrorMessage( + "Extension not supported", + $"Invalid file extension. Only markdown files are supported."); + return false; + } + + try + { + string markdownContent = File.ReadAllText(path); + Dictionary sections = ExtractHeadingsAndSections(markdownContent, gameName); + + var notes = new List(); + foreach (var section in sections) + { + notes.Add(new PlayNote(section.Key, section.Value)); + } + + if (!notes.HasItems()) + { + logger.Debug($"Notes not found"); + _playniteApi.Dialogs.ShowErrorMessage( + "Could not import notes", + "Could not import notes"); + return false; + } + + if (ClearBaseNotes) + { + databaseItem.Notes.Clear(); + } + + foreach (var note in notes) + { + databaseItem.Notes.Add(note); + } + } + catch (Exception ex) + { + logger.Debug($"Exception reading file: {path}, exception: {ex}"); + _playniteApi.Dialogs.ShowErrorMessage($"Error while reading file {path}"); + return false; + } + + return true; + } + private static Dictionary ExtractHeadingsAndSections(string markdown, string gameName) + { + Dictionary sections = new Dictionary(); + + // Regex to match H1 and H2 headings and their content + Regex regex = new Regex(@"^(#{1,2})\s+(.*?)\n([\s\S]*?)(?=\n#{1,2} |\z)", RegexOptions.Multiline); + + // Check for the first chunk of text before any headings + string firstChunk = null; + int firstHeadingIndex = markdown.IndexOf("#"); // Find where the first heading starts + + if (firstHeadingIndex == -1) + { + if (!string.IsNullOrEmpty(markdown)) + { + sections[gameName] = markdown.Trim(); + } + } + else + { + if (firstHeadingIndex > 0) + { + firstChunk = markdown.Substring(0, firstHeadingIndex).Trim(); // Content before any heading + if (!string.IsNullOrEmpty(firstChunk)) + { + if (!string.IsNullOrEmpty(firstChunk)) + { + sections["First"] = firstChunk; + } + } + } + + // Extract and store sections for H1 and H2 headings + foreach (Match match in regex.Matches(markdown)) + { + string headingType = match.Groups[1].Value.Trim(); // # for H1, ## for H2 + string heading = match.Groups[2].Value.Trim(); + string content = match.Groups[3].Value.Trim(); + + if (!string.IsNullOrEmpty(content)) + { + sections[heading] = content; + } + } + } + + return sections; + } + } +} \ No newline at end of file diff --git a/source/Generic/PlayNotes/PlayNotes.cs b/source/Generic/PlayNotes/PlayNotes.cs index 3c2b0e0860..815c671ee8 100644 --- a/source/Generic/PlayNotes/PlayNotes.cs +++ b/source/Generic/PlayNotes/PlayNotes.cs @@ -31,7 +31,7 @@ public PlayNotes(IPlayniteAPI api) : base(api) Settings = new PlayNotesSettingsViewModel(this); Properties = new GenericPluginProperties { - HasSettings = false + HasSettings = true }; AddCustomElementSupport(new AddCustomElementSupportArgs diff --git a/source/Generic/PlayNotes/PlayNotes.csproj b/source/Generic/PlayNotes/PlayNotes.csproj index cd780d6f6d..1c82072820 100644 --- a/source/Generic/PlayNotes/PlayNotes.csproj +++ b/source/Generic/PlayNotes/PlayNotes.csproj @@ -42,6 +42,9 @@ ..\..\packages\LiteDB.4.1.4\lib\net40\LiteDB.dll + + ..\..\packages\Markdig.0.40.0\lib\net462\Markdig.dll + ..\..\packages\MdXaml.1.26.0\lib\net462\MdXaml.dll @@ -72,6 +75,9 @@ ..\..\packages\Microsoft.Extensions.Primitives.6.0.0\lib\net461\Microsoft.Extensions.Primitives.dll + + ..\..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll + ..\..\packages\PlayniteSDK.6.11.0\lib\net462\Playnite.SDK.dll @@ -89,8 +95,8 @@ ..\..\packages\System.Diagnostics.DiagnosticSource.6.0.0\lib\net461\System.Diagnostics.DiagnosticSource.dll - - ..\..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll + + ..\..\packages\System.Memory.4.5.5\lib\net461\System.Memory.dll @@ -229,6 +235,7 @@ Shared\DatabaseCommon\LiteDbRepository.cs + diff --git a/source/Generic/PlayNotes/PlayNotesSettings.cs b/source/Generic/PlayNotes/PlayNotesSettings.cs index 4c8839d5c2..559d484d31 100644 --- a/source/Generic/PlayNotes/PlayNotesSettings.cs +++ b/source/Generic/PlayNotes/PlayNotesSettings.cs @@ -1,4 +1,5 @@ using MdXaml; +using Newtonsoft.Json; using Playnite.SDK; using Playnite.SDK.Data; using System; @@ -10,6 +11,7 @@ using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; +using System.Windows.Forms; using System.Windows.Media; namespace PlayNotes @@ -20,7 +22,11 @@ public class PlayNotesSettings : ObservableObject public bool IsControlVisible { get => _isControlVisible; set => SetValue(ref _isControlVisible, value); } private Style _markdownStyle; + [JsonIgnore] public Style MarkdownStyle { get => _markdownStyle; set => SetValue(ref _markdownStyle, value); } + + private string _existingNotesFolderPath; + public string ExistingNotesFolderPath { get => _existingNotesFolderPath; set => SetValue(ref _existingNotesFolderPath, value); } } public class PlayNotesSettingsViewModel : ObservableObject, ISettings @@ -75,7 +81,7 @@ public PlayNotesSettingsViewModel(PlayNotes plugin) imageStyle.Setters.Add(new Setter(Image.StretchProperty, Stretch.Uniform)); imageStyle.Setters.Add(new Setter(Image.StretchDirectionProperty, StretchDirection.DownOnly)); - var maxWidthBinding = new Binding("ActualWidth"); + var maxWidthBinding = new System.Windows.Data.Binding("ActualWidth"); var relativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(MarkdownScrollViewer) @@ -136,5 +142,22 @@ public bool VerifySettings(out List errors) errors = new List(); return true; } + + public RelayCommand BrowseFolder + { + get => new RelayCommand(() => + { + using (FolderBrowserDialog folderDialog = new FolderBrowserDialog()) + { + folderDialog.Description = "Select a folder"; + folderDialog.ShowNewFolderButton = true; + + if (folderDialog.ShowDialog() == DialogResult.OK) + { + Settings.ExistingNotesFolderPath = folderDialog.SelectedPath; // Updates the bound property + } + } + }); + } } } \ No newline at end of file diff --git a/source/Generic/PlayNotes/PlayNotesSettingsView.xaml b/source/Generic/PlayNotes/PlayNotesSettingsView.xaml index a38cc77f31..4691ed462d 100644 --- a/source/Generic/PlayNotes/PlayNotesSettingsView.xaml +++ b/source/Generic/PlayNotes/PlayNotesSettingsView.xaml @@ -6,9 +6,10 @@ mc:Ignorable="d" d:DesignHeight="400" d:DesignWidth="600"> - - - - + + + +