Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample App Issue/Discussion Links and Markdown Rendering #308

Merged
merged 9 commits into from
Dec 2, 2022
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Without any front matter.
[DataRow(4, DisplayName = "Keywords")]
[DataRow(7, DisplayName = "Category")]
[DataRow(8, DisplayName = "Subcategory")]
[DataRow(9, DisplayName = "GitHub Discussion Id")]
[DataRow(10, DisplayName = "GitHub Issue Id")]
[TestMethod]
public void MissingFrontMatterField(int removeline)
{
Expand All @@ -60,6 +62,8 @@ public void MissingFrontMatterField(int removeline)
- csharp
category: Controls
subcategory: Layout
discussion-id: 0
issue-id: 0
---
# This is some test documentation...
> [!SAMPLE Sample]
Expand All @@ -85,6 +89,8 @@ public void MarkdownInvalidSampleReference()
- csharp
category: Controls
subcategory: Layout
discussion-id: 0
issue-id: 0
---
# This is some test documentation...
> [!SAMPLE SampINVALIDle]
Expand All @@ -108,9 +114,11 @@ public void DocumentationMissingSample()
- csharp
category: Controls
subcategory: Layout
discussion-id: 0
issue-id: 0
---
# This is some test documentation...
Without any front matter.";
Without any sample.";

VerifyGeneratedDiagnostics<ToolkitSampleMetadataGenerator>(SimpleSource, markdown,
DiagnosticDescriptors.DocumentationHasNoSamples.Id,
Expand All @@ -129,11 +137,59 @@ public void DocumentationValid()
- csharp
category: Controls
subcategory: Layout
discussion-id: 0
issue-id: 0
---
# This is some test documentation...
Without any front matter.
Which is valid.
> [!SAMPLE Sample]";

VerifyGeneratedDiagnostics<ToolkitSampleMetadataGenerator>(SimpleSource, markdown);
}

[TestMethod]
public void DocumentationInvalidDiscussionId()
{
string markdown = @"---
title: Canvas Layout
author: mhawker
description: A canvas-like VirtualizingLayout for use in an ItemsRepeater
keywords: CanvasLayout, ItemsRepeater, VirtualizingLayout, Canvas, Layout, Panel, Arrange
dev_langs:
- csharp
category: Controls
subcategory: Layout
discussion-id: https://github.com/1234
issue-id: 0
---
# This is some test documentation...
Without an invalid discussion id.";

VerifyGeneratedDiagnostics<ToolkitSampleMetadataGenerator>(string.Empty, markdown,
DiagnosticDescriptors.MarkdownYAMLFrontMatterException.Id,
DiagnosticDescriptors.DocumentationHasNoSamples.Id);
}

[TestMethod]
public void DocumentationInvalidIssueId()
{
string markdown = @"---
title: Canvas Layout
author: mhawker
description: A canvas-like VirtualizingLayout for use in an ItemsRepeater
keywords: CanvasLayout, ItemsRepeater, VirtualizingLayout, Canvas, Layout, Panel, Arrange
dev_langs:
- csharp
category: Controls
subcategory: Layout
discussion-id: 0
issue-id: https://github.com/1234
---
# This is some test documentation...
Without an invalid discussion id.";

VerifyGeneratedDiagnostics<ToolkitSampleMetadataGenerator>(string.Empty, markdown,
DiagnosticDescriptors.MarkdownYAMLFrontMatterException.Id,
DiagnosticDescriptors.DocumentationHasNoSamples.Id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ public sealed class ToolkitFrontMatter : DocsFrontMatter
public ToolkitSampleCategory Category { get; set; }
public ToolkitSampleSubcategory Subcategory { get; set; }

public int DiscussionId { get; set; }
public int IssueId { get; set; }

//// Extra Metadata needed for Sample App
public string? FilePath { get; set; }

public string[]? SampleIdReferences { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ public partial class ToolkitSampleMetadataGenerator
private const string FrontMatterRegexSubcategoryExpression = @"^subcategory:\s*(?<subcategory>.*)$";
private static readonly Regex FrontMatterRegexSubcategory = new Regex(FrontMatterRegexSubcategoryExpression, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);

private const string FrontMatterRegexDiscussionIdExpression = @"^discussion-id:\s*(?<discussionid>.*)$";
private static readonly Regex FrontMatterRegexDiscussionId = new Regex(FrontMatterRegexDiscussionIdExpression, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);

private const string FrontMatterRegexIssueIdExpression = @"^issue-id:\s*(?<issueid>.*)$";
private static readonly Regex FrontMatterRegexIssueId = new Regex(FrontMatterRegexIssueIdExpression, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);

private const string MarkdownRegexSampleTagExpression = @"^>\s*\[!SAMPLE\s*(?<sampleid>.*)\s*\]\s*$";
private static readonly Regex MarkdownRegexSampleTag = new Regex(MarkdownRegexSampleTagExpression, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);

Expand Down Expand Up @@ -105,9 +111,13 @@ private ImmutableArray<ToolkitFrontMatter> GatherDocumentFrontMatter(SourceProdu
var category = ParseYamlField(ref ctx, file.Path, ref frontmatter, FrontMatterRegexCategory, "category");
var subcategory = ParseYamlField(ref ctx, file.Path, ref frontmatter, FrontMatterRegexSubcategory, "subcategory");

// TODO: Should these just be optional?
var discussion = ParseYamlField(ref ctx, file.Path, ref frontmatter, FrontMatterRegexDiscussionId, "discussionid")?.Trim();
var issue = ParseYamlField(ref ctx, file.Path, ref frontmatter, FrontMatterRegexIssueId, "issueid")?.Trim();

// Check we have all the fields we expect to continue (errors will have been spit out otherwise already from the ParseYamlField method)
if (title == null || description == null || keywords == null ||
category == null || subcategory == null)
category == null || subcategory == null || discussion == null || issue == null)
{
return null;
}
Expand Down Expand Up @@ -159,6 +169,28 @@ private ImmutableArray<ToolkitFrontMatter> GatherDocumentFrontMatter(SourceProdu
file.Path));
}

if (!int.TryParse(discussion, out var discussionId) || discussionId < 0)
{
ctx.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.MarkdownYAMLFrontMatterException,
Location.Create(file.Path, TextSpan.FromBounds(0, 1), new LinePositionSpan(LinePosition.Zero, LinePosition.Zero)),
file.Path,
"Can't parse discussion-id field, must be a positive integer or zero."));
return null;
}

if (!int.TryParse(issue, out var issueId) || issueId < 0)
{
ctx.ReportDiagnostic(
Diagnostic.Create(
DiagnosticDescriptors.MarkdownYAMLFrontMatterException,
Location.Create(file.Path, TextSpan.FromBounds(0, 1), new LinePositionSpan(LinePosition.Zero, LinePosition.Zero)),
file.Path,
"Can't parse issue-id field, must be a positive integer or zero."));
return null;
}

// Finally, construct the complete object.
return new ToolkitFrontMatter()
{
Expand All @@ -168,13 +200,15 @@ private ImmutableArray<ToolkitFrontMatter> GatherDocumentFrontMatter(SourceProdu
Category = categoryValue,
Subcategory = subcategoryValue,
FilePath = filepath,
SampleIdReferences = sampleids.ToArray()
SampleIdReferences = sampleids.ToArray(),
DiscussionId = discussionId,
IssueId = issueId,
};
}
}).OfType<ToolkitFrontMatter>().ToImmutableArray();
}

private string? ParseYamlField(ref SourceProductionContext ctx, string filepath, ref string content, Regex pattern, string fieldname)
private string? ParseYamlField(ref SourceProductionContext ctx, string filepath, ref string content, Regex pattern, string captureGroupName)
{
var match = pattern.Match(content);

Expand All @@ -185,11 +219,11 @@ private ImmutableArray<ToolkitFrontMatter> GatherDocumentFrontMatter(SourceProdu
DiagnosticDescriptors.MarkdownYAMLFrontMatterMissingField,
Location.Create(filepath, TextSpan.FromBounds(0, 1), new LinePositionSpan(LinePosition.Zero, LinePosition.Zero)),
filepath,
fieldname));
captureGroupName));
return null;
}

return match.Groups[fieldname].Value.Trim();
return match.Groups[captureGroupName].Value.Trim();
}

private void CreateDocumentRegistry(SourceProductionContext ctx, ImmutableArray<ToolkitFrontMatter> matter)
Expand Down Expand Up @@ -223,6 +257,6 @@ private static string FrontMatterToRegistryCall(ToolkitFrontMatter metadata)
var categoryParam = $"{nameof(ToolkitSampleCategory)}.{metadata.Category}";
var subcategoryParam = $"{nameof(ToolkitSampleSubcategory)}.{metadata.Subcategory}";

return @$"yield return new {typeof(ToolkitFrontMatter).FullName}() {{ Title = ""{metadata.Title}"", Author = ""{metadata.Author}"", Description = ""{metadata.Description}"", Keywords = ""{metadata.Keywords}"", Category = {categoryParam}, Subcategory = {subcategoryParam}, FilePath = @""{metadata.FilePath}"", SampleIdReferences = new string[] {{ ""{string.Join("\",\"", metadata.SampleIdReferences)}"" }} }};"; // TODO: Add list of sample ids in document
return @$"yield return new {typeof(ToolkitFrontMatter).FullName}() {{ Title = ""{metadata.Title}"", Author = ""{metadata.Author}"", Description = ""{metadata.Description}"", Keywords = ""{metadata.Keywords}"", Category = {categoryParam}, Subcategory = {subcategoryParam}, DiscussionId = {metadata.DiscussionId}, IssueId = {metadata.IssueId}, FilePath = @""{metadata.FilePath}"", SampleIdReferences = new string[] {{ ""{string.Join("\",\"", metadata.SampleIdReferences)}"" }} }};"; // TODO: Add list of sample ids in document
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,10 @@ public enum ToolkitSampleSubcategory : byte
/// A sample that focuses input controls.
/// </summary>
Input,

/// <summary>
/// A sample that focuses on media controls or behaviors.
/// </summary>
Media,
}

Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,7 @@
<Content Include="$(MSBuildThisFileDirectory)Assets\Wide310x150Logo.scale-400_altform-colorful_theme-light.png" />
<Content Include="$(MSBuildThisFileDirectory)LinkerConfig.xml" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(MSBuildThisFileDirectory)WasmCSS\Fonts.css" />
</ItemGroup>
</Project>
9 changes: 5 additions & 4 deletions common/CommunityToolkit.Labs.Shared/Helpers/IconHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public static class IconHelper
IconElement? iconElement = null;
switch (category)
{
case ToolkitSampleCategory.Controls: iconElement = new FontIcon() { Glyph = "\uE73A" }; break;
case ToolkitSampleCategory.Animations: iconElement = new FontIcon() { Glyph = "\uE945" }; break;
case ToolkitSampleCategory.Behaviors: iconElement = new FontIcon() { Glyph = "\uE2AC" }; break;
case ToolkitSampleCategory.Controls: iconElement = new FontIcon() { Glyph = "\ue73a" }; break;
case ToolkitSampleCategory.Animations: iconElement = new FontIcon() { Glyph = "\ue945" }; break;
case ToolkitSampleCategory.Behaviors: iconElement = new FontIcon() { Glyph = "\ue8b1" }; break;
}
return iconElement;
}
Expand All @@ -26,8 +26,9 @@ public static string GetSubcategoryIcon(ToolkitSampleSubcategory subcategory)
switch (subcategory)
{
case ToolkitSampleSubcategory.None: imagePath = "ms-appx:///Assets/ControlIcons/Control.png"; break;
case ToolkitSampleSubcategory.Layout: imagePath = "ms-appx:///Assets/ControlIcons/Layout.png"; break;
case ToolkitSampleSubcategory.Input: imagePath = "ms-appx:///Assets/ControlIcons/Input.png"; break;
case ToolkitSampleSubcategory.Layout: imagePath = "ms-appx:///Assets/ControlIcons/Layout.png"; break;
case ToolkitSampleSubcategory.Media: imagePath = "ms-appx:///Assets/ControlIcons/Control.png"; break;
case ToolkitSampleSubcategory.StatusAndInfo: imagePath = "ms-appx:///Assets/ControlIcons/Status.png"; break;
}
return imagePath;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
Fill="{ThemeResource TextFillColorPrimaryBrush}" />
<TextBlock Opacity="0.8"
Style="{StaticResource BodyTextBlockStyle}"
Text="A new way to contribute.." />
Text="A new way to build, contribute, experiment, and collaborate." />

<Button Margin="0,24,0,0">
<Button.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,96 @@
#endif
#endif

#if __WASM__
using Markdig;
michael-hawker marked this conversation as resolved.
Show resolved Hide resolved
using Uno.Foundation.Interop;
using Uno.UI.Runtime.WebAssembly;
#endif

namespace CommunityToolkit.Labs.Shared.Renderers;

/// <summary>
/// Provide an abstraction around the Toolkit MarkdownTextBlock for both UWP and WinUI 3 in the same namespace (until 8.0) as well as a polyfill for WebAssembly/WASM.
/// </summary>
#if __WASM__
[HtmlElement("div")]
public partial class MarkdownTextBlock : TextBlock
{
public MarkdownTextBlock()
{
Loaded += this.MarkdownTextBlock_Loaded;
}

protected override void OnTextChanged(string oldValue, string newValue)
{
if (IsLoaded)
{
UpdateText(newValue);
}
}

private void MarkdownTextBlock_Loaded(object sender, RoutedEventArgs e)
michael-hawker marked this conversation as resolved.
Show resolved Hide resolved
{
this.RegisterHtmlEventHandler("resize", HtmlElementResized);

UpdateText(Text);
}

#nullable enable
private void HtmlElementResized(object? sender, EventArgs e)
{
this.InvalidateMeasure();
}

private void UpdateText(string markdown)
{
// TODO: Check color hasn't changed since last time.
var color = (Foreground as SolidColorBrush)?.Color;
if (color != null)
{
this.SetCssStyle(("color", $"#{color!.ToString()!.Substring(3)}"), ("font-family", "Segoe UI"));
}
else
{
this.SetCssStyle("fontFamily", "Segoe UI");
}

this.SetCssClass("fluent-hyperlink-style");

this.SetHtmlContent(Markdown.ToHtml(markdown));

this.InvalidateMeasure();
}

protected override Size MeasureOverride(Size availableSize)
{
var size = this.MeasureHtmlView(availableSize, true);

return size;
}

//// Polyfill dummy for event callback
#pragma warning disable CS0067 // Unused on purpose for polyfill
public event EventHandler<LinkClickedEventArgs>? LinkClicked;
#pragma warning restore CS0067 // Unused on purpose for polyfill
}
#else
public partial class MarkdownTextBlock : ToolkitMTB
{
#if HAS_UNO
#if !HAS_UNO
public MarkdownTextBlock()
{
// Note: TODO: We can't use win:IsTextSelectionEnabled in XAML, for some reason getting a UWP compiler issue...? Maybe confused by inheritance?
IsTextSelectionEnabled = true;
}
#else
//// Polyfill dummy for event callback
#pragma warning disable CS0067 // Unused on purpose for polyfill
public event EventHandler<LinkClickedEventArgs>? LinkClicked;
#pragma warning restore CS0067 // Unused on purpose for polyfill
#endif
#endif
}
#endif

#if HAS_UNO
//// Polyfill dummy for event callback
Expand Down
Loading