diff --git a/Directory.Packages.props b/Directory.Packages.props
index 0064960bb..825133906 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -33,6 +33,7 @@
+
diff --git a/Havit.Blazor.Documentation.Mcp/Program.cs b/Havit.Blazor.Documentation.Mcp/Program.cs
index 6e3d4403b..39f8dcf13 100644
--- a/Havit.Blazor.Documentation.Mcp/Program.cs
+++ b/Havit.Blazor.Documentation.Mcp/Program.cs
@@ -1,6 +1,5 @@
using Azure.Monitor.OpenTelemetry.AspNetCore;
using Havit.Blazor.Documentation.Mcp.Diagnostics;
-using Havit.Blazor.Documentation.Mcp.Services;
using Havit.Blazor.Documentation.Mcp.Tools;
using Havit.Blazor.Documentation.Services;
using OpenTelemetry;
@@ -19,7 +18,8 @@
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddSingleton();
-builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services
diff --git a/Havit.Blazor.Documentation.Mcp/Tools/GetComponentDocsTool.cs b/Havit.Blazor.Documentation.Mcp/Tools/GetComponentDocsTool.cs
index 15082819f..da5ae76d9 100644
--- a/Havit.Blazor.Documentation.Mcp/Tools/GetComponentDocsTool.cs
+++ b/Havit.Blazor.Documentation.Mcp/Tools/GetComponentDocsTool.cs
@@ -1,8 +1,6 @@
-using System.ComponentModel;
+using System.ComponentModel;
using System.Diagnostics;
-using System.Reflection;
using Havit.Blazor.Documentation.Mcp.Diagnostics;
-using Havit.Blazor.Documentation.Mcp.Services;
using Havit.Blazor.Documentation.Model;
using Havit.Blazor.Documentation.Services;
using ModelContextProtocol.Server;
@@ -14,15 +12,15 @@ namespace Havit.Blazor.Documentation.Mcp.Tools;
///
internal class GetComponentDocsTool
{
- private static readonly Assembly s_documentationAssembly = typeof(DocumentationCatalogService).Assembly;
-
private readonly IApiDocModelProvider _modelProvider;
- private readonly McpDocMarkdownRenderer _renderer;
+ private readonly IDocMarkdownRenderer _renderer;
+ private readonly IComponentDemosProvider _componentDemosProvider;
- public GetComponentDocsTool(IApiDocModelProvider modelProvider, McpDocMarkdownRenderer renderer)
+ public GetComponentDocsTool(IApiDocModelProvider modelProvider, IDocMarkdownRenderer renderer, IComponentDemosProvider componentDemosProvider)
{
_modelProvider = modelProvider;
_renderer = renderer;
+ _componentDemosProvider = componentDemosProvider;
}
///
@@ -44,7 +42,7 @@ public string GetComponentDocs(
}
ApiDocModel model = _modelProvider.GetApiDocModel(type);
- IReadOnlyList sampleNames = GetSampleNames(componentName);
+ IReadOnlyList sampleNames = _componentDemosProvider.GetComponentDemoFileNames(componentName);
string markdown = _renderer.RenderComponentDoc(model, sampleNames);
activity?.SetTag("mcp.tool.result", "success");
@@ -53,45 +51,4 @@ public string GetComponentDocs(
return markdown;
}
-
- private static IReadOnlyList GetSampleNames(string componentName)
- {
- // Normalize component name by stripping generic type arguments (e.g., "HxGrid" -> "HxGrid")
- // This matches the behavior of ApiTypeHelper.GetType()
- int openingBracePosition = componentName.IndexOf("<");
- if (openingBracePosition > 0)
- {
- componentName = componentName[..openingBracePosition];
- }
-
- return s_documentationAssembly.GetManifestResourceNames()
- .Where(r => r.EndsWith(".razor", StringComparison.Ordinal)
- && r.Contains("Demo", StringComparison.Ordinal)
- && ExtractFileName(r).StartsWith(componentName + "_", StringComparison.OrdinalIgnoreCase))
- .OrderBy(r => r, StringComparer.Ordinal)
- .Select(r => ExtractFileName(r))
- .ToList();
- }
-
- ///
- /// Extracts the file name from an embedded resource name.
- /// E.g. "Havit.Blazor.Documentation.Pages.Components.HxAccordionDoc.HxAccordion_PlainDemo.razor" → "HxAccordion_PlainDemo.razor".
- ///
- private static string ExtractFileName(string resourceName)
- {
- int extensionIndex = resourceName.LastIndexOf(".razor", StringComparison.Ordinal);
- if (extensionIndex < 0)
- {
- return resourceName;
- }
-
- string withoutExtension = resourceName[..extensionIndex];
- int lastDot = withoutExtension.LastIndexOf('.');
- if (lastDot < 0)
- {
- return resourceName;
- }
-
- return withoutExtension[(lastDot + 1)..] + ".razor";
- }
}
diff --git a/Havit.Blazor.Documentation.Mcp/Tools/GetComponentSamplesTool.cs b/Havit.Blazor.Documentation.Mcp/Tools/GetComponentSamplesTool.cs
index 522ab7a5c..c2519dc84 100644
--- a/Havit.Blazor.Documentation.Mcp/Tools/GetComponentSamplesTool.cs
+++ b/Havit.Blazor.Documentation.Mcp/Tools/GetComponentSamplesTool.cs
@@ -1,6 +1,5 @@
-using System.ComponentModel;
+using System.ComponentModel;
using System.Diagnostics;
-using System.Reflection;
using System.Text;
using Havit.Blazor.Documentation.Mcp.Diagnostics;
using Havit.Blazor.Documentation.Services;
@@ -13,7 +12,12 @@ namespace Havit.Blazor.Documentation.Mcp.Tools;
///
internal class GetComponentSamplesTool
{
- private static readonly Assembly s_documentationAssembly = typeof(DocumentationCatalogService).Assembly;
+ private readonly IComponentDemosProvider _componentDemosProvider;
+
+ public GetComponentSamplesTool(IComponentDemosProvider componentDemosProvider)
+ {
+ _componentDemosProvider = componentDemosProvider;
+ }
///
/// Returns all demo samples for a given HAVIT Blazor component in Markdown format.
@@ -26,21 +30,16 @@ public string GetComponentSamples(
using Activity activity = McpToolActivitySource.Source.StartActivity("get_component_samples");
activity?.SetTag("mcp.tool.parameter.componentName", componentName);
- string[] resourceNames = s_documentationAssembly.GetManifestResourceNames()
- .Where(r => r.EndsWith(".razor", StringComparison.Ordinal)
- && r.Contains("Demo", StringComparison.Ordinal)
- && ExtractFileName(r).StartsWith(componentName + "_", StringComparison.OrdinalIgnoreCase))
- .OrderBy(r => r, StringComparer.Ordinal)
- .ToArray();
+ IReadOnlyList resourceNames = _componentDemosProvider.GetComponentDemoResourceNames(componentName);
- if (resourceNames.Length == 0)
+ if (resourceNames.Count == 0)
{
activity?.SetTag("mcp.tool.result", "not_found");
return $"No demo samples found for component '{componentName}'.";
}
activity?.SetTag("mcp.tool.result", "success");
- activity?.SetTag("mcp.tool.sampleCount", resourceNames.Length);
+ activity?.SetTag("mcp.tool.sampleCount", resourceNames.Count);
StringBuilder sb = new StringBuilder();
sb.AppendLine($"# {componentName} — Demo Samples");
@@ -48,11 +47,8 @@ public string GetComponentSamples(
foreach (string resourceName in resourceNames)
{
- string fileName = ExtractFileName(resourceName);
-
- using Stream stream = s_documentationAssembly.GetManifestResourceStream(resourceName);
- using StreamReader reader = new StreamReader(stream);
- string content = reader.ReadToEnd();
+ string fileName = ComponentDemosProvider.ExtractFileName(resourceName);
+ string content = _componentDemosProvider.GetDemoContentByResourceName(resourceName);
sb.AppendLine($"## {fileName}");
sb.AppendLine();
@@ -64,26 +60,4 @@ public string GetComponentSamples(
return sb.ToString();
}
-
- ///
- /// Extracts the file name from an embedded resource name.
- /// E.g. "Havit.Blazor.Documentation.Pages.Components.HxAccordionDoc.HxAccordion_PlainDemo.razor" → "HxAccordion_PlainDemo.razor".
- ///
- private static string ExtractFileName(string resourceName)
- {
- int extensionIndex = resourceName.LastIndexOf(".razor", StringComparison.Ordinal);
- if (extensionIndex < 0)
- {
- return resourceName;
- }
-
- string withoutExtension = resourceName[..extensionIndex];
- int lastDot = withoutExtension.LastIndexOf('.');
- if (lastDot < 0)
- {
- return resourceName;
- }
-
- return withoutExtension[(lastDot + 1)..] + ".razor";
- }
}
diff --git a/Havit.Blazor.Documentation.Mcp/Tools/GetTypeDocTool.cs b/Havit.Blazor.Documentation.Mcp/Tools/GetTypeDocTool.cs
index 3c11c6b97..9a97812cd 100644
--- a/Havit.Blazor.Documentation.Mcp/Tools/GetTypeDocTool.cs
+++ b/Havit.Blazor.Documentation.Mcp/Tools/GetTypeDocTool.cs
@@ -1,7 +1,6 @@
using System.ComponentModel;
using System.Diagnostics;
using Havit.Blazor.Documentation.Mcp.Diagnostics;
-using Havit.Blazor.Documentation.Mcp.Services;
using Havit.Blazor.Documentation.Model;
using Havit.Blazor.Documentation.Services;
using ModelContextProtocol.Server;
@@ -15,9 +14,9 @@ namespace Havit.Blazor.Documentation.Mcp.Tools;
internal class GetTypeDocTool
{
private readonly IApiDocModelProvider _modelProvider;
- private readonly McpDocMarkdownRenderer _renderer;
+ private readonly IDocMarkdownRenderer _renderer;
- public GetTypeDocTool(IApiDocModelProvider modelProvider, McpDocMarkdownRenderer renderer)
+ public GetTypeDocTool(IApiDocModelProvider modelProvider, IDocMarkdownRenderer renderer)
{
_modelProvider = modelProvider;
_renderer = renderer;
diff --git a/Havit.Blazor.Documentation.RepoDumpGenerator/Havit.Blazor.Documentation.RepoDumpGenerator.csproj b/Havit.Blazor.Documentation.RepoDumpGenerator/Havit.Blazor.Documentation.RepoDumpGenerator.csproj
new file mode 100644
index 000000000..cf3fc17d7
--- /dev/null
+++ b/Havit.Blazor.Documentation.RepoDumpGenerator/Havit.Blazor.Documentation.RepoDumpGenerator.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net10.0
+ Havit.Blazor.Documentation.RepoDumpGenerator
+ Havit.Blazor.Documentation.RepoDumpGenerator
+ Major
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Havit.Blazor.Documentation.RepoDumpGenerator/Program.cs b/Havit.Blazor.Documentation.RepoDumpGenerator/Program.cs
new file mode 100644
index 000000000..07ed87168
--- /dev/null
+++ b/Havit.Blazor.Documentation.RepoDumpGenerator/Program.cs
@@ -0,0 +1,37 @@
+using Havit.Blazor.Documentation.RepoDumpGenerator.Services;
+using Havit.Blazor.Documentation.Services;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
+
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+builder.Services.AddSingleton();
+
+using IHost host = builder.Build();
+
+string outputRoot = Path.Combine(FindRepoRoot(AppContext.BaseDirectory), "docs", "generated");
+
+DocDumpService dumpService = host.Services.GetRequiredService();
+dumpService.Run(outputRoot);
+
+// --- Helper methods ---
+
+static string FindRepoRoot(string startDir)
+{
+ string dir = startDir;
+ while (dir is not null)
+ {
+ if (File.Exists(Path.Combine(dir, "Havit.Blazor.sln")))
+ {
+ return dir;
+ }
+ dir = Path.GetDirectoryName(dir);
+ }
+ throw new InvalidOperationException("Could not find repository root (Havit.Blazor.sln).");
+}
diff --git a/Havit.Blazor.Documentation.RepoDumpGenerator/Services/DocDumpService.cs b/Havit.Blazor.Documentation.RepoDumpGenerator/Services/DocDumpService.cs
new file mode 100644
index 000000000..a75bbc086
--- /dev/null
+++ b/Havit.Blazor.Documentation.RepoDumpGenerator/Services/DocDumpService.cs
@@ -0,0 +1,169 @@
+using System.Text;
+using Havit.Blazor.Documentation.Model;
+using Havit.Blazor.Documentation.Services;
+
+namespace Havit.Blazor.Documentation.RepoDumpGenerator.Services;
+
+///
+/// Generates markdown documentation files for all components and types from the documentation catalog.
+///
+internal class DocDumpService
+{
+ private readonly IDocumentationCatalogService _catalogService;
+ private readonly IApiDocModelProvider _modelProvider;
+ private readonly IDocMarkdownRenderer _renderer;
+ private readonly IComponentDemosProvider _componentDemosProvider;
+
+ public DocDumpService(
+ IDocumentationCatalogService catalogService,
+ IApiDocModelProvider modelProvider,
+ IDocMarkdownRenderer renderer,
+ IComponentDemosProvider componentDemosProvider)
+ {
+ _catalogService = catalogService;
+ _modelProvider = modelProvider;
+ _renderer = renderer;
+ _componentDemosProvider = componentDemosProvider;
+ }
+
+ ///
+ /// Runs the documentation dump to the specified output directory.
+ ///
+ public void Run(string outputRoot)
+ {
+ // Clean output directory
+ if (Directory.Exists(outputRoot))
+ {
+ Directory.Delete(outputRoot, recursive: true);
+ }
+
+ var allItems = _catalogService.GetAll();
+
+ (int componentCount, int demoCount) = DumpComponents(allItems, outputRoot);
+ int typeCount = DumpTypes(allItems, outputRoot);
+
+ Console.WriteLine();
+ Console.WriteLine($"Done. Generated {componentCount} component docs, {demoCount} demo files, {typeCount} type docs.");
+ Console.WriteLine($"Output: {outputRoot}");
+ }
+
+ private (int ComponentCount, int DemoCount) DumpComponents(IReadOnlyList allItems, string outputRoot)
+ {
+ var componentCount = 0;
+ var demoCount = 0;
+
+ foreach (var item in allItems)
+ {
+ if (!item.Href.StartsWith("/components/Hx", StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ // Extract component name from href
+ // For anchored hrefs like /components/HxNavLink#HxNavLink, extract the anchor part
+ // Skip anchors that aren't component names (don't start with Hx)
+ string componentName;
+ int anchorIndex = item.Href.IndexOf('#');
+ if (anchorIndex >= 0)
+ {
+ var anchorPart = item.Href.Substring(anchorIndex + 1);
+
+ if (string.IsNullOrEmpty(anchorPart))
+ {
+ continue; // Skip malformed hrefs with empty anchors
+ }
+
+ // Only include if the anchor is a component name (starts with Hx)
+ if (!anchorPart.StartsWith("Hx", StringComparison.Ordinal))
+ {
+ continue; // Skip sub-pages like HxGrid#InfiniteScroll
+ }
+
+ componentName = anchorPart;
+ }
+ else
+ {
+ // Extract component name from path (e.g., "/components/HxButton" -> "HxButton")
+ // LastIndexOf('/') is guaranteed to find at least one '/' due to the filter above
+ int lastSlashIndex = item.Href.LastIndexOf('/');
+ componentName = item.Href.Substring(lastSlashIndex + 1);
+ }
+ var type = ApiTypeHelper.GetType(componentName, includeTypesContainingTypeName: true);
+ if (type is null)
+ {
+ Console.WriteLine($" SKIP component (type not found): {componentName}");
+ continue;
+ }
+
+ // Component API doc
+ var model = _modelProvider.GetApiDocModel(type);
+ var sampleNames = _componentDemosProvider.GetComponentDemoFileNames(componentName);
+ var markdown = _renderer.RenderComponentDoc(model, sampleNames, includeMcpToolHint: false);
+
+ var componentDir = outputRoot;
+ Directory.CreateDirectory(componentDir);
+ var filePath = Path.Combine(componentDir, $"{componentName}.md");
+ File.WriteAllText(filePath, markdown, Encoding.UTF8);
+ componentCount++;
+ Console.WriteLine($" Component: {componentName}");
+
+ // Demos
+ if (sampleNames.Count > 0)
+ {
+ var demosDir = Path.Combine(outputRoot, "demos", componentName);
+ Directory.CreateDirectory(demosDir);
+
+ foreach (var sampleName in sampleNames)
+ {
+ var content = _componentDemosProvider.GetDemoContentByFileName(sampleName);
+ if (content is not null)
+ {
+ var mdFileName = Path.ChangeExtension(sampleName, ".md");
+ var demoFilePath = Path.Combine(demosDir, mdFileName);
+ var demoMarkdown = $"# {sampleName}\n\n```razor\n{content}\n```\n";
+ File.WriteAllText(demoFilePath, demoMarkdown, Encoding.UTF8);
+ demoCount++;
+ }
+ }
+ }
+ }
+
+ return (componentCount, demoCount);
+ }
+
+ private int DumpTypes(IReadOnlyList allItems, string outputRoot)
+ {
+ var typeCount = 0;
+
+ foreach (var item in allItems)
+ {
+ if (!item.Href.StartsWith("/types/", StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ var typeName = item.Href.Split('/').Last();
+ // Some catalog entries have spaces in the href (e.g. "/types/MultiSelect Settings") — normalize
+ typeName = typeName.Replace(" ", string.Empty);
+
+ var type = ApiTypeHelper.GetType(typeName, includeTypesContainingTypeName: true);
+ if (type is null)
+ {
+ Console.WriteLine($" SKIP type (not found): {typeName}");
+ continue;
+ }
+
+ var model = _modelProvider.GetApiDocModel(type);
+ var markdown = _renderer.RenderTypeDoc(model);
+
+ var typesDir = Path.Combine(outputRoot, "types");
+ Directory.CreateDirectory(typesDir);
+ var filePath = Path.Combine(typesDir, $"{typeName}.md");
+ File.WriteAllText(filePath, markdown, Encoding.UTF8);
+ typeCount++;
+ Console.WriteLine($" Type: {typeName}");
+ }
+
+ return typeCount;
+ }
+}
diff --git a/Havit.Blazor.Documentation/Services/ApiDocModelBuilder.cs b/Havit.Blazor.Documentation/Services/ApiDocModelBuilder.cs
index 540663fb6..d343c358a 100644
--- a/Havit.Blazor.Documentation/Services/ApiDocModelBuilder.cs
+++ b/Havit.Blazor.Documentation/Services/ApiDocModelBuilder.cs
@@ -118,8 +118,6 @@ private void AdjustDelegate(ApiDocModel model)
if (genericTypeArgument is not null)
{
string genericTypeArgumentName = genericTypeArgument.ToString();
- Console.WriteLine("genericTypeArgument.ToString(): " + genericTypeArgumentName);
-
returnType = $"Task<{ApiRenderer.FormatType(genericTypeArgumentName, true)}> ";
}
else
diff --git a/Havit.Blazor.Documentation/Services/ComponentDemosProvider.cs b/Havit.Blazor.Documentation/Services/ComponentDemosProvider.cs
new file mode 100644
index 000000000..b820ef210
--- /dev/null
+++ b/Havit.Blazor.Documentation/Services/ComponentDemosProvider.cs
@@ -0,0 +1,104 @@
+using System.Reflection;
+
+namespace Havit.Blazor.Documentation.Services;
+
+///
+/// Provides access to demo resources embedded in the documentation assembly.
+///
+public class ComponentDemosProvider : IComponentDemosProvider
+{
+ private static readonly Assembly s_documentationAssembly = typeof(ComponentDemosProvider).Assembly;
+
+ ///
+ /// Returns the file names of all demos for the specified component.
+ ///
+ public IReadOnlyList GetComponentDemoFileNames(string componentName)
+ {
+ // Normalize component name by stripping generic type arguments (e.g., "HxGrid" -> "HxGrid")
+ int openingBracePosition = componentName.IndexOf("<", StringComparison.Ordinal);
+ if (openingBracePosition > 0)
+ {
+ componentName = componentName[..openingBracePosition];
+ }
+
+ return s_documentationAssembly.GetManifestResourceNames()
+ .Where(r => r.EndsWith(".razor", StringComparison.Ordinal)
+ && r.Contains("Demo", StringComparison.Ordinal)
+ && ExtractFileName(r).StartsWith(componentName + "_", StringComparison.OrdinalIgnoreCase))
+ .OrderBy(r => r, StringComparer.Ordinal)
+ .Select(r => ExtractFileName(r))
+ .ToList();
+ }
+
+ ///
+ /// Returns the resource names of all demo samples for the specified component.
+ ///
+ public IReadOnlyList GetComponentDemoResourceNames(string componentName)
+ {
+ return s_documentationAssembly.GetManifestResourceNames()
+ .Where(r => r.EndsWith(".razor", StringComparison.Ordinal)
+ && r.Contains("Demo", StringComparison.Ordinal)
+ && ExtractFileName(r).StartsWith(componentName + "_", StringComparison.OrdinalIgnoreCase))
+ .OrderBy(r => r, StringComparer.Ordinal)
+ .ToArray();
+ }
+
+ ///
+ /// Reads the content of a demo by its file name.
+ /// Returns null if the sample is not found.
+ ///
+ public string GetDemoContentByFileName(string demoFileName)
+ {
+ string resourceName = s_documentationAssembly.GetManifestResourceNames()
+ .Where(r => r.EndsWith(".razor", StringComparison.Ordinal)
+ && r.Contains("Demo", StringComparison.Ordinal)
+ && ExtractFileName(r) == demoFileName)
+ .FirstOrDefault();
+
+ if (resourceName is null)
+ {
+ return null;
+ }
+
+ using Stream stream = s_documentationAssembly.GetManifestResourceStream(resourceName);
+ using StreamReader reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+
+ ///
+ /// Reads the content of a demo sample by its full embedded resource name.
+ /// Returns null if the resource is not found.
+ ///
+ public string GetDemoContentByResourceName(string resourceName)
+ {
+ using Stream stream = s_documentationAssembly.GetManifestResourceStream(resourceName);
+ if (stream == null)
+ {
+ return null;
+ }
+ using StreamReader reader = new StreamReader(stream);
+ return reader.ReadToEnd();
+ }
+
+ ///
+ /// Extracts the file name from an embedded resource name.
+ /// E.g. "Havit.Blazor.Documentation.Pages.Components.HxAccordionDoc.HxAccordion_PlainDemo.razor" → "HxAccordion_PlainDemo.razor".
+ ///
+ public static string ExtractFileName(string resourceName)
+ {
+ int extensionIndex = resourceName.LastIndexOf(".razor", StringComparison.Ordinal);
+ if (extensionIndex < 0)
+ {
+ return resourceName;
+ }
+
+ string withoutExtension = resourceName[..extensionIndex];
+ int lastDot = withoutExtension.LastIndexOf('.');
+ if (lastDot < 0)
+ {
+ return resourceName;
+ }
+
+ return withoutExtension[(lastDot + 1)..] + ".razor";
+ }
+}
diff --git a/Havit.Blazor.Documentation.Mcp/Services/McpDocMarkdownRenderer.cs b/Havit.Blazor.Documentation/Services/DocMarkdownRenderer.cs
similarity index 93%
rename from Havit.Blazor.Documentation.Mcp/Services/McpDocMarkdownRenderer.cs
rename to Havit.Blazor.Documentation/Services/DocMarkdownRenderer.cs
index a6f44b26c..c5b610bb1 100644
--- a/Havit.Blazor.Documentation.Mcp/Services/McpDocMarkdownRenderer.cs
+++ b/Havit.Blazor.Documentation/Services/DocMarkdownRenderer.cs
@@ -1,14 +1,12 @@
-using System.Text;
-using System.Text.RegularExpressions;
+using System.Text.RegularExpressions;
using Havit.Blazor.Documentation.Model;
-using Havit.Blazor.Documentation.Services;
-namespace Havit.Blazor.Documentation.Mcp.Services;
+namespace Havit.Blazor.Documentation.Services;
///
-/// Renders a into a Markdown string suitable for MCP tool responses.
+/// Renders an into a Markdown string.
///
-internal class McpDocMarkdownRenderer
+public class DocMarkdownRenderer : IDocMarkdownRenderer
{
///
/// Renders the type API documentation as markdown (parameters, properties, events, methods).
@@ -23,11 +21,11 @@ public string RenderTypeDoc(ApiDocModel model)
///
/// Renders the component API documentation as markdown, including a list of available demo samples.
///
- public string RenderComponentDoc(ApiDocModel model, IReadOnlyList sampleNames)
+ public string RenderComponentDoc(ApiDocModel model, IReadOnlyList sampleNames, bool includeMcpToolHint = true)
{
- StringBuilder sb = new StringBuilder();
+ var sb = new StringBuilder();
RenderTypeDocCore(sb, model);
- RenderSampleList(sb, sampleNames);
+ RenderSampleList(sb, sampleNames, includeMcpToolHint);
return sb.ToString();
}
@@ -230,7 +228,7 @@ private static void RenderStaticMethods(StringBuilder sb, ApiDocModel model)
sb.AppendLine();
}
- private static void RenderSampleList(StringBuilder sb, IReadOnlyList sampleNames)
+ private static void RenderSampleList(StringBuilder sb, IReadOnlyList sampleNames, bool includeMcpToolHint)
{
if (sampleNames is null || sampleNames.Count == 0)
{
@@ -239,8 +237,12 @@ private static void RenderSampleList(StringBuilder sb, IReadOnlyList sam
sb.AppendLine("## Available demo samples");
sb.AppendLine();
- sb.AppendLine("Use the `get_component_samples` tool to retrieve the full source code.");
- sb.AppendLine();
+
+ if (includeMcpToolHint)
+ {
+ sb.AppendLine("Use the `get_component_samples` tool to retrieve the full source code.");
+ sb.AppendLine();
+ }
foreach (string name in sampleNames)
{
diff --git a/Havit.Blazor.Documentation/Services/DocumentationCatalogService.cs b/Havit.Blazor.Documentation/Services/DocumentationCatalogService.cs
index d07cf8296..f90dec581 100644
--- a/Havit.Blazor.Documentation/Services/DocumentationCatalogService.cs
+++ b/Havit.Blazor.Documentation/Services/DocumentationCatalogService.cs
@@ -124,7 +124,7 @@ public class DocumentationCatalogService : IDocumentationCatalogService
new("/types/ContextMenuSettings", "ContextMenu Settings", "defaults", DefaultsLevel),
new("/types/FormValueSettings", "FormValue Settings", "defaults", DefaultsLevel),
new("/types/GridSettings", "Grid Settings", "defaults", DefaultsLevel),
- new("/types/InputsSettings", "Inputs Settings", "defaults", DefaultsLevel),
+ new("/types/InputSettings", "Inputs Settings", "defaults", DefaultsLevel),
new("/types/InputFileSettings", "InputFile Settings", "defaults", DefaultsLevel),
new("/types/InputRangeSettings", "InputRangeSettings Settings", "defaults", DefaultsLevel),
new("/types/InputDateRangeSettings", "InputDateRange Settings", "defaults", DefaultsLevel),
@@ -150,7 +150,7 @@ public class DocumentationCatalogService : IDocumentationCatalogService
// Enums
new("/types/BadgeType", "BadgeType", "enum shape", EnumsLevel),
- new("/types/BootstrapTheme", "BootstrapTheme", "enum", EnumsLevel),
+ new("/types/BootstrapFlavor", "BootstrapFlavor", "enum", EnumsLevel),
new("/types/ButtonGroupOrientation", "ButtonGroupOrientation", "enum", EnumsLevel),
new("/types/ButtonGroupSize", "ButtonGroupSize", "enum", EnumsLevel),
new("/types/ButtonIconPlacement", "ButtonIconPlacement", "enum", EnumsLevel),
diff --git a/Havit.Blazor.Documentation/Services/IComponentDemosProvider.cs b/Havit.Blazor.Documentation/Services/IComponentDemosProvider.cs
new file mode 100644
index 000000000..11f7be6b4
--- /dev/null
+++ b/Havit.Blazor.Documentation/Services/IComponentDemosProvider.cs
@@ -0,0 +1,28 @@
+namespace Havit.Blazor.Documentation.Services;
+
+///
+/// Provides access to demo resources embedded in the documentation assembly.
+///
+public interface IComponentDemosProvider
+{
+ ///
+ /// Returns the file names of all demos for the specified component.
+ ///
+ IReadOnlyList GetComponentDemoFileNames(string componentName);
+
+ ///
+ /// Returns the resource names of all demo samples for the specified component.
+ ///
+ IReadOnlyList GetComponentDemoResourceNames(string componentName);
+
+ ///
+ /// Reads the content of a demo by its file name.
+ /// Returns null if the sample is not found.
+ ///
+ string GetDemoContentByFileName(string demoFileName);
+
+ ///
+ /// Reads the content of a demo sample by its full embedded resource name.
+ ///
+ string GetDemoContentByResourceName(string resourceName);
+}
diff --git a/Havit.Blazor.Documentation/Services/IDocMarkdownRenderer.cs b/Havit.Blazor.Documentation/Services/IDocMarkdownRenderer.cs
new file mode 100644
index 000000000..7976634b1
--- /dev/null
+++ b/Havit.Blazor.Documentation/Services/IDocMarkdownRenderer.cs
@@ -0,0 +1,19 @@
+using Havit.Blazor.Documentation.Model;
+
+namespace Havit.Blazor.Documentation.Services;
+
+///
+/// Renders an into a Markdown string.
+///
+public interface IDocMarkdownRenderer
+{
+ ///
+ /// Renders the type API documentation as markdown (parameters, properties, events, methods).
+ ///
+ string RenderTypeDoc(ApiDocModel model);
+
+ ///
+ /// Renders the component API documentation as markdown, including a list of available demo samples.
+ ///
+ string RenderComponentDoc(ApiDocModel model, IReadOnlyList sampleNames, bool includeMcpToolHint = true);
+}
diff --git a/Havit.Blazor.sln b/Havit.Blazor.sln
index fef6bb859..d03dbb101 100644
--- a/Havit.Blazor.sln
+++ b/Havit.Blazor.sln
@@ -93,6 +93,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Havit.Blazor.E2ETests", "Ha
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Havit.Blazor.Documentation.Mcp", "Havit.Blazor.Documentation.Mcp\Havit.Blazor.Documentation.Mcp.csproj", "{3E23B990-7FA7-4323-82E9-D311A6738D4F}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Havit.Blazor.Documentation.RepoDumpGenerator", "Havit.Blazor.Documentation.RepoDumpGenerator\Havit.Blazor.Documentation.RepoDumpGenerator.csproj", "{45BB304F-AA57-43A6-9EFA-3720E7FCEE6D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -211,6 +213,10 @@ Global
{3E23B990-7FA7-4323-82E9-D311A6738D4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E23B990-7FA7-4323-82E9-D311A6738D4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E23B990-7FA7-4323-82E9-D311A6738D4F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {45BB304F-AA57-43A6-9EFA-3720E7FCEE6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {45BB304F-AA57-43A6-9EFA-3720E7FCEE6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {45BB304F-AA57-43A6-9EFA-3720E7FCEE6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {45BB304F-AA57-43A6-9EFA-3720E7FCEE6D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Havit.Blazor.slnLaunch b/Havit.Blazor.slnLaunch
index 0959e0227..bba3fb22c 100644
--- a/Havit.Blazor.slnLaunch
+++ b/Havit.Blazor.slnLaunch
@@ -37,5 +37,14 @@
"Action": "Start"
}
]
+ },
+ {
+ "Name": "RepoDumpGenerator",
+ "Projects": [
+ {
+ "Path": "Havit.Blazor.Documentation.RepoDumpGenerator\\Havit.Blazor.Documentation.RepoDumpGenerator.csproj",
+ "Action": "Start"
+ }
+ ]
}
]
\ No newline at end of file