# Markdownin käyttö Blogging-asioissa

<!--category-- ASP.NET, Markdown -->
<datetime class="hidden">2024-08-02T17:00</datetime>

## Esitys

Markdown on helppokäyttöinen merkintäkieli, jota voi käyttää muotoilevien elementtien lisäämiseen yksinkertaiseen tekstiin.

Tällä sivustolla käytän erittäin yksinkertaista lähestymistapaa bloggingiin, yritin ja epäonnistuin pitämään yllä blogia menneisyydessä halusin tehdä siitä mahdollisimman helppoa kirjoittaa ja julkaista postit. käytän merkitsemistä kirjoittaakseni postit, ja tällä sivustolla on yksi ainoa palvelu käyttäen [Markdig](https://github.com/xoofx/markdig) converting markdown to HTML.

[TOC]

## Miksei Static Site generator?

Sanalla yksinkertaisuus. Tämä ei ole' ei tule olemaan superläheinen liikennepaikka, , käytän ASP-järjestelmää, M SK3 NET OutPutCache-järjestelmää sivujen varaamiseen ja IMSC4 en aio päivittää sitä niin usein, että se on ajantasaista. . Halusin pitää sivuston mahdollisimman yksinkertaisena eikä minun tarvitse huolestua statisen sivuston generaattorin ylimääräisistä kustannuksista sekä rakennusprosessissa että sivuston monimutkaisuudessa.

Selkeyttämään statisia site-generaattoreita, kuten [Hugo](https://gohugo.io/) / [Jekyll](https://jekyllrb.com/) etc... voi olla hyvä ratkaisu monille sivustoille, mutta tätä varten halusin pitää sen yksinkertaisena *minulle* ; Minulla on näkemyksiäM SK6 palveluja, valvontakoneita ja lukuisia käsitteitä HTML:stä.

## Miksi ei perustaa tietokantaa?

1. Tekninen yksinkertaisuus; Tietokannat ovat tehokkaita tietojen säilyttämiseen tarkoitettuja järjestelmiä. *asianmukaisesti* käyttäkää tietokantoja erityisesti ASP:n, .:n ja NET:n sovelluksissa, johon lisätään LOT-koodia, [EF Core](https://learn.microsoft.com/en-us/ef/core/), [Dapper](https://github.com/DapperLib/Dapper) tai raaka-SQL ADO.NET. Halusin pitää sivuston mahdollisimman yksinkertaisena *Ensinnäkin*.
2. Uudelleenlaadinnan ja käyttöönoton helppous. Tämän siten tarkoituksena on osoittaa, miten yksinkertainen Docker & Docker Compose voi olla paikankäytön kannaltaM SK2 Voin päivittää paikankäyntiä tarkistamalla uutta koodia ( sisältö mukaan lukienMST4 GitHubiinMSV5 toiminta toimiiMSC6 rakentaa kuva sen jälkeen Watchtower -menetelmä omassa dokkerissani kokoonpano-asiakirjassa päivittäy paikan kuva automaattisestiMSL7 Tämä on hyvin yksinkertaisen tapa päivittää sivustoa ja halusin säilyttää sen samalla tavallaMSSK8
3. päällekkäisyydet running; koska minulla on ZERO-tietoja, jotka eivät ole 't dockerin kuvauksessa, se tarkoittaa, että voin hyvin hyvin kääntää täsmälliset päällekkäiset paikallisesti (ni pienessä Ubuntu- klusterissa täällä kotonaM SK3 Tämä on erinomainen tapa testata muutoksia dockeria käyttäen. [kun olen tehnyt ImageSharp-muutoksia](/blog/imagesharpwithdocker) ) ennen niiden lähettämistä verkkoon
4. Halusin nähdä, miten pitkälle voisin päästä yksinkertaisella site-designilla, ja tähän mennessä olen hyvin tyytyväinen siihen.

## Miten kirjoitatte postsinne?

Siirrän yksinkertaisesti uuden .md-tiedoston merkitsemisjärjestelmiin, ja sivusto ottaa sen vastaan ja esittää sen. ♫ ( ♫ kun muistan, että se on otettava mukaan sisältöön.

Sitten kun tarkistan sivustoa GitHubiin, toiminta käy ja sivusto on ajantasaistettu. Simple!

```mermaid
flowchart LR
    A[Write New Markdown File] -->|Checkin To Github| B(Github Action Triggers) -->  C(Builds Docker Image) --> D(Watchtower Pulls new Image) --> E(Site Updated)
   
  
```

![setascontent.png](setascontent.png)

## Miten lisätään kuvia?

Koska olen juuri lisännyt kuvan tähän, sanon teille, miten tein sen.

```markdown
![setascontent.png](setascontent.png)
```

Sitten liitän Markdig-putkeni laajennus, joka kirjoittaa ne uudelleen oikeaan URL:hen. (kaikki yksinkertaisuudestaM SK1 [Ks. liitteen lähdekoodia.](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/MarkDigExtensions/ImgExtension.cs)

```csharp
using Markdig;
using Markdig.Renderers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;

namespace Mostlylucid.MarkDigExtensions;

public class ImgExtension : IMarkdownExtension
{
    public void Setup(MarkdownPipelineBuilder pipeline)
    {
        pipeline.DocumentProcessed += ChangeImgPath;
    }

    public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer)
    {
    }

    public void ChangeImgPath(MarkdownDocument document)
    {
        foreach (var link in document.Descendants<LinkInline>())
            if (link.IsImage)
                link.Url = "/articleimages/" + link.Url;
    }
}
```

## BlogService.

BlogService on yksinkertainen palvelu, joka lukee merkdown-tiedostoja Markdown-verkosta ja muuntaa ne HTML:iin Markdigin avulla.

Täsmällinen lähde tästä on jäljempänä ja [täällä](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/Services/BlogService.cs).

<details>
<summary>Click to see the source code for the BlogService</summary>

```csharp

using System.Globalization;
using System.Text.RegularExpressions;
using Markdig;
using Microsoft.Extensions.Caching.Memory;
using Mostlylucid.MarkDigExtensions;
using Mostlylucid.Models.Blog;

namespace Mostlylucid.Services;

public class BlogService
{
private const string Path = "Markdown";
private const string CacheKey = "Categories";

    private static readonly Regex DateRegex = new(
        @"<datetime class=""hidden"">(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})</datetime>",
        RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking);

    private static readonly Regex WordCoountRegex = new(@"\b\w+\b",
        RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking);

    private static readonly Regex CategoryRegex = new(@"<!--\s*category\s*--\s*([^,]+?)\s*(?:,\s*([^,]+?)\s*)?-->",
        RegexOptions.Compiled | RegexOptions.Singleline);

    private readonly ILogger<BlogService> _logger;

    private readonly IMemoryCache _memoryCache;

    private readonly MarkdownPipeline pipeline;

    public BlogService(IMemoryCache memoryCache, ILogger<BlogService> logger)
    {
        _logger = logger;
        _memoryCache = memoryCache;
        pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Use<ImgExtension>().Build();
        ListCategories();
    }


    private Dictionary<string, List<string>> GetFromCache()
    {
        return _memoryCache.Get<Dictionary<string, List<string>>>(CacheKey) ?? new Dictionary<string, List<string>>();
    }

    private void SetCache(Dictionary<string, List<string>> categories)
    {
        _memoryCache.Set(CacheKey, categories, new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(12)
        });
    }

    private void ListCategories()
    {
        var cacheCats = GetFromCache();
        var pages = Directory.GetFiles("Markdown", "*.md");
        var count = 0;

        foreach (var page in pages)
        {
            var pageAlreadyAdded = cacheCats.Values.Any(x => x.Contains(page));

            if (pageAlreadyAdded) continue;


            var text = File.ReadAllText(page);
            var categories = GetCategories(text);
            if (!categories.Any()) continue;
            count++;
            foreach (var category in categories)
                if (cacheCats.TryGetValue(category, out var pagesList))
                {
                    pagesList.Add(page);
                    cacheCats[category] = pagesList;
                    _logger.LogInformation("Added category {Category} for {Page}", category, page);
                }
                else
                {
                    cacheCats.Add(category, new List<string> { page });
                    _logger.LogInformation("Created category {Category} for {Page}", category, page);
                }
        }

        if (count > 0) SetCache(cacheCats);
    }

    public List<string> GetCategories()
    {
        var cacheCats = GetFromCache();
        return cacheCats.Keys.ToList();
    }


    public List<PostListModel> GetPostsByCategory(string category)
    {
        var pages = GetFromCache()[category];
        return GetPosts(pages.ToArray());
    }

    public BlogPostViewModel? GetPost(string postName)
    {
        try
        {
            var path = System.IO.Path.Combine(Path, postName + ".md");
            var page = GetPage(path, true);
            return new BlogPostViewModel
            {
                Categories = page.categories, WordCount = WordCount(page.restOfTheLines), Content = page.processed,
                PublishedDate = page.publishDate, Slug = page.slug, Title = page.title
            };
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Error getting post {PostName}", postName);
            return null;
        }
    }

    private int WordCount(string text)
    {
        return WordCoountRegex.Matches(text).Count;
    }


    private string GetSlug(string fileName)
    {
        var slug = System.IO.Path.GetFileNameWithoutExtension(fileName);
        return slug.ToLowerInvariant();
    }

    private static string[] GetCategories(string markdownText)
    {
        var matches = CategoryRegex.Matches(markdownText);
        var categories = matches
            .SelectMany(match => match.Groups.Cast<Group>()
                .Skip(1) // Skip the entire match group
                .Where(group => group.Success) // Ensure the group matched
                .Select(group => group.Value.Trim()))
            .ToArray();
        return categories;
    }

    public (string title, string slug, DateTime publishDate, string processed, string[] categories, string
        restOfTheLines) GetPage(string page, bool html)
    {
        var fileInfo = new FileInfo(page);

        // Ensure the file exists
        if (!fileInfo.Exists) throw new FileNotFoundException("The specified file does not exist.", page);

        // Read all lines from the file
        var lines = File.ReadAllLines(page);

        // Get the title from the first line
        var title = lines.Length > 0 ? Markdown.ToPlainText(lines[0].Trim()) : string.Empty;

        // Concatenate the rest of the lines with newline characters
        var restOfTheLines = string.Join(Environment.NewLine, lines.Skip(1));

        // Extract categories from the text
        var categories = GetCategories(restOfTheLines);

        var publishedDate = fileInfo.CreationTime;
        var publishDate = DateRegex.Match(restOfTheLines).Groups[1].Value;
        if (!string.IsNullOrWhiteSpace(publishDate))
            publishedDate = DateTime.ParseExact(publishDate, "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture);

        // Remove category tags from the text
        restOfTheLines = CategoryRegex.Replace(restOfTheLines, "");
        restOfTheLines = DateRegex.Replace(restOfTheLines, "");
        // Process the rest of the lines as either HTML or plain text
        var processed =
            html ? Markdown.ToHtml(restOfTheLines, pipeline) : Markdown.ToPlainText(restOfTheLines, pipeline);

        // Generate the slug from the page filename
        var slug = GetSlug(page);


        // Return the parsed and processed content
        return (title, slug, publishedDate, processed, categories, restOfTheLines);
    }

    public List<PostListModel> GetPosts(string[] pages)
    {
        List<PostListModel> pageModels = new();

        foreach (var page in pages)
        {
            var pageInfo = GetPage(page, false);

            var summary = Markdown.ToPlainText(pageInfo.restOfTheLines).Substring(0, 100) + "...";
            pageModels.Add(new PostListModel
            {
                Categories = pageInfo.categories, Title = pageInfo.title,
                Slug = pageInfo.slug, WordCount = WordCount(pageInfo.restOfTheLines),
                PublishedDate = pageInfo.publishDate, Summary = summary
            });
        }

        pageModels = pageModels.OrderByDescending(x => x.PublishedDate).ToList();
        return pageModels;
    }


    public List<PostListModel> GetPostsForFiles()
    {
        var pages = Directory.GetFiles("Markdown", "*.md");
        return GetPosts(pages);
    }
}
```

</details>
Kuten näette, tässä on joitakin elementtejä:

### Tietojen käsittely

Markdown-asiakirjojen käsittely HTML:iin on varsin yksinkertaista, Markdigin kirjastolla konvertoidaan markdown-asioita HTML:een, ja sen jälkeen käytän muutamia säännöllisiä ilmaisuja, jotta saamme esille luokkat ja julkaisun päivämäärän markdown -asiakirjasta.

GetPage-menetelmää käytetään merkitsemättömän tiedoston sisällön poistamiseen, sillä on muutamat vaiheet:

1. Ottakaa otsikko
   Valmistelun mukaan käytän ensimmäistä riviä merkitsevässä asiakirjassa postin otsikona. joten voin vain tehdä

```csharp
        var lines = File.ReadAllLines(page);

        // Get the title from the first line
        var title = lines.Length > 0 ? Markdown.ToPlainText(lines[0].Trim()) : string.Empty;
```

Koska otsikko on prefixoitu "#", käytän Markdown-menetelmää .ToPlainText -menetelmää poistaakseni "#" otsikosta

2. Luettelo otsakkeista
   Jokaisella postilla voi olla jopa kaksi luokkaa tämä menetelmä ekstrahoi nämä sitten poistan merkitsemisaineistosta tagin.

```csharp
// Concatenate the rest of the lines with newline characters
        var restOfTheLines = string.Join(Environment.NewLine, lines.Skip(1));

        // Extract categories from the text
        var categories = GetCategories(restOfTheLines);

   // Remove category tags from the text
        restOfTheLines = CategoryRegex.Replace(restOfTheLines, "");

```

GetCategories -menetelmässä käytetään säännöllistä ilmaisua, jotta kategoriat voidaan extrahoida merkitsemiskohdasta.

```csharp
    private static readonly Regex CategoryRegex = new(@"<!--\s*category\s*--\s*([^,]+?)\s*(?:,\s*([^,]+?)\s*)?-->",
        RegexOptions.Compiled | RegexOptions.Singleline);

    private static string[] GetCategories(string markdownText)
    {
        var matches = CategoryRegex.Matches(markdownText);
        var categories = matches
            .SelectMany(match => match.Groups.Cast<Group>()
                .Skip(1) // Skip the entire match group
                .Where(group => group.Success) // Ensure the group matched
                .Select(group => group.Value.Trim()))
            .ToArray();
        return categories;
        
        
    }
```

3. Poistetaan julkaistu päivämäärä
   Otan sen jälkeen päivämäärän pois postista. (Otin käyttänyt luotua päivämäärää, mutta miten otan tämän käyttöön käyttäen kokonaista docker-kuvaa, se tarkoittaa, että tämä ei ole enää kovin hyödyllistä.
   Se analysoi merkkiä siinä muodossa, joka on kussakin .md-asiakirjassa

```razor
 <datetime class="hidden">2024-08-02T17:00</datetime>
```

```csharp
     private static readonly Regex DateRegex = new(
        @"<datetime class=""hidden"">(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})</datetime>",
        RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.NonBacktracking);
     
           var publishedDate = fileInfo.CreationTime;
        var publishDate = DateRegex.Match(restOfTheLines).Groups[1].Value;
        if (!string.IsNullOrWhiteSpace(publishDate))
            publishedDate = DateTime.ParseExact(publishDate, "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture);

     
        restOfTheLines = DateRegex.Replace(restOfTheLines, "");
```

4. Otsake sisältöä
   Itse asiassa sisällön saaminen on varsin yksinkertaista se käyttää pipelineä (kuten edellä mainitun kuvamerkin korvaamiseksi ) ja antaa minulle valinnaisesti yksinkertaisen tekstin postien luetteloon tai HTML:iin varsinaisesta postista

```csharp
    pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Use<ImgExtension>().Build();
    
   var processed =
            html ? Markdown.ToHtml(restOfTheLines, pipeline) : Markdown.ToPlainText(restOfTheLines, pipeline);
```

5. Ottakaa 'slug'
   Tämä on yksinkertaisesti filename ilman laajennusta:
   
   ```csharp
       private string GetSlug(string fileName)
       {
           var slug = System.IO.Path.GetFileNameWithoutExtension(fileName);
           return slug.ToLowerInvariant();
       }
   ```

6. Palauttaa sisältö
   Nyt meillä on sivusisältö, jonka voimme kuvailla blogille!

<details>
<summary> The GetPage Method</summary>

```csharp
public (string title, string slug, DateTime publishDate, string processed, string[] categories, string
        restOfTheLines) GetPage(string page, bool html)
    {
        var fileInfo = new FileInfo(page);

        // Ensure the file exists
        if (!fileInfo.Exists) throw new FileNotFoundException("The specified file does not exist.", page);

        // Read all lines from the file
        var lines = File.ReadAllLines(page);

        // Get the title from the first line
        var title = lines.Length > 0 ? Markdown.ToPlainText(lines[0].Trim()) : string.Empty;

        // Concatenate the rest of the lines with newline characters
        var restOfTheLines = string.Join(Environment.NewLine, lines.Skip(1));

        // Extract categories from the text
        var categories = GetCategories(restOfTheLines);

        var publishedDate = fileInfo.CreationTime;
        var publishDate = DateRegex.Match(restOfTheLines).Groups[1].Value;
        if (!string.IsNullOrWhiteSpace(publishDate))
            publishedDate = DateTime.ParseExact(publishDate, "yyyy-MM-ddTHH:mm", CultureInfo.InvariantCulture);

        // Remove category tags from the text
        restOfTheLines = CategoryRegex.Replace(restOfTheLines, "");
        restOfTheLines = DateRegex.Replace(restOfTheLines, "");
        // Process the rest of the lines as either HTML or plain text
        var processed =
            html ? Markdown.ToHtml(restOfTheLines, pipeline) : Markdown.ToPlainText(restOfTheLines, pipeline);

        // Generate the slug from the page filename
        var slug = GetSlug(page);


        // Return the parsed and processed content
        return (title, slug, publishedDate, processed, categories, restOfTheLines);
    }
```

</details>
jäljempänä oleva koodi osoittaa, miten luon blogien luettelon, se käyttää `GetPage(page, false)` menetelmä, jolla otetaan pois otsikko, kategoriat, julkaisun päivämäärä ja käsitelty sisältöM SK2

```csharp
     public List<PostListModel> GetPosts(string[] pages)
    {
        List<PostListModel> pageModels = new();

        foreach (var page in pages)
        {
            var pageInfo = GetPage(page, false);

            var summary = Markdown.ToPlainText(pageInfo.restOfTheLines).Substring(0, 100) + "...";
            pageModels.Add(new PostListModel
            {
                Categories = pageInfo.categories, Title = pageInfo.title,
                Slug = pageInfo.slug, WordCount = WordCount(pageInfo.restOfTheLines),
                PublishedDate = pageInfo.publishDate, Summary = summary
            });
        }

        pageModels = pageModels.OrderByDescending(x => x.PublishedDate).ToList();
        return pageModels;
    }


    public List<PostListModel> GetPostsForFiles()
    {
        var pages = Directory.GetFiles("Markdown", "*.md");
        return GetPosts(pages);
    }
```