Skip to content
This repository was archived by the owner on Dec 21, 2023. It is now read-only.

Commit 755eb0a

Browse files
Merge pull request #88 from InfinityGhost/markdown-wiki
Add support for markdown in the wiki
2 parents ba6aa49 + cbed852 commit 755eb0a

File tree

17 files changed

+298
-116
lines changed

17 files changed

+298
-116
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.IO;
2+
using System.Linq;
3+
using System.Reflection;
4+
using System.Threading.Tasks;
5+
using OpenTabletDriver.Web.Core.Services;
6+
7+
namespace OpenTabletDriver.Web.Core.Reflection
8+
{
9+
public class ReflectionMarkdownSource : IMarkdownSource
10+
{
11+
private readonly Assembly _assembly;
12+
private readonly string _assemblyName;
13+
private readonly string[] _resources;
14+
15+
public ReflectionMarkdownSource(Assembly assembly)
16+
{
17+
_assembly = assembly;
18+
_assemblyName = assembly.GetName().Name!;
19+
_resources = assembly.GetManifestResourceNames();
20+
}
21+
22+
public async Task<string> GetPage(string category, string page)
23+
{
24+
var fileName = $"{_assemblyName}.Views.Wiki.{category}.{page}.md";
25+
var markdownResource = _resources.FirstOrDefault(n => n.Contains(fileName));
26+
27+
if (markdownResource == null)
28+
return null;
29+
30+
using (var stream = _assembly.GetManifestResourceStream(markdownResource)!)
31+
using (var sr = new StreamReader(stream))
32+
{
33+
return await sr.ReadToEndAsync();
34+
}
35+
}
36+
}
37+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Threading.Tasks;
2+
3+
namespace OpenTabletDriver.Web.Core.Services
4+
{
5+
public interface IMarkdownSource
6+
{
7+
Task<string> GetPage(string category, string page);
8+
}
9+
}

OpenTabletDriver.Web/Controllers/TabletsController.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
using System.IO;
2-
using System.Net.Http;
3-
using System.Runtime.Versioning;
41
using System.Threading.Tasks;
52
using Microsoft.AspNetCore.Html;
63
using Microsoft.AspNetCore.Mvc;
74
using OpenTabletDriver.Web.Core.Services;
85
using OpenTabletDriver.Web.Models;
6+
using OpenTabletDriver.Web.Utilities;
97

108
namespace OpenTabletDriver.Web.Controllers
119
{
@@ -22,8 +20,8 @@ public TabletsController(ITabletService tabletService)
2220
public async Task<IActionResult> Index(string search = null)
2321
{
2422
var markdown = await tabletService.GetMarkdownRaw();
25-
string html = Markdown.ToHtml(markdown);
26-
string patchedHtml = html.Replace(
23+
var html = Markdown.ToHtml(markdown);
24+
var patchedHtml = html.Replace(
2725
"<table>",
2826
"<table id=\"tablets\" class=\"table table-hover\">"
2927
);
Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,46 @@
1+
using System;
2+
using System.Threading.Tasks;
13
using Microsoft.AspNetCore.Mvc;
4+
using Microsoft.Extensions.Caching.Memory;
5+
using OpenTabletDriver.Web.Core.Services;
6+
using OpenTabletDriver.Web.Models;
7+
8+
#nullable enable
29

310
namespace OpenTabletDriver.Web.Controllers
411
{
512
public class WikiController : Controller
613
{
14+
private readonly IMarkdownSource _markdownSource;
15+
private readonly IMemoryCache _memoryCache;
16+
17+
public WikiController(IMarkdownSource markdownSource, IMemoryCache memoryCache)
18+
{
19+
_markdownSource = markdownSource;
20+
_memoryCache = memoryCache;
21+
}
22+
723
[ResponseCache(Duration = 300)]
824
public IActionResult Index()
925
{
1026
return View();
1127
}
1228

1329
[ResponseCache(Duration = 300)]
14-
[Route("Wiki/{category}/{page}")]
15-
public IActionResult Wiki(string category, string page)
30+
[Route("/Wiki/{category}/{page}")]
31+
public async Task<IActionResult> Wiki(string category, string page)
32+
{
33+
var route = $"{category}/{page}";
34+
var model = await _memoryCache.GetOrCreateAsync(route, entry => GenerateMarkdownWikiPage(entry, category, page));
35+
36+
return model == null ? View(route) : View("WikiMarkdownPage", model);
37+
}
38+
39+
private async Task<MarkdownViewModel?> GenerateMarkdownWikiPage(ICacheEntry entry, string category, string page)
1640
{
17-
return View($"{category}/{page}");
41+
entry.AbsoluteExpiration = DateTimeOffset.MaxValue;
42+
var markdown = await _markdownSource.GetPage(category, page);
43+
return markdown != null ? new MarkdownViewModel(markdown) : null;
1844
}
1945
}
2046
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.Collections.Generic;
2+
using System.IO;
3+
using System.Linq;
4+
using System.Text.RegularExpressions;
5+
using Markdig;
6+
using Markdig.Extensions.Yaml;
7+
using Markdig.Renderers;
8+
using Markdig.Syntax;
9+
using Microsoft.AspNetCore.Html;
10+
using YamlDotNet.Serialization;
11+
12+
namespace OpenTabletDriver.Web.Models
13+
{
14+
public class MarkdownViewModel
15+
{
16+
public MarkdownViewModel(string markdown)
17+
{
18+
var pipeline = new MarkdownPipelineBuilder()
19+
.UseYamlFrontMatter()
20+
.UseAdvancedExtensions()
21+
.Build();
22+
23+
using (var sw = new StringWriter())
24+
{
25+
var renderer = new HtmlRenderer(sw);
26+
pipeline.Setup(renderer);
27+
28+
var document = Markdown.Parse(markdown, pipeline);
29+
30+
var yamlBlock = document.Descendants<YamlFrontMatterBlock>().FirstOrDefault();
31+
if (yamlBlock != null)
32+
{
33+
var startIndex = yamlBlock.Span.Start + 4;
34+
var length = yamlBlock.Span.Length - 8;
35+
var yaml = markdown.Substring(startIndex, length);
36+
37+
Metadata = new Deserializer().Deserialize<Dictionary<string, object>>(yaml);
38+
}
39+
40+
renderer.Render(document);
41+
Content = GetHtmlContent(sw);
42+
}
43+
}
44+
45+
public IDictionary<string, object> Metadata { get; }
46+
public IHtmlContent Content { get; }
47+
48+
private static readonly Regex HeaderRegex = new Regex("(?<=.?\n)<h3", RegexOptions.Compiled);
49+
50+
private IHtmlContent GetHtmlContent(StringWriter sw)
51+
{
52+
sw.Flush();
53+
var html = sw.ToString()!;
54+
55+
var formattedHtml = HeaderRegex.Replace(html, "<hr><h3")
56+
.Replace("<h3", "<h3 class=\"wiki-nav-item pb-2\"")
57+
.Replace("<table>", "<table class=\"table table-hover ms-3\">")
58+
.Replace("<p>", "<p class=\"ms-3\">");
59+
60+
return new HtmlString(formattedHtml);
61+
}
62+
}
63+
}
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22

33
<PropertyGroup>
4-
<TargetFramework>net6.0</TargetFramework>
54
<RootNamespace>OpenTabletDriver.Web</RootNamespace>
5+
<TargetFramework>net6.0</TargetFramework>
66
</PropertyGroup>
77

88
<ItemGroup Label="Project References">
@@ -11,7 +11,12 @@
1111

1212
<ItemGroup Label="NuGet Packages">
1313
<PackageReference Include="Markdig" Version="0.25.0" />
14+
<PackageReference Include="YamlDotNet" Version="11.2.1" />
1415
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="5.0.8" />
1516
</ItemGroup>
1617

18+
<ItemGroup Label="Documentation">
19+
<EmbeddedResource Include="Views\Wiki\*\*.md" />
20+
</ItemGroup>
21+
1722
</Project>

OpenTabletDriver.Web/Startup.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
using System;
22
using System.Net;
33
using System.Net.Http;
4-
using System.Net.Http.Headers;
4+
using System.Reflection;
55
using Microsoft.AspNetCore.Builder;
66
using Microsoft.AspNetCore.Hosting;
77
using Microsoft.AspNetCore.HttpOverrides;
88
using Microsoft.Extensions.Configuration;
99
using Microsoft.Extensions.DependencyInjection;
1010
using Microsoft.Extensions.Hosting;
1111
using Octokit;
12-
using OpenTabletDriver.Web.Controllers;
13-
using OpenTabletDriver.Web.Core;
14-
using OpenTabletDriver.Web.Core.Contracts;
1512
using OpenTabletDriver.Web.Core.Framework;
1613
using OpenTabletDriver.Web.Core.GitHub.Services;
1714
using OpenTabletDriver.Web.Core.Plugins;
15+
using OpenTabletDriver.Web.Core.Reflection;
1816
using OpenTabletDriver.Web.Core.Services;
19-
using ProductHeaderValue = Octokit.ProductHeaderValue;
2017

2118
namespace OpenTabletDriver.Web
2219
{
@@ -39,7 +36,8 @@ public void ConfigureServices(IServiceCollection services)
3936
.AddSingleton<IGitHubClient, GitHubClient>(AuthenticateGitHub)
4037
.AddSingleton<ITabletService, GitHubTabletService>()
4138
.AddSingleton<IPluginMetadataService, GitHubPluginMetadataService>()
42-
.AddSingleton<IFrameworkService, DotnetCoreService>();
39+
.AddSingleton<IFrameworkService, DotnetCoreService>()
40+
.AddSingleton<IMarkdownSource, ReflectionMarkdownSource>(GetMarkdownSource);
4341

4442
services.AddHttpClient<IRepositoryService, GitHubRepositoryService>(SetupHttpClient);
4543

@@ -90,16 +88,20 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
9088

9189
private GitHubClient AuthenticateGitHub(IServiceProvider serviceProvider)
9290
{
93-
const string productHeader = "OpenTabletDriver-Web";
94-
string apiKey = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
91+
var apiKey = Environment.GetEnvironmentVariable("GITHUB_TOKEN");
9592

96-
var clientHeader = ProductHeaderValue.Parse(productHeader);
93+
var clientHeader = ProductHeaderValue.Parse("OpenTabletDriver-Web");
9794
return new GitHubClient(clientHeader)
9895
{
9996
Credentials = string.IsNullOrWhiteSpace(apiKey) ? Credentials.Anonymous : new Credentials(apiKey)
10097
};
10198
}
10299

100+
private ReflectionMarkdownSource GetMarkdownSource(IServiceProvider serviceProvider)
101+
{
102+
return new ReflectionMarkdownSource(Assembly.GetEntryAssembly()!);
103+
}
104+
103105
private void SetupHttpClient(HttpClient client)
104106
{
105107
client.DefaultRequestHeaders.Add("User-Agent", "OpenTabletDriver-Web");

OpenTabletDriver.Web/TagHelpers/CodeBlockTagHelper.cs

Lines changed: 11 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,42 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Collections.Immutable;
4-
using System.IO.Pipes;
53
using System.Linq;
6-
using System.Text.RegularExpressions;
74
using System.Threading.Tasks;
85
using Microsoft.AspNetCore.Html;
6+
using Microsoft.AspNetCore.Mvc.Rendering;
97
using Microsoft.AspNetCore.Razor.TagHelpers;
10-
using Microsoft.CodeAnalysis;
118

129
namespace OpenTabletDriver.Web.TagHelpers
1310
{
1411
[HtmlTargetElement("codeblock")]
1512
public class CodeBlockTagHelper : TagHelper
1613
{
1714
public string Language { set; get; }
18-
15+
1916
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
2017
{
2118
output.TagName = "pre";
2219

23-
if (output.Attributes.TryGetAttribute("class", out var classAttr))
24-
output.Attributes.SetAttribute("class", $"{classAttr.Value} card card-body");
25-
else
26-
output.Attributes.Add("class", $"card card-body");
27-
28-
2920
var content = await output.GetChildContentAsync();
3021
var innerHtml = content.GetContent().Trim('\n');
31-
3222
var body = TrimPreceding(innerHtml, ' ');
33-
output.Content.SetHtmlContent(body);
23+
24+
var code = new TagBuilder("code");
25+
code.AddCssClass("hljs");
26+
code.AddCssClass($"language-{Language}");
27+
code.InnerHtml.SetHtmlContent(body);
28+
29+
output.Content.SetHtmlContent(code);
3430
}
3531

3632
private string TrimPreceding(string value, char character)
3733
{
3834
var lines = value.Split(Environment.NewLine);
39-
int preceding = CountPreceding(lines, character);
35+
var preceding = CountPreceding(lines, character);
4036
var trimmedLines = from line in lines
4137
select TrimPrecedingLine(line, character, preceding);
4238

43-
var formattedLines = LanguageFormat(trimmedLines.ToArray(), Language);
44-
45-
return string.Join(Environment.NewLine, formattedLines);
46-
}
47-
48-
private IEnumerable<string> LanguageFormat(IList<string> lines, string language)
49-
{
50-
switch (language)
51-
{
52-
case "sh":
53-
case "bash":
54-
case "nix":
55-
{
56-
for (int i = 0; i < lines.Count; i++)
57-
{
58-
var line = lines[i];
59-
if (line.TrimStart().StartsWith("#"))
60-
{
61-
var nextLine = lines[i + 1];
62-
const string tag = "span";
63-
yield return $"<{tag} class=\"text-muted\">{line}{Environment.NewLine}</{tag}>{nextLine}";
64-
i++;
65-
}
66-
else
67-
{
68-
yield return line;
69-
}
70-
}
71-
72-
break;
73-
}
74-
case "ini":
75-
{
76-
for (int i = 0; i < lines.Count; i++)
77-
{
78-
var line = lines[i];
79-
if (Regex.IsMatch(line, @"^\[.+?\]$"))
80-
{
81-
var nextLine = lines[i + 1];
82-
const string tag = "span";
83-
yield return $"<{tag} class=\"text-info\">{line}{Environment.NewLine}</{tag}>{nextLine}";
84-
i++;
85-
}
86-
else
87-
{
88-
yield return line;
89-
}
90-
}
91-
92-
break;
93-
}
94-
default:
95-
{
96-
foreach (var line in lines)
97-
yield return line;
98-
break;
99-
}
100-
}
39+
return string.Join(Environment.NewLine, trimmedLines);
10140
}
10241

10342
private int CountPreceding(IEnumerable<string> lines, char leadingCharacter)

OpenTabletDriver.Web/Markdown.cs renamed to OpenTabletDriver.Web/Utilities/Markdown.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
using System.Reflection.Metadata;
21
using Markdig;
32
using Microsoft.AspNetCore.Html;
4-
using Microsoft.AspNetCore.Mvc.Rendering;
53

6-
namespace OpenTabletDriver.Web
4+
namespace OpenTabletDriver.Web.Utilities
75
{
86
public class Markdown
97
{

0 commit comments

Comments
 (0)