Χρησιμοποιώντας Markdown για το Blogging (ελληνικά (Greek))

Χρησιμοποιώντας Markdown για το Blogging

Friday, 02 August 2024

//

11 minute read

Εισαγωγή

Markdown είναι μια ελαφριά γλώσσα σήμανσης που μπορείτε να χρησιμοποιήσετε για την προσθήκη στοιχείων διαμόρφωσης σε κειμενικά έγγραφα κειμένου. Δημιουργήθηκε από τον John Gruber στο 2004, Markdown αποτελεί πλέον μία από τις πιο δημοφιλείς γλώσσες μάρκετινγκM SK2

Σε αυτόν τον ιστότοπο χρησιμοποιώ μια πολύ απλή προσέγγιση για το blogging, έχοντας δοκιμάσει και απέτυχε να διατηρήσει ένα blog στο παρελθόν ήθελα να κάνω όσο το δυνατόν πιο εύκολο να γράψετε και να δημοσιεύσετε posts. Χρησιμοποιώ markdown για να γράφετε τα μηνύματά μου και αυτό το site έχει μία υπηρεσία με τη χρήση Σημαντικό Για να μετατρέψετε τη σήμανση σε HTML.

Γιατί όχι ένα στατικό γεννήτρια ιστοσελίδων?

Σε μια λέξη απλότητα. Αυτό δεν είναιΜSK1 θα είναι ένα super high traffic site, Χρησιμοποιώ ASPM SK3NET OutPutCache για να κρυπτογραφήσετε τις σελίδες και εγώ ΜSK4 δεν πρόκειται να το ενημερώσει ότι συχνά ΜΚ5 ήθελα να κρατήσω τον ιστότοπο όσο πιο απλό όσο το δυνατόν και δεν χρειάζεται να ανησυχείτε για την κορυφή ενός στατικού γεννήτη τοποθεσίας τόσο όσον αφορά τη διαδικασία κατασκευής και την πολυπλοκότητα του ιστοτόπου

Για να διευκρινιστεί; στατικές γεννήτριες τοποθεσίας όπως HUGO / Τζέικλ etc... μπορεί να είναι μια καλή λύση για πολλούς ιστότοπους, αλλά γι 'αυτό ήθελα να το κρατήσω απλό Για μένα όσο το δυνατόν περισσότερο.ΜSK1μ 25μ έτος ASPM SK3NET veteran έτσι καταλαβαίνω μέσα και έξωMK4Αυτός ο ιστότοπος σχεδιασμός προσθέτει πολυπλοκότητα;Έχω θέσειςM Sk6 υπηρεσιώνMzk7 διαχειριστές και ένα LOT του εγχειριδίου HTMLMk8μ CSS αλλά μου αρέσει'μ άνετα με αυτό

Γιατί όχι μια βάση δεδομένων?

  1. Απλότητα του σχεδιασμού;Τα βάσεις δεδομένων είναι ισχυρά συστήματα για την αποθήκευση δεδομένων ΜSK1 και I' θα προσθέσει ένα για σχόλια σύντομαM SK3 αλλά επίσης προσθέτουν πολυπλοκότηταMK4 σωστά Χρησιμοποιήστε τις βάσεις δεδομένων ειδικά σε μια εφαρμογή ASP.NET προσθέτετε ένα LOT του κώδικα, δεν έχει σημασία αν χρησιμοποιείτε το ' Κύριε, Δαπέρ ή ακατέργαστο SQL με ADO.NET. θέλω να κρατήσω τον ιστότοπο όσο το δυνατόν απλούστερο Για να ξεκινήσουμε με.
  2. Εύκολη ενημέρωση και εκτέλεση.Αυτό το site έχει ως στόχο να αποδείξει πόσο απλό Docker &Docker Compose μπορεί να είναι για να τρέξει ένα siteM SK2Μπορώ να ενημερώσω τον ιστότοπο με την επαλήθευση σε νέο κώδικα ΜSK3συμπεριλαμβανομένου του περιεχομένουMK4 στο GitHub, η δράση τρέχειM Sk6 χτίζει την εικόνα τότε η μέθοδος Watchtower στο docker μου συνθέτει τα αρχεία αναβαθμίσεις της εικόνας του ιστοτόπου αυτόματαMesk7Αυτός είναι ένας πολύ απλός τρόπος για να επικαιροποιήσετε μια ιστοσελίδα και ήθελα να τη διατηρήσω έτσι
  3. Δουλεύοντας διπλάσεις; καθώς έχω μηδενικά δεδομένα που δεν είναιΜSK1τ εντός της εικόνας docker αυτό σημαίνει ότι μπορώ EASILY εκτελέσει ακριβή διπλήσεις τοπικά (μουν το μικρό μου Ubuntu κλουστέρ εδώ στο σπίτιM SK3Αυτό είναι ένας πολύ καλός τρόπος για να δοκιμάσετε τις αλλαγές με το docker (μεις.μ Όταν έκανα τις αλλαγές στο ImageSharp ) πριν από την τοποθέτησή τους στον ζωντανό ιστότοποΜSK1
  4. Επειδή μου άρεσε't θέλω να πετύχω! ήθελα να δω πόσο μακριά θα μπορούσα να φτάσω με ένα απλό σχεδιασμό τοποθεσίας και μέχρι στιγμής μου αρέσει'm αρκετά χαρούμενος με αυτό.

Πώς να γράψετε τα μηνύματά σας?

Απλά ρίχνω ένα νέο αρχείο .md στο φάκελο Markdown και ο ιστότοπος το αναβαθμίζει και την επιστρέφει ΜSK1when I remember to aet it as content, this ensures itM SK3 avaiable in the output files

Στη συνέχεια, όταν ελέγχω την τοποθεσία στο GitHub η δράση τρέχει και ενημερώνεται ο ιστότοπος. 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

Πώς να προσθέσετε εικόνες?

Δεδομένου ότι μόλις πρόσθεσα την εικόνα εδώ, θα σας δείξω πώς το έκαναΜSK2 απλώς προσθέτω την εικόνα στο φάκελο wwwroot/articleimages και αναφέρομαι σε αυτό το αρχείο μάρκινγκ όπως αυτό

![setascontent.png](setascontent.png)

Στη συνέχεια, προσθέτω μια επέκταση στο δίκτυό μου Markdig που επαναγράφει αυτά στο σωστό URL ( αναφέρει για την απλότητα). αναφέρει Δείτε εδώ για τον κωδικό πηγής για την επέκταση.

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 είναι μια απλή υπηρεσία που διαβάζει τα αρχεία καταγραφής από το φάκελο Καταγραφής και τα μετατρέπει σε HTML χρησιμοποιώντας Markdig.

Η πλήρης πηγή για αυτό είναι κάτω και ΕΔΩ.

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);
    }
}
Όπως μπορείτε να δείτε αυτό έχει μερικά στοιχεία:

επεξεργασία αρχείων

Ο κώδικας για την επεξεργασία των αρχείων μάρκινγκ στο HTML είναι αρκετά απλός, αν και χρησιμοποιώ τη βιβλιοθήκη μάρκετινγκ για να μετατρέψω το μάρκα σε HTML και στη συνέχεια χρησιμοποιώ μερικές συνηθισμένες εκφράσεις για να εξαγάξω τις κατηγορίες και την δημοσιευμένη ημερομηνία από το αρχείο μαρκίνγκ

Η μέθοδος GetPage χρησιμοποιείται για να εξαγάγει το περιεχόμενο του αρχείου μάρκινγκ, αν έχει μερικά βήματα:

  1. Αποκτήστε τον τίτλο Με τη σύμβαση χρησιμοποιώ την πρώτη γραμμή του αρχείου μάρκας ως τον τίτλο της ανάρτησης. Έτσι μπορώ απλά να κάνωΜSK1
        var lines = File.ReadAllLines(page);

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

Δεδομένου ότι ο τίτλος είναι προεπιλεγμένος με "#" Χρησιμοποιώ τη μέθοδο MarkdownM SK1ToPlainText για να διαγράψω το ΜSK2 από τον τίτλο.

  1. Αποκτήστε τις κατηγορίες Κάθε ανάρτηση μπορεί να έχει έως και δύο κατηγορίες αυτή η μέθοδος εξάγει αυτές, τότε αφαιρέσω την ετικέτα από το αρχείο μάρκετινγκ.
// 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 χρησιμοποιεί μια τακτική έκφραση για να εξαγάγει τις κατηγορίες από το αρχείο μάρκετινγκ.

    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. Αποκτήστε την δημοσιευμένη ημερομηνία Στη συνέχεια, αφαιρέσω την ημερομηνία από το post (I WAS χρησιμοποιώντας τη δημιουργημένη ημέρα αλλά πώς εκτείνω αυτό χρησιμοποιώντας μια ολόκληρη εικόνα docker σημαίνει ότι αυτό δεν είναι σίγουρα' πραγματικά χρήσιμο πλέον) οπότε μου αρέσει'Μπορεί να μην χρησιμοποιεί μια τακτική έκφρασηM SK4 Αυτό μοιράζεται μια ετικέτα στη φόρμα που βρίσκεται σε κάθε .md αρχείο.
 
     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. Αποκτήστε το περιεχόμενο Στην πραγματικότητα, η λήψη του περιεχομένου είναι αρκετά απλή αυτό χρησιμοποιεί μια γραμμή ( για την αντικατάσταση της ετικέτας εικόνας που αναφέρεται παραπάνω) ίσως τότε προαιρετικά μου δίνει καθαρό κείμενο για τον κατάλογο των δημοσιεύσεων ή HTML για το πραγματικό post
    pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Use<ImgExtension>().Build();
    
   var processed =
            html ? Markdown.ToHtml(restOfTheLines, pipeline) : Markdown.ToPlainText(restOfTheLines, pipeline);
  1. Κάντε το 'slugΜSK1 Αυτό είναι απλώς το όνομα του αρχείου χωρίς την επέκταση:

        private string GetSlug(string fileName)
        {
            var slug = System.IO.Path.GetFileNameWithoutExtension(fileName);
            return slug.ToLowerInvariant();
        }
    
  2. Επιστρέψτε το περιεχόμενο Τώρα έχουμε περιεχόμενο σελίδας που μπορούμε να εμφανίσουμε για το blog!

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);
    }
Ο κώδικας παρακάτω δείχνει πώς δημιουργώ τη λίστα των δημοσιεύσεων στο blog, χρησιμοποιεί το `GetPage(page, false)` μέθοδος για την εξαγωγή του τίτλου, κατηγορίεςΜSK1 ημερομηνία δημοσίευσης και το επεξεργασμένο περιεχόμενο.
     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.