Skip to content

Commit

Permalink
Generate blogs from markdown files (#3922)
Browse files Browse the repository at this point in the history
* WIP: convert markdown to razor

* Rename ExampleMarkup

* Code blocks

* Add more inlines

* Add more page info

* Use code block info for naming of example files

* Delete test markdown

* Persist code block handling and fix inlines

* Convert BeginnersGuideToCreateBlazoriseApp blog

* Improvements

* Make Paths as static class

* Remov unused method

* Use all compilers

* Generate Example files along with Code files

* Contribution guide

* Skip generating Code from markdown examples

* Run all builders

* Optimize image note
  • Loading branch information
stsrki authored Jul 1, 2022
1 parent 21ba906 commit fea4864
Show file tree
Hide file tree
Showing 21 changed files with 1,174 additions and 114 deletions.
35 changes: 31 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,34 @@ To get the development and test environment set up on your local machine, you ne

You are now ready to build and test Blazorise:

## Running documentation
## How to Write a Community Blog?

To write a blog post, you must create a new subfolder under the `Pages/Blog` in the `Blazorise.Docs` project. The subfolder must contain a blog date and name in the format `YYYY-MM-DD_BlogName`, e.g., `2022-06-08_BeginnersGuideToCreateBlazoriseApp`. Note that the blog name must be unique.

Next create the `Index.md` file under the same folder. The following metadata should always be present at the beginning of the file. Once pasted, adjust them for your blog.

```
---
title: How to create a Blazorise WASM application: A Beginner's Guide
description: Learn How to create a Blazorise WASM application: A Beginner's Guide.
permalink: /blog/how-to-create-a-blazorise-application-beginners-guide
canonical: /blog/how-to-create-a-blazorise-application-beginners-guide
image-url: img/blog/2022-06-08/How_to_create_a_Blazorise_application_A_Beginners_Guide.png
image-title: Blazorise WASM application: A Beginner's Guide
author-name: Mladen Macanović
author-image: mladen
posted-on: June 8th, 2022
read-time: 5 min
---
```

Most of the settings should be self-explanatory. The only one that might need some extra work is the `author-image`. The image should be placed in the `wwwroot/img/avatars` under the `Blazorise.Docs.Server` project and in the `*.png` file format. An image should be at least **512x512 px** in size and should be [optimized for minimum size](https://imagecompressor.com/).

After you have written or added a blog content, you can try running the documentation.

> :info: Before you start writing, please look at the **Branch Organization** to learn how we organize our work and publish the new versions.
## Running the Documentation

1. Start a command prompt and navigate to the `\Documentation\Blazorise.Docs.Server` folder under Blazorise root.
2. Run command: `dotnet watch run`.
Expand All @@ -21,14 +48,14 @@ You are now ready to build and test Blazorise:

1. Start a command prompt and run: selenium-standalone start. Note: this service needs to be running before you start Visual Studio, or test runs may fail.
2. Open the Blazorise solution at the root folder (Blazorise.sln).
3. Select Build > Build Solution on main menu. All of the projects should build sucessfully.
3. Select Build > Build Solution on main menu. All of the projects should build successfully.
4. Run all of the unit tests in the solution use Test > Run All Tests on main menu. They should all pass at this point.

## Branch Organization

We use `master` branch for all development and for all new features and bug fixes that are going into the ongoing milestone.

Once we finish with the work for the current milestone we will create a new **support** branch named `rel-X.Y` where all the bug fixes will go.
Once we finish the work for the current milestone, we will create a new **support** branch named `rel-X.Y`, where all the bug fixes will go. Also, we regularly publish our web from the latest `rel-X.Y` branch, so if you plan to write a blog, this is the recommended way and place to create it.

Branch naming must follow this guidelines:

Expand All @@ -43,4 +70,4 @@ When submitting a pull request:
2. Create a branch from `master` or `rel-X.Y` and give it a meaningful name (e.g. `rel-{version-num}-my-awesome-new-feature`) and describe the feature or fix.
4. Open a pull request on GitHub.

> :warning: **Don't make any changes on master branch**: You must always create a feature branch by following our guidelines or we will close the PR until the changes are properly organized.
> :warning: **Don't make any changes on master branch**: You must always create a feature branch by following our guidelines or we will close the PR until the changes are properly organized.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -7,6 +7,7 @@

<ItemGroup>
<PackageReference Include="ColorCode.HTML" Version="2.0.9" />
<PackageReference Include="Markdig" Version="0.30.2" />
</ItemGroup>

</Project>
272 changes: 272 additions & 0 deletions Documentation/Blazorise.Docs.Compiler/BlogBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using ColorCode;
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;

namespace Blazorise.Docs.Compiler
{
public class BlogBuilder
{
private readonly string blogName;
private readonly string blogDirectory;
private readonly StringBuilder sb;
private const int IndentSize = 4;
private string NewLine = "\r\n";
private int codeIndex = 0;

public BlogBuilder( string blogName, string blogDirectory )
{
this.blogName = blogName;
this.blogDirectory = blogDirectory;

sb = new StringBuilder();
}

public void AddPageAndSeo( string url, string title, string description, string imageUrl, string imageTitle )
{
sb.Append( $"@page \"{url}\"" ).Append( NewLine ).Append( NewLine );

sb.Append( $"<Seo Canonical=\"{url}\" Title=\"{title}\" Description=\"{description}\" />" ).Append( NewLine ).Append( NewLine );

sb.Append( $"<BlogPageImage Source=\"{imageUrl}\" Text=\"{imageTitle}\" />" ).Append( NewLine ).Append( NewLine );
}

public void AddPagePostInto( string authorName, string authorImage, string postedOn, string readTime )
{
sb.Append( $"<BlogPagePostInto UserName=\"{authorName}\" ImageName=\"{authorImage}\" PostedOn=\"{postedOn}\" Read=\"{readTime}\" />" ).Append( NewLine );
}

private void AddInlines( ContainerInline containerInline )
{
foreach ( var inline in containerInline )
{
if ( inline is EmphasisInline emphasisInline )
{
if ( emphasisInline.DelimiterCount == 2 )
sb.Append( "<Strong>" ).Append( string.Join( "", emphasisInline ) ).Append( "</Strong>" );
else if ( emphasisInline.DelimiterCount == 1 )
sb.Append( "<Text Italic>" ).Append( string.Join( "", emphasisInline ) ).Append( "</Text>" );
}
else if ( inline is LinkInline linkInline )
{
var title = string.IsNullOrEmpty( linkInline.Title ) ? linkInline.FirstChild?.ToString() : linkInline.Title;

if ( linkInline.IsImage )
sb.Append( $"<BlogPageImageModal ImageSource=\"{linkInline.Url}\" ImageTitle=\"{title}\" />" );
//sb.Append( $"<Image Source=\"{linkInline.Url}\" Text=\"{title}\">" ).Append( linkInline.FirstChild?.ToString() ).Append( "</Image>" );
else
sb.Append( $"<Anchor To=\"{linkInline.Url}\" Title=\"Link to {title}\">" ).Append( linkInline.FirstChild?.ToString() ).Append( "</Anchor>" );
}
else if ( inline is CodeInline codeInline )
{
var content = codeInline.Content;

if ( content.StartsWith( '<' ) && content.EndsWith( '>' ) )
{
content = content.Trim( '<', '>' );

sb.Append( $"<Code Tag>" ).Append( content ).Append( "</Code>" );
}
else
sb.Append( $"<Code>" ).Append( content ).Append( "</Code>" );
}
else
sb.Append( inline.ToString() );
}

sb.Append( NewLine );
}

public void AddPageTitle( HeadingBlock headingBlock )
{
sb.Append( "<BlogPageTitle>" ).Append( NewLine );

sb.Append( "".PadLeft( IndentSize, ' ' ) );

if ( headingBlock.Inline != null )
AddInlines( headingBlock.Inline );

sb.Append( "</BlogPageTitle>" ).Append( NewLine ).Append( NewLine );
}

public void AddPageSubtitle( HeadingBlock headingBlock )
{
sb.Append( "<BlogPageSubtitle>" ).Append( NewLine );

sb.Append( "".PadLeft( IndentSize, ' ' ) );

if ( headingBlock.Inline != null )
AddInlines( headingBlock.Inline );

sb.Append( "</BlogPageSubtitle>" ).Append( NewLine ).Append( NewLine );
}

public void AddPageParagraph( ParagraphBlock paragraphBlock )
{
if ( paragraphBlock.Inline == null )
return;

if ( paragraphBlock.Inline.FirstChild is LinkInline linkInline && linkInline.IsImage )
{
var title = string.IsNullOrEmpty( linkInline.Title ) ? linkInline.FirstChild?.ToString() : linkInline.Title;

sb.Append( $"<BlogPageImageModal ImageSource=\"{linkInline.Url}\" ImageTitle=\"{title}\" />" ).Append( NewLine ).Append( NewLine );
}
else
{
sb.Append( "<BlogPageParagraph>" ).Append( NewLine );

sb.Append( "".PadLeft( IndentSize, ' ' ) );

if ( paragraphBlock.Inline != null )
AddInlines( paragraphBlock.Inline );

sb.Append( "</BlogPageParagraph>" ).Append( NewLine ).Append( NewLine );
}
}

public void AddPageQuote( QuoteBlock quoteBlock )
{
foreach ( var block in quoteBlock )
{
if ( block is ParagraphBlock paragraphBlock )
{
sb.Append( "<BlogPageParagraph>" ).Append( NewLine );

sb.Append( "".PadLeft( IndentSize, ' ' ) );

sb.Append( "<Blockquote>" ).Append( NewLine );

sb.Append( "".PadLeft( IndentSize * 2, ' ' ) );

if ( paragraphBlock.Inline != null )
AddInlines( paragraphBlock.Inline );

sb.Append( "".PadLeft( IndentSize, ' ' ) );

sb.Append( "</Blockquote>" ).Append( NewLine );

sb.Append( "</BlogPageParagraph>" ).Append( NewLine ).Append( NewLine );
}
}
}

public void AddPageList( ListBlock listBlock )
{
sb.Append( $"<BlogPageList" );

if ( listBlock.IsOrdered )
sb.Append( " Ordered" );

sb.Append( ">" ).Append( NewLine );

foreach ( ListItemBlock listItem in listBlock )
{
sb.Append( "".PadLeft( IndentSize, ' ' ) );
sb.Append( "<BlogPageListItem>" ).Append( NewLine );

sb.Append( "".PadLeft( IndentSize * 2, ' ' ) );

foreach ( var block in listItem )
{
if ( block is ParagraphBlock paragraphBlock )
{
AddInlines( paragraphBlock.Inline );
}
else if ( block is FencedCodeBlock fencedCodeBlock )
{
PersistCodeBlock( fencedCodeBlock, 2 );
}
}

sb.Append( "".PadLeft( IndentSize, ' ' ) );
sb.Append( "</BlogPageListItem>" ).Append( NewLine );
}

sb.Append( "</BlogPageList>" ).Append( NewLine ).Append( NewLine );
}

public (string builtCodeBlock, string parsedCodeBlock) AddCodeBlock( FencedCodeBlock fencedCodeBlock, string codeBlockName, int indentLevel )
{
var formatter = new HtmlClassFormatter();

sb.Append( "".PadLeft( IndentSize * indentLevel, ' ' ) );

sb.Append( $"<BlogPageSourceBlock Code=\"{codeBlockName}" );

var parsedCodeBlock = ParseCodeBlock( fencedCodeBlock );

var builtCodeBlock = new MarkupBuilder( formatter )
.Build( parsedCodeBlock, fencedCodeBlock.Info != null && ( fencedCodeBlock.Info.StartsWith( "csharp" ) || fencedCodeBlock.Info.StartsWith( "cs" ) ) );

sb.Append( "\"" );

sb.Append( " />" ).Append( NewLine );

if ( indentLevel == 0 )
sb.Append( NewLine );

return (builtCodeBlock, parsedCodeBlock);
}

public void PersistCodeBlock( FencedCodeBlock fencedCodeBlock, int indentLevel )
{
var codeBlockName = fencedCodeBlock.Info != null && fencedCodeBlock.Info.IndexOf( '|' ) > 0
? $"{blogName}_{fencedCodeBlock.Info.Substring( fencedCodeBlock.Info.IndexOf( '|' ) + 1 )}"
: $"{blogName}{( ++codeIndex )}";

var codeBlockFileName = Path.Combine( blogDirectory, "Code", $"{codeBlockName}Code.html" );
var codeBlockExampleFileName = Path.Combine( blogDirectory, "Examples", $"{codeBlockName}.snippet" );
var codeBlockDirectory = Path.GetDirectoryName( codeBlockFileName );
var codeBlockExamplesDirectory = Path.GetDirectoryName( codeBlockExampleFileName );
var currentCodeBlock = string.Empty;
var currentCodeBlockExample = string.Empty;

if ( !Directory.Exists( codeBlockDirectory ) )
{
Directory.CreateDirectory( codeBlockDirectory );
}

if ( !Directory.Exists( codeBlockExamplesDirectory ) )
{
Directory.CreateDirectory( codeBlockExamplesDirectory );
}

var (builtCodeBlock, parsedCodeBlock) = AddCodeBlock( fencedCodeBlock, codeBlockName, indentLevel );

if ( File.Exists( codeBlockFileName ) )
{
currentCodeBlock = File.ReadAllText( codeBlockFileName );
}

if ( File.Exists( codeBlockExampleFileName ) )
{
currentCodeBlockExample = File.ReadAllText( codeBlockExampleFileName );
}

if ( currentCodeBlock != builtCodeBlock )
{
File.WriteAllText( codeBlockFileName, builtCodeBlock );
}

if ( currentCodeBlockExample != parsedCodeBlock )
{
File.WriteAllText( codeBlockExampleFileName, parsedCodeBlock );
}
}

static string ParseCodeBlock( FencedCodeBlock fencedCodeBlock )
{
return string.Join( "\r\n", fencedCodeBlock.Lines );
}

public override string ToString()
{
return sb.ToString();
}
}
}
Loading

0 comments on commit fea4864

Please sign in to comment.