From 52b0ceaa9517afb504060dca2f344bcf36b27e8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Wed, 5 Jun 2024 18:00:05 +0200 Subject: [PATCH 01/46] Wordpress: Enabled terminal output rendering --- .../Markdown/Modifiers/WordpressModifier.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Libs/BookGen.DomainServices/Markdown/Modifiers/WordpressModifier.cs b/Libs/BookGen.DomainServices/Markdown/Modifiers/WordpressModifier.cs index be2bb19d..a9fb82e9 100644 --- a/Libs/BookGen.DomainServices/Markdown/Modifiers/WordpressModifier.cs +++ b/Libs/BookGen.DomainServices/Markdown/Modifiers/WordpressModifier.cs @@ -35,6 +35,7 @@ public void Setup(MarkdownPipelineBuilder pipeline) public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { // Method intentionally left empty. + PipelineHelpers.SetupSyntaxRenderForWeb(renderer); } private void PipelineOnDocumentProcessed(MarkdownDocument document) From 01052a6ee0aded2667fcccc64519b9b5a2b97547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Wed, 12 Jun 2024 17:50:45 +0200 Subject: [PATCH 02/46] GUI: Exception when using check for updates --- Libs/BookGen.Gui/MenuEnums/MainMenuAction.cs | 2 -- Libs/BookGen.Gui/Properties/Resources.Designer.cs | 9 --------- Libs/BookGen.Gui/Properties/Resources.resx | 3 --- Prog/BookGen/ConsoleUi/MainMenu.cs | 12 ------------ 4 files changed, 26 deletions(-) diff --git a/Libs/BookGen.Gui/MenuEnums/MainMenuAction.cs b/Libs/BookGen.Gui/MenuEnums/MainMenuAction.cs index b31375d1..36732880 100644 --- a/Libs/BookGen.Gui/MenuEnums/MainMenuAction.cs +++ b/Libs/BookGen.Gui/MenuEnums/MainMenuAction.cs @@ -29,8 +29,6 @@ public enum MainMenuAction PreviewServer, [Text("ID_Stat")] Stat, - [Text("ID_Update")] - Update, [Text("ID_Help")] Help, [Text("ID_Exit")] diff --git a/Libs/BookGen.Gui/Properties/Resources.Designer.cs b/Libs/BookGen.Gui/Properties/Resources.Designer.cs index 3db503b7..99ee141b 100644 --- a/Libs/BookGen.Gui/Properties/Resources.Designer.cs +++ b/Libs/BookGen.Gui/Properties/Resources.Designer.cs @@ -213,15 +213,6 @@ internal static string ID_Stat { } } - /// - /// Looks up a localized string similar to Check for updates. - /// - internal static string ID_Update { - get { - return ResourceManager.GetString("ID_Update", resourceCulture); - } - } - /// /// Looks up a localized string similar to Validate config. /// diff --git a/Libs/BookGen.Gui/Properties/Resources.resx b/Libs/BookGen.Gui/Properties/Resources.resx index 9dd01794..7d044728 100644 --- a/Libs/BookGen.Gui/Properties/Resources.resx +++ b/Libs/BookGen.Gui/Properties/Resources.resx @@ -168,9 +168,6 @@ Statistics - - Check for updates - Validate config diff --git a/Prog/BookGen/ConsoleUi/MainMenu.cs b/Prog/BookGen/ConsoleUi/MainMenu.cs index 94503472..cba370f3 100644 --- a/Prog/BookGen/ConsoleUi/MainMenu.cs +++ b/Prog/BookGen/ConsoleUi/MainMenu.cs @@ -81,17 +81,6 @@ private bool StartModuleInWorkdir(string name) return true; } - private bool LaunchUpdater() - { - using (var p = new Process()) - { - p.StartInfo.WorkingDirectory = AppContext.BaseDirectory; - p.StartInfo.FileName = "BookGen.Update.exe"; - p.Start(); - } - return true; - } - private bool DoAction(MainMenuAction selection, Renderer renderer) { renderer.Clear(); @@ -108,7 +97,6 @@ private bool DoAction(MainMenuAction selection, Renderer renderer) MainMenuAction.Serve => StartModuleInWorkdir("serve"), MainMenuAction.PreviewServer => StartModuleInWorkdir("preview"), MainMenuAction.Stat => StartModuleInWorkdir("stat"), - MainMenuAction.Update => LaunchUpdater(), MainMenuAction.Help => ToggleHelp(), MainMenuAction.Exit => false, _ => throw new InvalidOperationException("Unknown command"), From dd68e98efbb4948ac722609c1ab2b180fff9ca5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Wed, 12 Jun 2024 17:56:45 +0200 Subject: [PATCH 03/46] Exception during shell init, when terminal profile is not installed --- Prog/BookGen/Commands/TerminalInstallCommand.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Prog/BookGen/Commands/TerminalInstallCommand.cs b/Prog/BookGen/Commands/TerminalInstallCommand.cs index b4050507..79ca1c15 100644 --- a/Prog/BookGen/Commands/TerminalInstallCommand.cs +++ b/Prog/BookGen/Commands/TerminalInstallCommand.cs @@ -25,7 +25,10 @@ public override int Execute(TerminalInstallArguments arguments, string[] context if (arguments.CheckInstall) { bool installed = Directory.Exists(TerminalProfileInstaller.TerminalFragmentPath); - installed &= Directory.GetFiles(TerminalProfileInstaller.TerminalFragmentPath, "*.json").Length > 0; + if (installed) + { + installed &= Directory.GetFiles(TerminalProfileInstaller.TerminalFragmentPath, "*.json").Length > 0; + } return installed ? Constants.Succes : Constants.GeneralError; } From 033ab5f079fd4bb887714a4739c31504d70999da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Wed, 12 Jun 2024 21:55:30 +0200 Subject: [PATCH 04/46] Reworked CDG command --- Prog/BookGen.Shell/BookGen.Shell.csproj | 5 - Prog/BookGen.Shell/Cdg/CdgSelector.cs | 217 ++++++++++++++++++ Prog/BookGen.Shell/Cdg/DirectoriesProvider.cs | 102 -------- .../Cdg/DirectorySelectorMenu.cs | 58 ----- Prog/BookGen.Shell/Cdg/SelectionItemAction.cs | 11 + Prog/BookGen.Shell/Cdg/SelectionItemBase.cs | 19 ++ .../Cdg/SelectionItemDirectory.cs | 11 + .../BookGen.Shell/Cdg/SelectionItemFactory.cs | 115 ++++++++++ .../Cdg/SelectorNameConverter.cs | 37 --- Prog/BookGen.Shell/Commands/CdgCommand.cs | 52 +---- 10 files changed, 377 insertions(+), 250 deletions(-) create mode 100644 Prog/BookGen.Shell/Cdg/CdgSelector.cs delete mode 100644 Prog/BookGen.Shell/Cdg/DirectoriesProvider.cs delete mode 100644 Prog/BookGen.Shell/Cdg/DirectorySelectorMenu.cs create mode 100644 Prog/BookGen.Shell/Cdg/SelectionItemAction.cs create mode 100644 Prog/BookGen.Shell/Cdg/SelectionItemBase.cs create mode 100644 Prog/BookGen.Shell/Cdg/SelectionItemDirectory.cs create mode 100644 Prog/BookGen.Shell/Cdg/SelectionItemFactory.cs delete mode 100644 Prog/BookGen.Shell/Cdg/SelectorNameConverter.cs diff --git a/Prog/BookGen.Shell/BookGen.Shell.csproj b/Prog/BookGen.Shell/BookGen.Shell.csproj index bf252728..8f846ef7 100644 --- a/Prog/BookGen.Shell/BookGen.Shell.csproj +++ b/Prog/BookGen.Shell/BookGen.Shell.csproj @@ -28,11 +28,6 @@ - - - - - True diff --git a/Prog/BookGen.Shell/Cdg/CdgSelector.cs b/Prog/BookGen.Shell/Cdg/CdgSelector.cs new file mode 100644 index 00000000..50e3eb59 --- /dev/null +++ b/Prog/BookGen.Shell/Cdg/CdgSelector.cs @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------------- +// (c) 2023-2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Spectre.Console; + +using System.Diagnostics.CodeAnalysis; + +namespace BookGen.Shell.Cdg; + +internal sealed class CdgSelector +{ + private readonly SelectionItemAction[] _menuItems; + private readonly List _directories; + private bool _canRun; + + public string _currentPath { get; set; } + + private void SetDirectories(IEnumerable items) + { + _directories.Clear(); + _directories.AddRange(items); + } + + public CdgSelector(string startDirectory) + { + _currentPath = startDirectory; + _directories = new List(); + _menuItems = new SelectionItemAction[] + { + new SelectionItemAction + { + DisplayString = "Select current directory", + Icon = ":right_arrow:", + Action = () => + { + Environment.SetEnvironmentVariable("cdgPath", _currentPath, EnvironmentVariableTarget.User); + _canRun = false; + }, + Color = Color.Red, + }, + new SelectionItemAction + { + DisplayString = "Up one directory", + Icon = ":upwards_button:", + Action = () => + { + if (TryGetUpOneDirectory(_currentPath, out string newPath)) + { + _currentPath = newPath; + SetDirectories(SelectionItemFactory.CreateFromDirectories(Directory.GetDirectories(newPath))); + } + }, + Color = Color.Olive, + }, + new SelectionItemAction + { + DisplayString = "Jump home", + Icon = ":house:", + Action = () => + { + _currentPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + SetDirectories(SelectionItemFactory.CreateFromDirectories(Directory.GetDirectories(_currentPath))); + }, + Color = Color.Yellow, + }, + new SelectionItemAction + { + DisplayString = "Known folders", + Icon = ":file_cabinet: ", + Action = () => + { + _currentPath = "Special folders"; + SetDirectories(SelectionItemFactory.GetSpecialFolders()); + }, + Color = Color.Lime, + }, + new SelectionItemAction + { + DisplayString = "Path folders", + Icon = ":file_cabinet: ", + Action = () => + { + _currentPath = "Path folders"; + SetDirectories(SelectionItemFactory.GetPathDirectories()); + }, + Color = Color.Lime, + }, + new SelectionItemAction + { + DisplayString = "Drives", + Icon = ":desktop_computer: ", + Action = () => + { + _currentPath = "Drives"; + SetDirectories(SelectionItemFactory.GetDrives()); + }, + Color = Color.Fuchsia, + }, + }; + } + + private static bool CanAccess(string path, [NotNullWhen(true)] out string[]? subdirs) + { + try + { + subdirs = Directory.GetDirectories(path); + return true; + } + catch (Exception) + { + subdirs = null; + return false; + } + } + + private static bool TryGetUpOneDirectory(string currentPath, out string newPath) + { + if (string.IsNullOrEmpty(currentPath)) + { + newPath = string.Empty; + return false; + } + + var directory = new DirectoryInfo(currentPath); + if (directory.Parent is null) + { + newPath = string.Empty; + return false; + } + + newPath = directory.Parent.FullName; + return true; + } + + private static string Render(SelectionItemBase item) + { + string seperator = " "; + if (item.IsMenu) + seperator = " "; + + return $"{item.Icon}{seperator}[{item.Color.ToMarkup()}]{item.DisplayString.EscapeMarkup()}[/]"; + } + + private static SelectionItemBase CreateGroup(string groupName, string icon) + { + return new SelectionItemBase + { + DisplayString = groupName, + Icon = icon, + IsMenu = true, + Color = Spectre.Console.Color.Red + }; + } + + private SelectionPrompt CreateSelection() + { + SelectionPrompt selector = new() + { + Converter = Render, + Title = $"Location: {_currentPath}", + WrapAround = true, + PageSize = Console.WindowHeight - 4, + Mode = SelectionMode.Leaf + }; + + selector.AddChoiceGroup(CreateGroup("Menu", ":fire:"), _menuItems); + if (_directories.Count > 0) + { + selector.AddChoiceGroup(CreateGroup("Directories", ":file_folder:"), _directories); + } + + return selector; + } + + public async Task ShowMenu() + { + SetDirectories(SelectionItemFactory.CreateFromDirectories(Directory.GetDirectories(_currentPath))); + _canRun = true; + while (_canRun) + { + try + { + AnsiConsole.Clear(); + var menu = CreateSelection(); + var selected = await menu.ShowAsync(AnsiConsole.Console, CancellationToken.None); + if (selected is SelectionItemDirectory directory) + { + if (CanAccess(directory.Path, out string[]? subdirs)) + { + _currentPath = directory.Path; + SetDirectories(SelectionItemFactory.CreateFromDirectories(subdirs)); + } + else + { + AnsiConsole.Clear(); + AnsiConsole.MarkupInterpolated($"[red]Cannot access {directory.Path}[/]"); + } + } + else if (selected is SelectionItemAction action) + { + action.Action(); + } + } + catch (Exception ex) + { +#if DEBUG + AnsiConsole.WriteException(ex); +#endif + AnsiConsole.WriteLine(ex.Message); + var confirm = new ConfirmationPrompt("Press a key to continue").HideChoices(); + await confirm.ShowAsync(AnsiConsole.Console, CancellationToken.None); + } + } + } +} \ No newline at end of file diff --git a/Prog/BookGen.Shell/Cdg/DirectoriesProvider.cs b/Prog/BookGen.Shell/Cdg/DirectoriesProvider.cs deleted file mode 100644 index 0a5c4efb..00000000 --- a/Prog/BookGen.Shell/Cdg/DirectoriesProvider.cs +++ /dev/null @@ -1,102 +0,0 @@ -//----------------------------------------------------------------------------- -// (c) 2023 Ruzsinszki Gábor -// This code is licensed under MIT license (see LICENSE for details) -//----------------------------------------------------------------------------- - -using TextResources = BookGen.Shell.Properties.Resources; - -namespace BookGen.Shell.Cdg; - -internal sealed class DirectoriesProvider -{ - private readonly Dictionary _knownFolders; - - public DirectoriesProvider() - { - _knownFolders = new Dictionary(); - foreach (var folder in Enum.GetValues().Distinct()) - { - string path = Environment.GetFolderPath(folder); - if (!string.IsNullOrEmpty(path)) - { - _knownFolders.Add($"|-{folder}", path); - } - } - } - - public static bool PathIsRootDirString(string input) => input == nameof(TextResources._MenuSelectorRootDir_30); - - public static bool PathIsCurrentDirString(string input) => input == nameof(TextResources._MenuSelectorCurrentDir_10); - - public static bool PathIsHomeDirString(string input) => input == nameof(TextResources._MenuSelectorHomeDir_35); - - public static bool PathIsKnownDirsString(string input) => input == nameof(TextResources._MenuSelectorKnownDirs_40); - - public bool TryKnownFolder(string selected, out string newFolder) - { - if (_knownFolders.TryGetValue(selected, out string? value)) - { - newFolder = value; - return true; - } - newFolder = string.Empty; - return false; - } - - public static bool TryUpOneDir(string dir, string path, out string oneDirUp) - { - if (dir == nameof(TextResources._MenuSelectorUpOneDir_20)) - { - DirectoryInfo di = new(path); - var parent = di.Parent; - if (parent != null) - { - oneDirUp = parent.FullName; - } - else - { - oneDirUp = nameof(TextResources._MenuSelectorRootDir_30); - } - return true; - } - oneDirUp = path; - return false; - } - - public IEnumerable GetSubdirs(string workDir, bool showHidden) - { - if (PathIsRootDirString(workDir)) - { - return GetDrives(); - } - else if (PathIsKnownDirsString(workDir)) - { - return _knownFolders.Select(x => x.Key).Order(); - } - return GetDirectories(workDir, showHidden); - } - - private static IEnumerable GetDrives() - { - foreach (var drive in DriveInfo.GetDrives()) - { - if (drive.IsReady) - yield return drive.Name; - } - } - - private static IEnumerable GetDirectories(string workDir, bool showHidden) - { - DirectoryInfo directory = new(workDir); - - foreach (var subdir in directory.GetDirectories()) - { - if (subdir.Attributes.HasFlag(FileAttributes.Hidden) - && !showHidden) - { - continue; - } - yield return subdir.FullName; - } - } -} diff --git a/Prog/BookGen.Shell/Cdg/DirectorySelectorMenu.cs b/Prog/BookGen.Shell/Cdg/DirectorySelectorMenu.cs deleted file mode 100644 index d9494992..00000000 --- a/Prog/BookGen.Shell/Cdg/DirectorySelectorMenu.cs +++ /dev/null @@ -1,58 +0,0 @@ -//----------------------------------------------------------------------------- -// (c) 2023 Ruzsinszki Gábor -// This code is licensed under MIT license (see LICENSE for details) -//----------------------------------------------------------------------------- - -using Spectre.Console; - -using TextResources = BookGen.Shell.Properties.Resources; - -namespace BookGen.Shell.Cdg; - -internal sealed class DirectorySelectorMenu -{ - private readonly SelectorNameConverter _converter; - public string CurrentPath { get; set; } - - public DirectorySelectorMenu(string startDir) - { - CurrentPath = startDir; - _converter = new SelectorNameConverter(); - } - - public SelectionPrompt CreateSelection(IEnumerable items) - { - SelectionPrompt selector = new() - { - Converter = _converter.Convert, - Title = string.Format(TextResources.MenuTitle, DisplayPath(CurrentPath)), - WrapAround = true, - PageSize = Console.WindowHeight - 4, - Mode = SelectionMode.Leaf, - }; - selector.AddChoiceGroup(TextResources.GroupSpecials, GetSpecialItems()); - if (items.Any()) - { - selector.AddChoiceGroup(TextResources.GroupDirectories, items); - } - return selector; - } - - private static IEnumerable GetSpecialItems() - { - yield return nameof(TextResources._MenuSelectorCurrentDir_10); - yield return nameof(TextResources._MenuSelectorUpOneDir_20); - yield return nameof(TextResources._MenuSelectorRootDir_30); - yield return nameof(TextResources._MenuSelectorHomeDir_35); - yield return nameof(TextResources._MenuSelectorKnownDirs_40); - } - - private static string DisplayPath(string currentPath) - { - return currentPath switch - { - nameof(TextResources._MenuSelectorRootDir_30) => TextResources.PathNameRootDir, - _ => currentPath, - }; - } -} diff --git a/Prog/BookGen.Shell/Cdg/SelectionItemAction.cs b/Prog/BookGen.Shell/Cdg/SelectionItemAction.cs new file mode 100644 index 00000000..8c7d47a5 --- /dev/null +++ b/Prog/BookGen.Shell/Cdg/SelectionItemAction.cs @@ -0,0 +1,11 @@ +//----------------------------------------------------------------------------- +// (c) 2023-2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +namespace BookGen.Shell.Cdg; + +internal sealed class SelectionItemAction : SelectionItemBase +{ + public required Action Action { get; init; } +} diff --git a/Prog/BookGen.Shell/Cdg/SelectionItemBase.cs b/Prog/BookGen.Shell/Cdg/SelectionItemBase.cs new file mode 100644 index 00000000..6e459d01 --- /dev/null +++ b/Prog/BookGen.Shell/Cdg/SelectionItemBase.cs @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------------- +// (c) 2023-2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Spectre.Console; + +using System.Diagnostics; + +namespace BookGen.Shell.Cdg; + +[DebuggerDisplay("{DisplayString}")] +internal class SelectionItemBase +{ + public required string DisplayString { get; init; } + public required string Icon { get; init; } + public required Color Color { get; init; } + public bool IsMenu { get; init; } = false; +} diff --git a/Prog/BookGen.Shell/Cdg/SelectionItemDirectory.cs b/Prog/BookGen.Shell/Cdg/SelectionItemDirectory.cs new file mode 100644 index 00000000..dd6c940e --- /dev/null +++ b/Prog/BookGen.Shell/Cdg/SelectionItemDirectory.cs @@ -0,0 +1,11 @@ +//----------------------------------------------------------------------------- +// (c) 2023-2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +namespace BookGen.Shell.Cdg; + +internal sealed class SelectionItemDirectory : SelectionItemBase +{ + public required string Path { get; init; } +} diff --git a/Prog/BookGen.Shell/Cdg/SelectionItemFactory.cs b/Prog/BookGen.Shell/Cdg/SelectionItemFactory.cs new file mode 100644 index 00000000..cc35d990 --- /dev/null +++ b/Prog/BookGen.Shell/Cdg/SelectionItemFactory.cs @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------------- +// (c) 2023-2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; + +namespace BookGen.Shell.Cdg; + +internal static partial class SelectionItemFactory +{ + public static IEnumerable GetDrives() + { + static string GetVolumeLabel(DriveInfo drive) + { + if (drive.IsReady) + { + return drive.VolumeLabel; + } + return string.Empty; + } + + var drives = DriveInfo.GetDrives(); + foreach (var drive in drives) + { + yield return new SelectionItemDirectory + { + DisplayString = $"{drive.Name} - {GetVolumeLabel(drive)}", + Icon = GetIcon(drive.DriveType), + Color = Spectre.Console.Color.Blue, + Path = drive.Name + }; + } + } + + public static IEnumerable CreateFromDirectories(string[] directories) + { + foreach (var directory in directories) + { + yield return new SelectionItemDirectory + { + DisplayString = Path.GetFileName(directory), + Icon = ":open_file_folder:", + Color = Spectre.Console.Color.Yellow, + Path = directory + }; + } + } + + public static IEnumerable GetSpecialFolders() + { + foreach (var specialFolder in Enum.GetValues()) + { + var path = Environment.GetFolderPath(specialFolder); + if (!string.IsNullOrEmpty(path)) + { + yield return new SelectionItemDirectory + { + DisplayString = $"{Humanize(specialFolder)}", + Icon = ":up_right_arrow:", + Color = Spectre.Console.Color.Green, + Path = path + }; + } + } + } + + private static string Humanize(Environment.SpecialFolder specialFolder) + { + var original = specialFolder.ToString(); + return EnumNameHumanizer().Replace(original, " $1").TrimStart(); + } + + public static IEnumerable GetPathDirectories() + { + char seperator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ';' : ':'; + var directories = Environment.GetEnvironmentVariable("PATH") + ?.Split(seperator, StringSplitOptions.RemoveEmptyEntries) + ?? Array.Empty(); + + foreach (var directory in directories) + { + if (Directory.Exists(directory)) + { + yield return new SelectionItemDirectory + { + DisplayString = directory, + Icon = ":open_file_folder:", + Color = Spectre.Console.Color.Yellow, + Path = directory + }; + } + } + } + + private static string GetIcon(DriveType driveType) + { + return driveType switch + { + DriveType.Fixed => ":computer_disk:", + DriveType.Unknown => ":white_question_mark:", + DriveType.NoRootDirectory => ":open_file_folder:", + DriveType.Removable => ":floppy_disk:", + DriveType.Network => ":link:", + DriveType.CDRom => ":optical_disk:", + DriveType.Ram => ":memo:", + _ => throw new UnreachableException(), + }; + } + + [GeneratedRegex("([A-Z0-9]{1,3}|[0-9]+)")] + private static partial Regex EnumNameHumanizer(); +} diff --git a/Prog/BookGen.Shell/Cdg/SelectorNameConverter.cs b/Prog/BookGen.Shell/Cdg/SelectorNameConverter.cs deleted file mode 100644 index cfa73ff2..00000000 --- a/Prog/BookGen.Shell/Cdg/SelectorNameConverter.cs +++ /dev/null @@ -1,37 +0,0 @@ -//----------------------------------------------------------------------------- -// (c) 2023 Ruzsinszki Gábor -// This code is licensed under MIT license (see LICENSE for details) -//----------------------------------------------------------------------------- - -using TextResources = BookGen.Shell.Properties.Resources; - -namespace BookGen.Shell.Cdg; - -internal sealed class SelectorNameConverter -{ - private readonly string[] _drives; - - public SelectorNameConverter() - { - _drives = Environment.GetLogicalDrives(); - } - - public string Convert(string arg) - { - if (_drives.Contains(arg)) - { - DriveInfo di = new(arg); - return $"{di.Name} - {di.VolumeLabel}"; - } - - return arg switch - { - nameof(TextResources._MenuSelectorCurrentDir_10) => TextResources._MenuSelectorCurrentDir_10, - nameof(TextResources._MenuSelectorUpOneDir_20) => TextResources._MenuSelectorUpOneDir_20, - nameof(TextResources._MenuSelectorRootDir_30) => TextResources._MenuSelectorRootDir_30, - nameof(TextResources._MenuSelectorHomeDir_35) => TextResources._MenuSelectorHomeDir_35, - nameof(TextResources._MenuSelectorKnownDirs_40) => TextResources._MenuSelectorKnownDirs_40, - _ => Path.GetFileName(arg) == string.Empty ? arg : Path.GetFileName(arg), - }; - } -} diff --git a/Prog/BookGen.Shell/Commands/CdgCommand.cs b/Prog/BookGen.Shell/Commands/CdgCommand.cs index 2a89779d..7f989315 100644 --- a/Prog/BookGen.Shell/Commands/CdgCommand.cs +++ b/Prog/BookGen.Shell/Commands/CdgCommand.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// (c) 2023 Ruzsinszki Gábor +// (c) 2023-2024 Ruzsinszki Gábor // This code is licensed under MIT license (see LICENSE for details) //----------------------------------------------------------------------------- @@ -7,8 +7,6 @@ using BookGen.Cli.Annotations; using BookGen.Shell.Cdg; -using Spectre.Console; - namespace BookGen.Shell.Commands; [CommandName("cdg")] @@ -16,50 +14,8 @@ internal sealed class CdgCommand : AsyncCommand { public override async Task Execute(CdgArguments arguments, string[] context) { - var directories = new DirectoriesProvider(); - var selector = new DirectorySelectorMenu(arguments.Folder); - - while (true) - { - try - { - AnsiConsole.Clear(); - IEnumerable items = directories.GetSubdirs(selector.CurrentPath, arguments.ShowHidden); - SelectionPrompt menu = selector.CreateSelection(items); - string selected = await menu.ShowAsync(AnsiConsole.Console, CancellationToken.None); - - if (DirectoriesProvider.PathIsCurrentDirString(selected)) - { - Environment.SetEnvironmentVariable("cdgPath", selector.CurrentPath, EnvironmentVariableTarget.User); - return 0; - } - else if (DirectoriesProvider.PathIsHomeDirString(selected)) - { - selector.CurrentPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - } - else if (directories.TryKnownFolder(selected, out string newPath)) - { - selector.CurrentPath = newPath; - } - else if (DirectoriesProvider.TryUpOneDir(selected, selector.CurrentPath, out newPath)) - { - selector.CurrentPath = newPath; - } - else - { - selector.CurrentPath = selected; - } - - } - catch (Exception ex) - { -#if DEBUG - AnsiConsole.WriteException(ex); -#endif - AnsiConsole.WriteLine(ex.Message); - var confirm = new ConfirmationPrompt("Press a key to continue").HideChoices(); - await confirm.ShowAsync(AnsiConsole.Console, CancellationToken.None); - } - } + var menu = new Cdg.CdgSelector(arguments.Folder); + await menu.ShowMenu(); + return 0; } } \ No newline at end of file From 612d142d5b8e8ce3c2a3de4e83caa347c57b14ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Wed, 12 Jun 2024 21:56:47 +0200 Subject: [PATCH 05/46] Reworked CDG command --- Prog/BookGen.Shell/Cdg/CdgSelector.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Prog/BookGen.Shell/Cdg/CdgSelector.cs b/Prog/BookGen.Shell/Cdg/CdgSelector.cs index 50e3eb59..3cc71807 100644 --- a/Prog/BookGen.Shell/Cdg/CdgSelector.cs +++ b/Prog/BookGen.Shell/Cdg/CdgSelector.cs @@ -15,7 +15,7 @@ internal sealed class CdgSelector private readonly List _directories; private bool _canRun; - public string _currentPath { get; set; } + public string _currentPath; private void SetDirectories(IEnumerable items) { @@ -27,8 +27,8 @@ public CdgSelector(string startDirectory) { _currentPath = startDirectory; _directories = new List(); - _menuItems = new SelectionItemAction[] - { + _menuItems = + [ new SelectionItemAction { DisplayString = "Select current directory", @@ -98,7 +98,7 @@ public CdgSelector(string startDirectory) }, Color = Color.Fuchsia, }, - }; + ]; } private static bool CanAccess(string path, [NotNullWhen(true)] out string[]? subdirs) From 9b9a879d4678e0e33e5e6ae7ac33a30311c46a91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Wed, 12 Jun 2024 23:26:06 +0200 Subject: [PATCH 06/46] Reworked CDG command --- Prog/BookGen.Shell/Cdg/CdgSelector.cs | 13 ++++++++++--- Prog/BookGen.Shell/Commands/CdgCommand.cs | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Prog/BookGen.Shell/Cdg/CdgSelector.cs b/Prog/BookGen.Shell/Cdg/CdgSelector.cs index 3cc71807..f716a15b 100644 --- a/Prog/BookGen.Shell/Cdg/CdgSelector.cs +++ b/Prog/BookGen.Shell/Cdg/CdgSelector.cs @@ -16,6 +16,7 @@ internal sealed class CdgSelector private bool _canRun; public string _currentPath; + private readonly bool _showHidden; private void SetDirectories(IEnumerable items) { @@ -23,9 +24,10 @@ private void SetDirectories(IEnumerable items) _directories.AddRange(items); } - public CdgSelector(string startDirectory) + public CdgSelector(string startDirectory, bool showHidden) { _currentPath = startDirectory; + _showHidden = showHidden; _directories = new List(); _menuItems = [ @@ -101,11 +103,16 @@ public CdgSelector(string startDirectory) ]; } - private static bool CanAccess(string path, [NotNullWhen(true)] out string[]? subdirs) + private bool CanAccess(string path, [NotNullWhen(true)] out string[]? subdirs) { try { - subdirs = Directory.GetDirectories(path); + var items = new DirectoryInfo(path).GetDirectories(); + if (_showHidden) + { + items = items.Where(x => !x.Attributes.HasFlag(FileAttributes.Hidden)).ToArray(); + } + subdirs = items.Select(x => x.FullName).ToArray(); return true; } catch (Exception) diff --git a/Prog/BookGen.Shell/Commands/CdgCommand.cs b/Prog/BookGen.Shell/Commands/CdgCommand.cs index 7f989315..69ccc84c 100644 --- a/Prog/BookGen.Shell/Commands/CdgCommand.cs +++ b/Prog/BookGen.Shell/Commands/CdgCommand.cs @@ -14,7 +14,7 @@ internal sealed class CdgCommand : AsyncCommand { public override async Task Execute(CdgArguments arguments, string[] context) { - var menu = new Cdg.CdgSelector(arguments.Folder); + var menu = new Cdg.CdgSelector(arguments.Folder, arguments.ShowHidden); await menu.ShowMenu(); return 0; } From 4c90df73b1237da21514775a752224ef12b4f23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Sat, 15 Jun 2024 12:57:14 +0200 Subject: [PATCH 07/46] CDG selector update --- Prog/BookGen.Shell/Cdg/CdgSelector.cs | 38 +++++++++++++++++++-- Prog/BookGen.Shell/Cdg/SelectionItemBase.cs | 5 +-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Prog/BookGen.Shell/Cdg/CdgSelector.cs b/Prog/BookGen.Shell/Cdg/CdgSelector.cs index f716a15b..3c99373f 100644 --- a/Prog/BookGen.Shell/Cdg/CdgSelector.cs +++ b/Prog/BookGen.Shell/Cdg/CdgSelector.cs @@ -5,6 +5,7 @@ using Spectre.Console; +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace BookGen.Shell.Cdg; @@ -16,7 +17,7 @@ internal sealed class CdgSelector private bool _canRun; public string _currentPath; - private readonly bool _showHidden; + private bool _showHidden; private void SetDirectories(IEnumerable items) { @@ -42,6 +43,13 @@ public CdgSelector(string startDirectory, bool showHidden) }, Color = Color.Red, }, + new SelectionItemAction + { + DisplayString = "Open in file manager", + Icon = ":right_arrow:", + Action = OpenInFileExplorer, + Color = Color.Red, + }, new SelectionItemAction { DisplayString = "Up one directory", @@ -100,9 +108,33 @@ public CdgSelector(string startDirectory, bool showHidden) }, Color = Color.Fuchsia, }, + new SelectionItemAction + { + Id = "ShowHide", + DisplayString = _showHidden ? "Hide hidden files" : "Toggle hidden files", + Icon = ":eye: ", + Action = () => + { + var showHide = _menuItems?.First(m => m.Id == "ShowHide") ?? throw new InvalidOperationException(); + showHide.DisplayString = _showHidden ? "Show hidden files" : "Hide hidden files"; + _showHidden = !_showHidden; + }, + Color = Color.Blue, + }, ]; } + private void OpenInFileExplorer() + { + using var process = Process.Start(new ProcessStartInfo + { + FileName = _currentPath, + UseShellExecute = true, + Verb = "open" + }); + _canRun = false; + } + private bool CanAccess(string path, [NotNullWhen(true)] out string[]? subdirs) { try @@ -144,7 +176,7 @@ private static bool TryGetUpOneDirectory(string currentPath, out string newPath) private static string Render(SelectionItemBase item) { string seperator = " "; - if (item.IsMenu) + if (item.IsMenuHeader) seperator = " "; return $"{item.Icon}{seperator}[{item.Color.ToMarkup()}]{item.DisplayString.EscapeMarkup()}[/]"; @@ -156,7 +188,7 @@ private static SelectionItemBase CreateGroup(string groupName, string icon) { DisplayString = groupName, Icon = icon, - IsMenu = true, + IsMenuHeader = true, Color = Spectre.Console.Color.Red }; } diff --git a/Prog/BookGen.Shell/Cdg/SelectionItemBase.cs b/Prog/BookGen.Shell/Cdg/SelectionItemBase.cs index 6e459d01..e80439d0 100644 --- a/Prog/BookGen.Shell/Cdg/SelectionItemBase.cs +++ b/Prog/BookGen.Shell/Cdg/SelectionItemBase.cs @@ -12,8 +12,9 @@ namespace BookGen.Shell.Cdg; [DebuggerDisplay("{DisplayString}")] internal class SelectionItemBase { - public required string DisplayString { get; init; } + public string Id { get; init; } = string.Empty; + public required string DisplayString { get; set; } public required string Icon { get; init; } public required Color Color { get; init; } - public bool IsMenu { get; init; } = false; + public bool IsMenuHeader { get; init; } = false; } From 7548c62fe0f1519d0e1b8e16bdda0b98d5587a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Sun, 16 Jun 2024 17:39:06 +0200 Subject: [PATCH 08/46] WIP: Scripting support --- .../BookGen.DomainServices.csproj | 2 + .../Scripting/CsharpScriptExecutor.cs | 79 +++++++++++++++++ .../Markdown/Scripting/NotSupportedReader.cs | 55 ++++++++++++ .../Markdown/Scripting/ScriptBlock.cs | 21 +++++ .../Markdown/Scripting/ScriptBlockParser.cs | 86 +++++++++++++++++++ .../Markdown/Scripting/ScriptBlockRenderer.cs | 25 ++++++ 6 files changed, 268 insertions(+) create mode 100644 Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs create mode 100644 Libs/BookGen.DomainServices/Markdown/Scripting/NotSupportedReader.cs create mode 100644 Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlock.cs create mode 100644 Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockParser.cs create mode 100644 Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockRenderer.cs diff --git a/Libs/BookGen.DomainServices/BookGen.DomainServices.csproj b/Libs/BookGen.DomainServices/BookGen.DomainServices.csproj index a06be2b5..1647b7ea 100644 --- a/Libs/BookGen.DomainServices/BookGen.DomainServices.csproj +++ b/Libs/BookGen.DomainServices/BookGen.DomainServices.csproj @@ -22,6 +22,8 @@ + + diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs new file mode 100644 index 00000000..93f088d8 --- /dev/null +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs @@ -0,0 +1,79 @@ +//----------------------------------------------------------------------------- +// (c) 2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; + +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; + +namespace BookGen.DomainServices.Markdown.Scripting; +internal sealed class CsharpScriptExecutor +{ + private readonly ScriptOptions _options; + + public CsharpScriptExecutor() + { + _options = ScriptOptions.Default + .WithCheckOverflow(true) + .WithFileEncoding(Encoding.UTF8) + .WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Latest) + .WithOptimizationLevel(Microsoft.CodeAnalysis.OptimizationLevel.Release) + .WithImports(GetImports()) + .WithReferences(GetReferences()); + } + + private static HashSet GetReferences() + { + HashSet references = + [ + typeof(object).Assembly, + typeof(List<>).Assembly, + typeof(Enumerable).Assembly, + typeof(Task).Assembly, + typeof(File).Assembly, + typeof(Regex).Assembly + ]; + return references; + } + + private static IEnumerable GetImports() + { + yield return "System"; + yield return "System.Collections.Generic"; + yield return "System.Linq"; + yield return "System.Text"; + yield return "System.Threading.Tasks"; + yield return "System.IO"; + yield return "System.Text.RegularExpressions"; + } + + public async Task Execute(string code) + { + var originalConsoleOut = Console.Out; + var originalConsoleIn = Console.In; + using (var writer = new StringWriter()) + { + Console.SetOut(writer); + Console.SetIn(new NotSupportedReader()); + try + { + await CSharpScript.RunAsync(code, _options); + } + catch (Exception ex) + { + writer.WriteLine(ex.Message); + writer.WriteLine(ex.StackTrace); + } + finally + { + Console.SetIn(originalConsoleIn); + Console.SetOut(originalConsoleOut); + } + return writer.ToString(); + } + } +} diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/NotSupportedReader.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/NotSupportedReader.cs new file mode 100644 index 00000000..b7ff6234 --- /dev/null +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/NotSupportedReader.cs @@ -0,0 +1,55 @@ +//----------------------------------------------------------------------------- +// (c) 2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +namespace BookGen.DomainServices.Markdown.Scripting; +internal sealed class NotSupportedReader : TextReader +{ + private const string Message = "Console Inputs is not supported in scripting"; + + public override int Read() + => throw new NotSupportedException(Message); + + public override int Read(char[] buffer, int index, int count) + => throw new NotSupportedException(Message); + + public override int Read(Span buffer) + => throw new NotSupportedException(Message); + + public override Task ReadAsync(char[] buffer, int index, int count) + => throw new NotSupportedException(Message); + + public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default) + => throw new NotSupportedException(Message); + + public override int ReadBlock(char[] buffer, int index, int count) + => throw new NotSupportedException(Message); + + public override int ReadBlock(Span buffer) + => throw new NotSupportedException(Message); + + public override Task ReadBlockAsync(char[] buffer, int index, int count) + => throw new NotSupportedException(Message); + + public override ValueTask ReadBlockAsync(Memory buffer, CancellationToken cancellationToken = default) + => throw new NotSupportedException(Message); + + public override string? ReadLine() + => throw new NotSupportedException(Message); + + public override Task ReadLineAsync() + => throw new NotSupportedException(Message); + + public override ValueTask ReadLineAsync(CancellationToken cancellationToken) + => throw new NotSupportedException(Message); + + public override string ReadToEnd() + => throw new NotSupportedException(Message); + + public override Task ReadToEndAsync() + => throw new NotSupportedException(Message); + + public override Task ReadToEndAsync(CancellationToken cancellationToken) + => throw new NotSupportedException(Message); +} diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlock.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlock.cs new file mode 100644 index 00000000..ae0e7366 --- /dev/null +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlock.cs @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------------- +// (c) 2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Markdig.Parsers; +using Markdig.Syntax; + +namespace BookGen.DomainServices.Markdown.Scripting; + +internal sealed class ScriptBlock : FencedCodeBlock +{ + public ScriptBlock(BlockParser parser) : base(parser) + { + } + + public string GetScript() + { + return string.Join(Environment.NewLine, Lines); + } +} diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockParser.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockParser.cs new file mode 100644 index 00000000..9c4a52d5 --- /dev/null +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockParser.cs @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// (c) 2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Markdig.Helpers; +using Markdig.Parsers; +using Markdig.Syntax; + +namespace BookGen.DomainServices.Markdown.Scripting; + +internal sealed class ScriptBlockParser : FencedBlockParserBase +{ + public ScriptBlockParser() + { + OpeningCharacters = new[] { '`' }; + InfoPrefix = "script"; + InfoParser = ScriptInfoParser; + } + + protected override ScriptBlock CreateFencedBlock(BlockProcessor processor) + { + var block = new ScriptBlock(this); + return block; + } + + private bool ScriptInfoParser(BlockProcessor state, + ref StringSlice line, + IFencedBlock fenced, + char openingCharacter) + { + string infoString; + string? argString = null; + + var c = line.CurrentChar; + // An info string cannot contain any backsticks + int firstSpace = -1; + for (int i = line.Start; i <= line.End; i++) + { + c = line.Text[i]; + if (c == '`') + { + return false; + } + + if (firstSpace < 0 && c.IsSpaceOrTab()) + { + firstSpace = i; + } + } + + if (firstSpace > 0) + { + infoString = line.Text[line.Start..firstSpace].Trim(); + + // Skip any spaces after info string + firstSpace++; + while (true) + { + c = line[firstSpace]; + if (c.IsSpaceOrTab()) + { + firstSpace++; + } + else + { + break; + } + } + + argString = line.Text.Substring(firstSpace, line.End - firstSpace + 1).Trim(); + } + else + { + infoString = line.ToString().Trim(); + } + + if (infoString != "script") + return false; + + fenced.Info = HtmlHelper.Unescape(infoString); + fenced.Arguments = HtmlHelper.Unescape(argString); + + return true; + } +} \ No newline at end of file diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockRenderer.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockRenderer.cs new file mode 100644 index 00000000..9e27b523 --- /dev/null +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockRenderer.cs @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------------- +// (c) 2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Markdig.Renderers; +using Markdig.Renderers.Html; + +namespace BookGen.DomainServices.Markdown.Scripting; + +internal sealed class ScriptBlockRenderer : HtmlObjectRenderer +{ + private readonly CsharpScriptExecutor _scriptExecutor; + + public ScriptBlockRenderer(CsharpScriptExecutor scriptExecutor) + { + _scriptExecutor = scriptExecutor; + } + + protected override void Write(HtmlRenderer renderer, ScriptBlock obj) + { + var result = _scriptExecutor.Execute(obj.GetScript()).Result; + renderer.Write(result); + } +} From 9346eb4887aae6eb49bbf468fdc38eb2a6fdeb74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Mon, 17 Jun 2024 19:20:10 +0200 Subject: [PATCH 09/46] Scripting extension --- .../Markdown/Scripting/ScriptExtension.cs | 32 +++++++++++++++++++ .../Markdown/Scripting/ScriptExtensions.cs | 18 +++++++++++ 2 files changed, 50 insertions(+) create mode 100644 Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtension.cs create mode 100644 Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtensions.cs diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtension.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtension.cs new file mode 100644 index 00000000..abc4bdd7 --- /dev/null +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtension.cs @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// (c) 2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Markdig; +using Markdig.Renderers; + +namespace BookGen.DomainServices.Markdown.Scripting; + +internal sealed class ScriptExtension : IMarkdownExtension +{ + private readonly CsharpScriptExecutor _scriptExecutor; + + public ScriptExtension() + { + _scriptExecutor = new CsharpScriptExecutor(); + } + + public void Setup(MarkdownPipelineBuilder pipeline) + { + pipeline.BlockParsers.AddIfNotAlready(new ScriptBlockParser()); + } + + public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) + { + if (renderer is HtmlRenderer htmlRenderer) + { + htmlRenderer.ObjectRenderers.AddIfNotAlready(new ScriptBlockRenderer(_scriptExecutor)); + } + } +} diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtensions.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtensions.cs new file mode 100644 index 00000000..946faf23 --- /dev/null +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtensions.cs @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// (c) 2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using Markdig; +using Markdig.Renderers; + +namespace BookGen.DomainServices.Markdown.Scripting; + +internal static class ScriptExtensions +{ + public static MarkdownPipelineBuilder UseScripting(this MarkdownPipelineBuilder pipelineBuilder) + { + pipelineBuilder.Extensions.AddIfNotAlready(new ScriptExtension()); + return pipelineBuilder; + } +} From fb34a830db91ca4a038a634048310312d1df6ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Mon, 17 Jun 2024 19:43:08 +0200 Subject: [PATCH 10/46] Scripting extension --- .../Markdown/BookGenPipeline.cs | 6 +++ .../Scripting/CsharpScriptExecutor.cs | 1 + .../Markdown/Scripting/ScriptBlockParser.cs | 4 +- .../Markdown/Scripting/ScriptExtension.cs | 6 ++- .../Markdown/Scripting/ScriptExtensions.cs | 1 - Test/BookGen.Tests/UT_Scripting.cs | 49 +++++++++++++++++++ 6 files changed, 63 insertions(+), 4 deletions(-) create mode 100644 Test/BookGen.Tests/UT_Scripting.cs diff --git a/Libs/BookGen.DomainServices/Markdown/BookGenPipeline.cs b/Libs/BookGen.DomainServices/Markdown/BookGenPipeline.cs index 127d902f..14856e82 100644 --- a/Libs/BookGen.DomainServices/Markdown/BookGenPipeline.cs +++ b/Libs/BookGen.DomainServices/Markdown/BookGenPipeline.cs @@ -4,6 +4,7 @@ //----------------------------------------------------------------------------- using BookGen.DomainServices.Markdown.Modifiers; +using BookGen.DomainServices.Markdown.Scripting; using BookGen.DomainServices.Markdown.TableOfContents; using BookGen.Interfaces; using Markdig; @@ -17,6 +18,7 @@ public static MarkdownPipeline Web get => new MarkdownPipelineBuilder() .UseAdvancedExtensions() .UseTableOfContents() + .UseScripting() .Use() .Build(); } @@ -26,6 +28,7 @@ public static MarkdownPipeline Print get => new MarkdownPipelineBuilder() .UseAdvancedExtensions() .UseTableOfContents() + .UseScripting() .Use() .Build(); } @@ -41,6 +44,7 @@ public static MarkdownPipeline Epub get => new MarkdownPipelineBuilder() .UseAdvancedExtensions() .UseTableOfContents() + .UseScripting() .Use() .Build(); } @@ -50,6 +54,7 @@ public static MarkdownPipeline Preview get => new MarkdownPipelineBuilder() .UseAdvancedExtensions() .UseTableOfContents() + .UseScripting() .Use() .Build(); } @@ -59,6 +64,7 @@ public static MarkdownPipeline Wordpress get => new MarkdownPipelineBuilder() .UseAdvancedExtensions() .UseTableOfContents() + .UseScripting() .Use() .Build(); } diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs index 93f088d8..63e9de0c 100644 --- a/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs @@ -30,6 +30,7 @@ private static HashSet GetReferences() { HashSet references = [ + typeof(Console).Assembly, typeof(object).Assembly, typeof(List<>).Assembly, typeof(Enumerable).Assembly, diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockParser.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockParser.cs index 9c4a52d5..80a62306 100644 --- a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockParser.cs +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptBlockParser.cs @@ -13,7 +13,7 @@ internal sealed class ScriptBlockParser : FencedBlockParserBase { public ScriptBlockParser() { - OpeningCharacters = new[] { '`' }; + OpeningCharacters = new[] { '\'' }; InfoPrefix = "script"; InfoParser = ScriptInfoParser; } @@ -38,7 +38,7 @@ private bool ScriptInfoParser(BlockProcessor state, for (int i = line.Start; i <= line.End; i++) { c = line.Text[i]; - if (c == '`') + if (c == '\'') { return false; } diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtension.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtension.cs index abc4bdd7..598e7da4 100644 --- a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtension.cs +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtension.cs @@ -5,6 +5,7 @@ using Markdig; using Markdig.Renderers; +using Markdig.Renderers.Html; namespace BookGen.DomainServices.Markdown.Scripting; @@ -26,7 +27,10 @@ public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) { if (renderer is HtmlRenderer htmlRenderer) { - htmlRenderer.ObjectRenderers.AddIfNotAlready(new ScriptBlockRenderer(_scriptExecutor)); + if (htmlRenderer.ObjectRenderers.Contains()) + htmlRenderer.ObjectRenderers.InsertBefore(new ScriptBlockRenderer(_scriptExecutor)); + else + htmlRenderer.ObjectRenderers.AddIfNotAlready(new ScriptBlockRenderer(_scriptExecutor)); } } } diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtensions.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtensions.cs index 946faf23..77064a3b 100644 --- a/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtensions.cs +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/ScriptExtensions.cs @@ -4,7 +4,6 @@ //----------------------------------------------------------------------------- using Markdig; -using Markdig.Renderers; namespace BookGen.DomainServices.Markdown.Scripting; diff --git a/Test/BookGen.Tests/UT_Scripting.cs b/Test/BookGen.Tests/UT_Scripting.cs new file mode 100644 index 00000000..f632fc3b --- /dev/null +++ b/Test/BookGen.Tests/UT_Scripting.cs @@ -0,0 +1,49 @@ +using BookGen.DomainServices.Markdown.Scripting; + +using Markdig; + +namespace BookGen.Tests; + +public class UT_Scripting +{ + private MarkdownPipeline _pipeline; + + [SetUp] + public void Setup() + { + _pipeline = new MarkdownPipelineBuilder() + .UseScripting() + .UseGenericAttributes() + .Build(); + } + + [Test] + public void TestScripting() + { + var script = """ + '''script + for (int i = 0; i < 10; i++) + { + Console.WriteLine($"

Hello World {i}

"); + } + ''' + """; + + var expected = """ +

Hello World 0

+

Hello World 1

+

Hello World 2

+

Hello World 3

+

Hello World 4

+

Hello World 5

+

Hello World 6

+

Hello World 7

+

Hello World 8

+

Hello World 9

+ + """; + + string result = Markdown.ToHtml(script, _pipeline); + Assert.That(result, Is.EqualTo(expected)); + } +} \ No newline at end of file From 004af93510b2272a42fd2d16f4ad966acb58d650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Mon, 17 Jun 2024 19:46:48 +0200 Subject: [PATCH 11/46] Scripting extension --- .../Markdown/Scripting/CsharpScriptExecutor.cs | 1 - Test/BookGen.Tests/UT_Scripting.cs | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs b/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs index 63e9de0c..c0e86eaa 100644 --- a/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs +++ b/Libs/BookGen.DomainServices/Markdown/Scripting/CsharpScriptExecutor.cs @@ -67,7 +67,6 @@ public async Task Execute(string code) catch (Exception ex) { writer.WriteLine(ex.Message); - writer.WriteLine(ex.StackTrace); } finally { diff --git a/Test/BookGen.Tests/UT_Scripting.cs b/Test/BookGen.Tests/UT_Scripting.cs index f632fc3b..6da4f23c 100644 --- a/Test/BookGen.Tests/UT_Scripting.cs +++ b/Test/BookGen.Tests/UT_Scripting.cs @@ -46,4 +46,17 @@ public void TestScripting() string result = Markdown.ToHtml(script, _pipeline); Assert.That(result, Is.EqualTo(expected)); } + + [Test] + public void TestConsoleReadThrows() + { + var script = """ + '''script + Console.ReadLine(); + ''' + """; + + string result = Markdown.ToHtml(script, _pipeline); + Assert.That(result, Is.EqualTo("Console Inputs is not supported in scripting\r\n")); + } } \ No newline at end of file From 73a61dcdda81014af9d2939cc1d836334fe3b527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Tue, 18 Jun 2024 18:02:42 +0200 Subject: [PATCH 12/46] Remove shortcodes folder --- Libs/BookGen.Contents/BookGen.Contents.csproj | 3 --- Libs/BookGen.Contents/ShortCodes/Readme.txt | 8 -------- 2 files changed, 11 deletions(-) delete mode 100644 Libs/BookGen.Contents/ShortCodes/Readme.txt diff --git a/Libs/BookGen.Contents/BookGen.Contents.csproj b/Libs/BookGen.Contents/BookGen.Contents.csproj index 7baf2cb3..277388bf 100644 --- a/Libs/BookGen.Contents/BookGen.Contents.csproj +++ b/Libs/BookGen.Contents/BookGen.Contents.csproj @@ -15,9 +15,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/Libs/BookGen.Contents/ShortCodes/Readme.txt b/Libs/BookGen.Contents/ShortCodes/Readme.txt deleted file mode 100644 index 14641b3e..00000000 --- a/Libs/BookGen.Contents/ShortCodes/Readme.txt +++ /dev/null @@ -1,8 +0,0 @@ -Shortcodes - -Place your custom implemented Shortcodes in this folder. - -1. Create a C# project -2. Add reference to BookGen.Api assembly -3. Implement the ITemplateShortCode interface -4. Place compiled dll to this folder. \ No newline at end of file From 0d1630313053172893a17fe6f41cba39fb60cff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruzsinszki=20G=C3=A1bor?= Date: Tue, 18 Jun 2024 19:08:14 +0200 Subject: [PATCH 13/46] Preparation work for new command --- .../BookGen.Resources.csproj | 10 + Libs/BookGen.Resources/NewFiles.cs | 60 ++ Libs/BookGen.Resources/NewFiles/html.html | 14 + Libs/BookGen.Resources/NewFiles/markdown.md | 3 + Libs/BookGen.Resources/NewFiles/mvpcss.css | 538 +++++++++++++ Libs/BookGen.Resources/NewFiles/newcss.css | 451 +++++++++++ Libs/BookGen.Resources/NewFiles/simple.css | 711 ++++++++++++++++++ Libs/BookGen.Resources/ResourceHandler.cs | 6 +- 8 files changed, 1790 insertions(+), 3 deletions(-) create mode 100644 Libs/BookGen.Resources/NewFiles.cs create mode 100644 Libs/BookGen.Resources/NewFiles/html.html create mode 100644 Libs/BookGen.Resources/NewFiles/markdown.md create mode 100644 Libs/BookGen.Resources/NewFiles/mvpcss.css create mode 100644 Libs/BookGen.Resources/NewFiles/newcss.css create mode 100644 Libs/BookGen.Resources/NewFiles/simple.css diff --git a/Libs/BookGen.Resources/BookGen.Resources.csproj b/Libs/BookGen.Resources/BookGen.Resources.csproj index b38d8a3e..ed6a2697 100644 --- a/Libs/BookGen.Resources/BookGen.Resources.csproj +++ b/Libs/BookGen.Resources/BookGen.Resources.csproj @@ -41,6 +41,11 @@ + + + + + @@ -73,6 +78,11 @@ + + + + + diff --git a/Libs/BookGen.Resources/NewFiles.cs b/Libs/BookGen.Resources/NewFiles.cs new file mode 100644 index 00000000..37e2ceaa --- /dev/null +++ b/Libs/BookGen.Resources/NewFiles.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------------- +// (c) 2024 Ruzsinszki Gábor +// This code is licensed under MIT license (see LICENSE for details) +//----------------------------------------------------------------------------- + +using System.Text; + +namespace BookGen.Resources; +internal class NewFiles +{ + private readonly Dictionary _files; + + internal record class FileKey + { + public FileKey(string name, string path) + { + Name = name; + Path = path; + } + + public string Name { get; } + public string Path { get; } + } + + public NewFiles() + { + _files = new Dictionary + { + { new FileKey("html", "/NewFiles/html.html"), "Blank HTML5 page" }, + { new FileKey("markdown", "/NewFiles/markdown.md"), "Blank markdown document" }, + { new FileKey("mvpcss", "/NewFiles/mvpcss.css"), "A minimalist stylesheet for HTML elements - https://andybrewer.github.io/mvp/" }, + { new FileKey("newcss", "/NewFiles/newcss.css"), "new.css is a classless CSS framework to write modern websites using only HTML. - https://newcss.net/" }, + { new FileKey("simple", "/NewFiles/simple.css"), "Simple.css is a CSS framework that makes semantic HTML look good, really quickly. - https://simplecss.org/" }, + }; + } + + public string GetHelp() + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine("Available templates:"); + foreach (var file in _files) + { + sb.AppendLine($"* {file.Key.Name}") + .AppendLine($" {file.Value}"); + } + return sb.ToString(); + } + + public bool TryGetFile(string name, out string content) + { + var key = _files.Keys.FirstOrDefault(k => k.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + if (key != null) + { + content = ResourceHandler.GetResourceFile(key.Path); + return true; + } + content = string.Empty; + return false; + } +} diff --git a/Libs/BookGen.Resources/NewFiles/html.html b/Libs/BookGen.Resources/NewFiles/html.html new file mode 100644 index 00000000..4d70452f --- /dev/null +++ b/Libs/BookGen.Resources/NewFiles/html.html @@ -0,0 +1,14 @@ + + + + + + + Page Title + + + +

Content

+ + + diff --git a/Libs/BookGen.Resources/NewFiles/markdown.md b/Libs/BookGen.Resources/NewFiles/markdown.md new file mode 100644 index 00000000..c9abf1fc --- /dev/null +++ b/Libs/BookGen.Resources/NewFiles/markdown.md @@ -0,0 +1,3 @@ +# Title + +content \ No newline at end of file diff --git a/Libs/BookGen.Resources/NewFiles/mvpcss.css b/Libs/BookGen.Resources/NewFiles/mvpcss.css new file mode 100644 index 00000000..a44c50bc --- /dev/null +++ b/Libs/BookGen.Resources/NewFiles/mvpcss.css @@ -0,0 +1,538 @@ +/* MVP.css v1.15 - https://github.com/andybrewer/mvp */ + +:root { + --active-brightness: 0.85; + --border-radius: 5px; + --box-shadow: 2px 2px 10px; + --color-accent: #118bee15; + --color-bg: #fff; + --color-bg-secondary: #e9e9e9; + --color-link: #118bee; + --color-secondary: #920de9; + --color-secondary-accent: #920de90b; + --color-shadow: #f4f4f4; + --color-table: #118bee; + --color-text: #000; + --color-text-secondary: #999; + --color-scrollbar: #cacae8; + --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + --hover-brightness: 1.2; + --justify-important: center; + --justify-normal: left; + --line-height: 1.5; + --width-card: 285px; + --width-card-medium: 460px; + --width-card-wide: 800px; + --width-content: 1080px; +} + +@media (prefers-color-scheme: dark) { + :root[color-mode="user"] { + --color-accent: #0097fc4f; + --color-bg: #333; + --color-bg-secondary: #555; + --color-link: #0097fc; + --color-secondary: #e20de9; + --color-secondary-accent: #e20de94f; + --color-shadow: #bbbbbb20; + --color-table: #0097fc; + --color-text: #f7f7f7; + --color-text-secondary: #aaa; + } +} + +html { + scroll-behavior: smooth; +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } +} + +/* Layout */ +article aside { + background: var(--color-secondary-accent); + border-left: 4px solid var(--color-secondary); + padding: 0.01rem 0.8rem; +} + +body { + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-family); + line-height: var(--line-height); + margin: 0; + overflow-x: hidden; + padding: 0; +} + +footer, +header, +main { + margin: 0 auto; + max-width: var(--width-content); + padding: 3rem 1rem; +} + +hr { + background-color: var(--color-bg-secondary); + border: none; + height: 1px; + margin: 4rem 0; + width: 100%; +} + +section { + display: flex; + flex-wrap: wrap; + justify-content: var(--justify-important); +} + +section img, +article img { + max-width: 100%; +} + +section pre { + overflow: auto; +} + +section aside { + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow) var(--color-shadow); + margin: 1rem; + padding: 1.25rem; + width: var(--width-card); +} + +section aside:hover { + box-shadow: var(--box-shadow) var(--color-bg-secondary); +} + +[hidden] { + display: none; +} + +/* Headers */ +article header, +div header, +main header { + padding-top: 0; +} + +header { + text-align: var(--justify-important); +} + +header a b, +header a em, +header a i, +header a strong { + margin-left: 0.5rem; + margin-right: 0.5rem; +} + +header nav img { + margin: 1rem 0; +} + +section header { + padding-top: 0; + width: 100%; +} + +/* Nav */ +nav { + align-items: center; + display: flex; + font-weight: bold; + justify-content: space-between; + margin-bottom: 7rem; +} + +nav ul { + list-style: none; + padding: 0; +} + +nav ul li { + display: inline-block; + margin: 0 0.5rem; + position: relative; + text-align: left; +} + +/* Nav Dropdown */ +nav ul li:hover ul { + display: block; +} + +nav ul li ul { + background: var(--color-bg); + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow) var(--color-shadow); + display: none; + height: auto; + left: -2px; + padding: .5rem 1rem; + position: absolute; + top: 1.7rem; + white-space: nowrap; + width: auto; + z-index: 1; +} + +nav ul li ul::before { + /* fill gap above to make mousing over them easier */ + content: ""; + position: absolute; + left: 0; + right: 0; + top: -0.5rem; + height: 0.5rem; +} + +nav ul li ul li, +nav ul li ul li a { + display: block; +} + +/* Typography */ +code, +samp { + background-color: var(--color-accent); + border-radius: var(--border-radius); + color: var(--color-text); + display: inline-block; + margin: 0 0.1rem; + padding: 0 0.5rem; +} + +details { + margin: 1.3rem 0; +} + +details summary { + font-weight: bold; + cursor: pointer; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + line-height: var(--line-height); + text-wrap: balance; +} + +mark { + padding: 0.1rem; +} + +ol li, +ul li { + padding: 0.2rem 0; +} + +p { + margin: 0.75rem 0; + padding: 0; + width: 100%; +} + +pre { + margin: 1rem 0; + max-width: var(--width-card-wide); + padding: 1rem 0; +} + +pre code, +pre samp { + display: block; + max-width: var(--width-card-wide); + padding: 0.5rem 2rem; + white-space: pre-wrap; +} + +small { + color: var(--color-text-secondary); +} + +sup { + background-color: var(--color-secondary); + border-radius: var(--border-radius); + color: var(--color-bg); + font-size: xx-small; + font-weight: bold; + margin: 0.2rem; + padding: 0.2rem 0.3rem; + position: relative; + top: -2px; +} + +/* Links */ +a { + color: var(--color-link); + display: inline-block; + font-weight: bold; + text-decoration: underline; +} + +a:hover { + filter: brightness(var(--hover-brightness)); +} + +a:active { + filter: brightness(var(--active-brightness)); +} + +a b, +a em, +a i, +a strong, +button, +input[type="submit"] { + border-radius: var(--border-radius); + display: inline-block; + font-size: medium; + font-weight: bold; + line-height: var(--line-height); + margin: 0.5rem 0; + padding: 1rem 2rem; +} + +button, +input[type="submit"] { + font-family: var(--font-family); +} + +button:hover, +input[type="submit"]:hover { + cursor: pointer; + filter: brightness(var(--hover-brightness)); +} + +button:active, +input[type="submit"]:active { + filter: brightness(var(--active-brightness)); +} + +a b, +a strong, +button, +input[type="submit"] { + background-color: var(--color-link); + border: 2px solid var(--color-link); + color: var(--color-bg); +} + +a em, +a i { + border: 2px solid var(--color-link); + border-radius: var(--border-radius); + color: var(--color-link); + display: inline-block; + padding: 1rem 2rem; +} + +article aside a { + color: var(--color-secondary); +} + +/* Images */ +figure { + margin: 0; + padding: 0; +} + +figure img { + max-width: 100%; +} + +figure figcaption { + color: var(--color-text-secondary); +} + +/* Forms */ +button:disabled, +input:disabled { + background: var(--color-bg-secondary); + border-color: var(--color-bg-secondary); + color: var(--color-text-secondary); + cursor: not-allowed; +} + +button[disabled]:hover, +input[type="submit"][disabled]:hover { + filter: none; +} + +form { + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow) var(--color-shadow); + display: block; + max-width: var(--width-card-wide); + min-width: var(--width-card); + padding: 1.5rem; + text-align: var(--justify-normal); +} + +form header { + margin: 1.5rem 0; + padding: 1.5rem 0; +} + +input, +label, +select, +textarea { + display: block; + font-size: inherit; + max-width: var(--width-card-wide); +} + +input[type="checkbox"], +input[type="radio"] { + display: inline-block; +} + +input[type="checkbox"]+label, +input[type="radio"]+label { + display: inline-block; + font-weight: normal; + position: relative; + top: 1px; +} + +input[type="range"] { + padding: 0.4rem 0; +} + +input, +select, +textarea { + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + margin-bottom: 1rem; + padding: 0.4rem 0.8rem; +} + +input[type="text"], +input[type="password"] +textarea { + width: calc(100% - 1.6rem); +} + +input[readonly], +textarea[readonly] { + background-color: var(--color-bg-secondary); +} + +label { + font-weight: bold; + margin-bottom: 0.2rem; +} + +/* Popups */ +dialog { + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + box-shadow: var(--box-shadow) var(--color-shadow); + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 50%; + z-index: 999; +} + +/* Tables */ +table { + border: 1px solid var(--color-bg-secondary); + border-radius: var(--border-radius); + border-spacing: 0; + display: inline-block; + max-width: 100%; + overflow-x: auto; + padding: 0; + white-space: nowrap; +} + +table td, +table th, +table tr { + padding: 0.4rem 0.8rem; + text-align: var(--justify-important); +} + +table thead { + background-color: var(--color-table); + border-collapse: collapse; + border-radius: var(--border-radius); + color: var(--color-bg); + margin: 0; + padding: 0; +} + +table thead tr:first-child th:first-child { + border-top-left-radius: var(--border-radius); +} + +table thead tr:first-child th:last-child { + border-top-right-radius: var(--border-radius); +} + +table thead th:first-child, +table tr td:first-child { + text-align: var(--justify-normal); +} + +table tr:nth-child(even) { + background-color: var(--color-accent); +} + +/* Quotes */ +blockquote { + display: block; + font-size: x-large; + line-height: var(--line-height); + margin: 1rem auto; + max-width: var(--width-card-medium); + padding: 1.5rem 1rem; + text-align: var(--justify-important); +} + +blockquote footer { + color: var(--color-text-secondary); + display: block; + font-size: small; + line-height: var(--line-height); + padding: 1.5rem 0; +} + +/* Scrollbars */ +* { + scrollbar-width: thin; + scrollbar-color: var(--color-scrollbar) transparent; +} + +*::-webkit-scrollbar { + width: 5px; + height: 5px; +} + +*::-webkit-scrollbar-track { + background: transparent; +} + +*::-webkit-scrollbar-thumb { + background-color: var(--color-scrollbar); + border-radius: 10px; +} diff --git a/Libs/BookGen.Resources/NewFiles/newcss.css b/Libs/BookGen.Resources/NewFiles/newcss.css new file mode 100644 index 00000000..586a6938 --- /dev/null +++ b/Libs/BookGen.Resources/NewFiles/newcss.css @@ -0,0 +1,451 @@ +:root { + --nc-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --nc-font-mono: Consolas, monaco, 'Ubuntu Mono', 'Liberation Mono', 'Courier New', Courier, monospace; + + /* Light theme */ + --nc-tx-1: #000000; + --nc-tx-2: #1A1A1A; + --nc-bg-1: #FFFFFF; + --nc-bg-2: #F6F8FA; + --nc-bg-3: #E5E7EB; + --nc-lk-1: #0070F3; + --nc-lk-2: #0366D6; + --nc-lk-tx: #FFFFFF; + --nc-ac-1: #79FFE1; + --nc-ac-tx: #0C4047; + + /* Dark theme */ + --nc-d-tx-1: #ffffff; + --nc-d-tx-2: #eeeeee; + --nc-d-bg-1: #000000; + --nc-d-bg-2: #111111; + --nc-d-bg-3: #222222; + --nc-d-lk-1: #3291FF; + --nc-d-lk-2: #0070F3; + --nc-d-lk-tx: #FFFFFF; + --nc-d-ac-1: #7928CA; + --nc-d-ac-tx: #FFFFFF; +} + +@media (prefers-color-scheme: dark) { + :root { + --nc-tx-1: var(--nc-d-tx-1); + --nc-tx-2: var(--nc-d-tx-2); + --nc-bg-1: var(--nc-d-bg-1); + --nc-bg-2: var(--nc-d-bg-2); + --nc-bg-3: var(--nc-d-bg-3); + --nc-lk-1: var(--nc-d-lk-1); + --nc-lk-2: var(--nc-d-lk-2); + --nc-lk-tx: var(--nc--dlk-tx); + --nc-ac-1: var(--nc-d-ac-1); + --nc-ac-tx: var(--nc--dac-tx); + } +} + +* { + /* Reset margins and padding */ + margin: 0; + padding: 0; +} + +address, +area, +article, +aside, +audio, +blockquote, +datalist, +details, +dl, +fieldset, +figure, +form, +input, +iframe, +img, +meter, +nav, +ol, +optgroup, +option, +output, +p, +pre, +progress, +ruby, +section, +table, +textarea, +ul, +video { + /* Margins for most elements */ + margin-bottom: 1rem; +} + +html,input,select,button { + /* Set body font family and some finicky elements */ + font-family: var(--nc-font-sans); +} + +body { + /* Center body in page */ + margin: 0 auto; + max-width: 750px; + padding: 2rem; + border-radius: 6px; + overflow-x: hidden; + word-break: break-word; + overflow-wrap: break-word; + background: var(--nc-bg-1); + + /* Main body text */ + color: var(--nc-tx-2); + font-size: 1.03rem; + line-height: 1.5; +} + +::selection { + /* Set background color for selected text */ + background: var(--nc-ac-1); + color: var(--nc-ac-tx); +} + +h1,h2,h3,h4,h5,h6 { + line-height: 1; + color: var(--nc-tx-1); + padding-top: .875rem; +} + +h1, +h2, +h3 { + color: var(--nc-tx-1); + padding-bottom: 2px; + margin-bottom: 8px; + border-bottom: 1px solid var(--nc-bg-2); +} + +h4, +h5, +h6 { + margin-bottom: .3rem; +} + +h1 { + font-size: 2.25rem; +} + +h2 { + font-size: 1.85rem; +} + +h3 { + font-size: 1.55rem; +} + +h4 { + font-size: 1.25rem; +} + +h5 { + font-size: 1rem; +} + +h6 { + font-size: .875rem; +} + +a { + color: var(--nc-lk-1); +} + +a:hover { + color: var(--nc-lk-2); +} + +abbr:hover { + /* Set the '?' cursor while hovering an abbreviation */ + cursor: help; +} + +blockquote { + padding: 1.5rem; + background: var(--nc-bg-2); + border-left: 5px solid var(--nc-bg-3); +} + +abbr { + cursor: help; +} + +blockquote *:last-child { + padding-bottom: 0; + margin-bottom: 0; +} + +header { + background: var(--nc-bg-2); + border-bottom: 1px solid var(--nc-bg-3); + padding: 2rem 1.5rem; + + /* This sets the right and left margins to cancel out the body's margins. It's width is still the same, but the background stretches across the page's width. */ + + margin: -2rem calc(50% - 50vw) 2rem; + + /* Shorthand for: + + margin-top: -2rem; + margin-bottom: 2rem; + + margin-left: calc(50% - 50vw); + margin-right: calc(50% - 50vw); */ + + padding-left: calc(50vw - 50%); + padding-right: calc(50vw - 50%); +} + +header h1, +header h2, +header h3 { + padding-bottom: 0; + border-bottom: 0; +} + +header > *:first-child { + margin-top: 0; + padding-top: 0; +} + +header > *:last-child { + margin-bottom: 0; +} + +a button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + font-size: 1rem; + display: inline-block; + padding: 6px 12px; + text-align: center; + text-decoration: none; + white-space: nowrap; + background: var(--nc-lk-1); + color: var(--nc-lk-tx); + border: 0; + border-radius: 4px; + box-sizing: border-box; + cursor: pointer; + color: var(--nc-lk-tx); +} + +a button[disabled], +button[disabled], +input[type="submit"][disabled], +input[type="reset"][disabled], +input[type="button"][disabled] { + cursor: default; + opacity: .5; + + /* Set the [X] cursor while hovering a disabled link */ + cursor: not-allowed; +} + +.button:focus, +.button:enabled:hover, +button:focus, +button:enabled:hover, +input[type="submit"]:focus, +input[type="submit"]:enabled:hover, +input[type="reset"]:focus, +input[type="reset"]:enabled:hover, +input[type="button"]:focus, +input[type="button"]:enabled:hover { + background: var(--nc-lk-2); +} + +a img { + margin-bottom: 0px; +} + +code, +pre, +kbd, +samp { + /* Set the font family for monospaced elements */ + font-family: var(--nc-font-mono); +} + +code, +samp, +kbd, +pre { + /* The main preformatted style. This is changed slightly across different cases. */ + background: var(--nc-bg-2); + border: 1px solid var(--nc-bg-3); + border-radius: 4px; + padding: 3px 6px; + /* ↓ font-size is relative to containing element, so it scales for titles*/ + font-size: 0.9em; +} + +kbd { + /* Makes the kbd element look like a keyboard key */ + border-bottom: 3px solid var(--nc-bg-3); +} + +pre { + padding: 1rem 1.4rem; + max-width: 100%; + overflow: auto; +} + +pre code { + /* When is in a
, reset it's formatting to blend in */
+	background: inherit;
+	font-size: inherit;
+	color: inherit;
+	border: 0;
+	padding: 0;
+	margin: 0;
+}
+
+code pre {
+	/* When 
 is in a , reset it's formatting to blend in */
+	display: inline;
+	background: inherit;
+	font-size: inherit;
+	color: inherit;
+	border: 0;
+	padding: 0;
+	margin: 0;
+}
+
+details {
+	/* Make the 
look more "clickable" */ + padding: .6rem 1rem; + background: var(--nc-bg-2); + border: 1px solid var(--nc-bg-3); + border-radius: 4px; +} + +summary { + /* Makes the look more like a "clickable" link with the pointer cursor */ + cursor: pointer; + font-weight: bold; +} + +details[open] { + /* Adjust the
padding while open */ + padding-bottom: .75rem; +} + +details[open] summary { + /* Adjust the
padding while open */ + margin-bottom: 6px; +} + +details[open]>*:last-child { + /* Resets the bottom margin of the last element in the
while
is opened. This prevents double margins/paddings. */ + margin-bottom: 0; +} + +dt { + font-weight: bold; +} + +dd::before { + /* Add an arrow to data table definitions */ + content: '→ '; +} + +hr { + /* Reset the border of the
separator, then set a better line */ + border: 0; + border-bottom: 1px solid var(--nc-bg-3); + margin: 1rem auto; +} + +fieldset { + margin-top: 1rem; + padding: 2rem; + border: 1px solid var(--nc-bg-3); + border-radius: 4px; +} + +legend { + padding: auto .5rem; +} + +table { + /* border-collapse sets the table's elements to share borders, rather than floating as separate "boxes". */ + border-collapse: collapse; + width: 100% +} + +td, +th { + border: 1px solid var(--nc-bg-3); + text-align: left; + padding: .5rem; +} + +th { + background: var(--nc-bg-2); +} + +tr:nth-child(even) { + /* Set every other cell slightly darker. Improves readability. */ + background: var(--nc-bg-2); +} + +table caption { + font-weight: bold; + margin-bottom: .5rem; +} + +textarea { + /* Don't let the