Markdownin käyttö Blogging-asioissa (Suomi (Finnish))

Markdownin käyttö Blogging-asioissa

Friday, 02 August 2024

//

10 minute read

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 converting markdown to HTML.

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 / Jekyll 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, 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 ) 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!

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

Miten lisätään kuvia?

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

![setascontent.png](setascontent.png)

Sitten liitän Markdig-putkeni laajennus, joka kirjoittaa ne uudelleen oikeaan URL:hen. (kaikki yksinkertaisuudestaM SK1 Ks. liitteen lähdekoodia.

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ä.

Click to see the source code for the BlogService

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);
    }
}
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ä
        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

  1. Luettelo otsakkeista Jokaisella postilla voi olla jopa kaksi luokkaa tämä menetelmä ekstrahoi nämä sitten poistan merkitsemisaineistosta tagin.
// 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.

    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;
        
        
    }
  1. 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
 
     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, "");
  1. 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
    pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Use<ImgExtension>().Build();
    
   var processed =
            html ? Markdown.ToHtml(restOfTheLines, pipeline) : Markdown.ToPlainText(restOfTheLines, pipeline);
  1. Ottakaa 'slug' Tämä on yksinkertaisesti filename ilman laajennusta:

        private string GetSlug(string fileName)
        {
            var slug = System.IO.Path.GetFileNameWithoutExtension(fileName);
            return slug.ToLowerInvariant();
        }
    
  2. Palauttaa sisältö Nyt meillä on sivusisältö, jonka voimme kuvailla blogille!

The GetPage Method
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);
    }
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
     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);
    }
Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.