[feat:] forgot to git-add the wiki source files... here they are
This commit is contained in:
8
Wiki/EditorWindow.axaml
Normal file
8
Wiki/EditorWindow.axaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="Logof_Client.Wiki.EditorWindow"
|
||||||
|
Title="Wiki Editor (disabled)" Width="400" Height="150">
|
||||||
|
<Grid>
|
||||||
|
<TextBlock Margin="12" TextWrapping="Wrap">The editor feature has been disabled. The Edit button remains for UI compatibility.</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
17
Wiki/EditorWindow.axaml.cs
Normal file
17
Wiki/EditorWindow.axaml.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Logof_Client.Wiki;
|
||||||
|
|
||||||
|
public partial class EditorWindow : Window
|
||||||
|
{
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditorWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
126
Wiki/MarkdownRenderer.cs
Normal file
126
Wiki/MarkdownRenderer.cs
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using Markdig;
|
||||||
|
using Markdig.Syntax;
|
||||||
|
using Markdig.Syntax.Inlines;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Logof_Client.Wiki;
|
||||||
|
|
||||||
|
public static class MarkdownRenderer
|
||||||
|
{
|
||||||
|
public static Control Render(string markdown)
|
||||||
|
{
|
||||||
|
var panel = new StackPanel { Spacing = 6 };
|
||||||
|
if (string.IsNullOrWhiteSpace(markdown)) return panel;
|
||||||
|
|
||||||
|
var doc = Markdown.Parse(markdown);
|
||||||
|
|
||||||
|
foreach (var block in doc)
|
||||||
|
{
|
||||||
|
switch (block)
|
||||||
|
{
|
||||||
|
case HeadingBlock hb:
|
||||||
|
{
|
||||||
|
var text = GetInlineText(hb.Inline);
|
||||||
|
var tb = new TextBlock
|
||||||
|
{
|
||||||
|
Text = text,
|
||||||
|
FontWeight = FontWeight.Bold,
|
||||||
|
Margin = new Avalonia.Thickness(0, hb.Level == 1 ? 6 : 2, 0, 2)
|
||||||
|
};
|
||||||
|
tb.FontSize = hb.Level switch { 1 => 22, 2 => 18, 3 => 16, _ => 14 };
|
||||||
|
panel.Children.Add(tb);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ParagraphBlock pb:
|
||||||
|
{
|
||||||
|
var text = GetInlineText(pb.Inline);
|
||||||
|
var tb = new TextBlock { Text = text, TextWrapping = Avalonia.Media.TextWrapping.Wrap };
|
||||||
|
panel.Children.Add(tb);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case FencedCodeBlock cb:
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var line in cb.Lines.Lines)
|
||||||
|
{
|
||||||
|
sb.Append(line.ToString());
|
||||||
|
}
|
||||||
|
var codeBox = new TextBox
|
||||||
|
{
|
||||||
|
Text = sb.ToString(),
|
||||||
|
FontFamily = "Consolas, monospace",
|
||||||
|
IsReadOnly = true,
|
||||||
|
AcceptsReturn = true
|
||||||
|
};
|
||||||
|
panel.Children.Add(codeBox);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ListBlock lb:
|
||||||
|
{
|
||||||
|
var sp = new StackPanel { Spacing = 2 };
|
||||||
|
var number = 1;
|
||||||
|
foreach (var item in lb)
|
||||||
|
{
|
||||||
|
if (item is ListItemBlock lib)
|
||||||
|
{
|
||||||
|
var itemText = new StringBuilder();
|
||||||
|
foreach (var sub in lib)
|
||||||
|
{
|
||||||
|
if (sub is ParagraphBlock pp)
|
||||||
|
itemText.Append(GetInlineText(pp.Inline));
|
||||||
|
}
|
||||||
|
var tb = new TextBlock { Text = (lb.IsOrdered ? (number++ + ". ") : "• ") + itemText.ToString() };
|
||||||
|
sp.Children.Add(tb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel.Children.Add(sp);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
// fallback: raw text
|
||||||
|
panel.Children.Add(new TextBlock { Text = block.ToString() });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetInlineText(ContainerInline? container)
|
||||||
|
{
|
||||||
|
if (container == null) return string.Empty;
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
foreach (var inline in container)
|
||||||
|
{
|
||||||
|
switch (inline)
|
||||||
|
{
|
||||||
|
case LiteralInline li:
|
||||||
|
sb.Append(li.Content.ToString());
|
||||||
|
break;
|
||||||
|
case EmphasisInline ei:
|
||||||
|
sb.Append(GetInlineText(ei));
|
||||||
|
break;
|
||||||
|
case CodeInline ci:
|
||||||
|
sb.Append(ci.Content);
|
||||||
|
break;
|
||||||
|
case LinkInline li:
|
||||||
|
sb.Append(GetInlineText(li));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.Append(inline.ToString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Wiki/NamePromptWindow.axaml
Normal file
13
Wiki/NamePromptWindow.axaml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="Logof_Client.Wiki.NamePromptWindow"
|
||||||
|
Width="400" Height="120" Title="Input">
|
||||||
|
<StackPanel Margin="8">
|
||||||
|
<TextBlock Name="LblPrompt" />
|
||||||
|
<TextBox Name="TbInput" Margin="0,6,0,6" />
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
|
||||||
|
<Button Name="BtnOk" Content="OK" />
|
||||||
|
<Button Name="BtnCancel" Content="Cancel" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
||||||
26
Wiki/NamePromptWindow.axaml.cs
Normal file
26
Wiki/NamePromptWindow.axaml.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
|
||||||
|
namespace Logof_Client.Wiki;
|
||||||
|
|
||||||
|
public partial class NamePromptWindow : Window
|
||||||
|
{
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NamePromptWindow(string prompt)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
LblPrompt.Text = prompt;
|
||||||
|
BtnOk.Click += BtnOk_Click;
|
||||||
|
BtnCancel.Click += (_, __) => Close(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnOk_Click(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close(TbInput.Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
13
Wiki/WikiItem.cs
Normal file
13
Wiki/WikiItem.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
|
||||||
|
namespace Logof_Client.Wiki;
|
||||||
|
|
||||||
|
public class WikiItem
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Path { get; set; }
|
||||||
|
public bool IsFolder { get; set; }
|
||||||
|
public ObservableCollection<WikiItem> Children { get; } = new();
|
||||||
|
|
||||||
|
public override string ToString() => Name;
|
||||||
|
}
|
||||||
76
Wiki/WikiService.cs
Normal file
76
Wiki/WikiService.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Logof_Client.Wiki;
|
||||||
|
|
||||||
|
public class WikiService
|
||||||
|
{
|
||||||
|
public string WikiRoot { get; }
|
||||||
|
public string WikiRootFullPath { get; }
|
||||||
|
|
||||||
|
public WikiService(string wikiRoot = "wiki")
|
||||||
|
{
|
||||||
|
// prefer global wiki storage path if configured
|
||||||
|
if (Global._instance != null && !string.IsNullOrWhiteSpace(Global._instance.wiki_storage_path))
|
||||||
|
{
|
||||||
|
var cfg = Global._instance.wiki_storage_path;
|
||||||
|
WikiRootFullPath = Path.IsPathRooted(cfg)
|
||||||
|
? cfg
|
||||||
|
: Path.Combine(Directory.GetCurrentDirectory(), cfg);
|
||||||
|
WikiRoot = WikiRootFullPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
WikiRoot = wikiRoot;
|
||||||
|
WikiRootFullPath = Path.Combine(Directory.GetCurrentDirectory(), wikiRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<WikiItem> GetRootItems()
|
||||||
|
{
|
||||||
|
var list = new List<WikiItem>();
|
||||||
|
|
||||||
|
if (!Directory.Exists(WikiRootFullPath)) return list;
|
||||||
|
|
||||||
|
var dirInfo = new DirectoryInfo(WikiRootFullPath);
|
||||||
|
|
||||||
|
// Add folders
|
||||||
|
foreach (var dir in dirInfo.GetDirectories())
|
||||||
|
{
|
||||||
|
list.Add(BuildFolderItem(dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add files in root
|
||||||
|
foreach (var file in dirInfo.GetFiles("*.md"))
|
||||||
|
{
|
||||||
|
list.Add(new WikiItem { Name = file.Name, Path = file.FullName, IsFolder = false });
|
||||||
|
}
|
||||||
|
|
||||||
|
return list.OrderBy(i => i.IsFolder ? 0 : 1).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WikiItem BuildFolderItem(DirectoryInfo dir)
|
||||||
|
{
|
||||||
|
var node = new WikiItem { Name = dir.Name, Path = dir.FullName, IsFolder = true };
|
||||||
|
|
||||||
|
foreach (var subdir in dir.GetDirectories())
|
||||||
|
{
|
||||||
|
node.Children.Add(BuildFolderItem(subdir));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var file in dir.GetFiles("*.md"))
|
||||||
|
{
|
||||||
|
node.Children.Add(new WikiItem { Name = file.Name, Path = file.FullName, IsFolder = false });
|
||||||
|
}
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<string?> LoadFileContentAsync(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path)) return Task.FromResult<string?>(null);
|
||||||
|
return File.ReadAllTextAsync(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user