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