diff --git a/Wiki/EditorWindow.axaml b/Wiki/EditorWindow.axaml
new file mode 100644
index 0000000..580ba78
--- /dev/null
+++ b/Wiki/EditorWindow.axaml
@@ -0,0 +1,8 @@
+
+
+ The editor feature has been disabled. The Edit button remains for UI compatibility.
+
+
diff --git a/Wiki/EditorWindow.axaml.cs b/Wiki/EditorWindow.axaml.cs
new file mode 100644
index 0000000..a645229
--- /dev/null
+++ b/Wiki/EditorWindow.axaml.cs
@@ -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();
+ }
+}
diff --git a/Wiki/MarkdownRenderer.cs b/Wiki/MarkdownRenderer.cs
new file mode 100644
index 0000000..c7396f3
--- /dev/null
+++ b/Wiki/MarkdownRenderer.cs
@@ -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();
+ }
+}
diff --git a/Wiki/NamePromptWindow.axaml b/Wiki/NamePromptWindow.axaml
new file mode 100644
index 0000000..d48dd86
--- /dev/null
+++ b/Wiki/NamePromptWindow.axaml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Wiki/NamePromptWindow.axaml.cs b/Wiki/NamePromptWindow.axaml.cs
new file mode 100644
index 0000000..05e52ae
--- /dev/null
+++ b/Wiki/NamePromptWindow.axaml.cs
@@ -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);
+ }
+}
+
diff --git a/Wiki/WikiItem.cs b/Wiki/WikiItem.cs
new file mode 100644
index 0000000..3d72c5f
--- /dev/null
+++ b/Wiki/WikiItem.cs
@@ -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 Children { get; } = new();
+
+ public override string ToString() => Name;
+}
diff --git a/Wiki/WikiService.cs b/Wiki/WikiService.cs
new file mode 100644
index 0000000..a7f40c0
--- /dev/null
+++ b/Wiki/WikiService.cs
@@ -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 GetRootItems()
+ {
+ var list = new List();
+
+ 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 LoadFileContentAsync(string path)
+ {
+ if (!File.Exists(path)) return Task.FromResult(null);
+ return File.ReadAllTextAsync(path);
+ }
+}