diff --git a/Mostlylucid/Controllers/BlogController.cs b/Mostlylucid/Controllers/BlogController.cs index ba4a199..d5def80 100644 --- a/Mostlylucid/Controllers/BlogController.cs +++ b/Mostlylucid/Controllers/BlogController.cs @@ -10,6 +10,6 @@ public class BlogController(BlogService blogService, ILogger log public IActionResult Show(string slug) { var post = blogService.GetPost(slug); - return View(); + return View("Post", post); } } \ No newline at end of file diff --git a/Mostlylucid/Markdown/githubactions.md b/Mostlylucid/Markdown/githubactions.md index 24aeef1..4ccb07f 100644 --- a/Mostlylucid/Markdown/githubactions.md +++ b/Mostlylucid/Markdown/githubactions.md @@ -1,10 +1,9 @@ # Using GitHub Actions to build and push a docker image - - This is a simple example of how to use GitHub Actions to build and push a docker image to a container registry. + ## Prerequisites - A docker file exists for the project you want to build and push. @@ -14,7 +13,9 @@ This is a simple example of how to use GitHub Actions to build and push a docker For this project I started with the basic .NET Core ASP.NET project and the default Dockerfile created by Rider. + ### Dockerfile + This Dockerfile is a multi-stage build that builds the project and then copies the output to a runtime image. For this proect, as I use TailwindCSS, I also need to install Node.js and run the TailwindCSS build command. @@ -32,7 +33,6 @@ This downloads the latest (at the time of writing) version of Node.js and instal Later in the file -
```dockerfile @@ -81,4 +81,6 @@ ENTRYPOINT ["dotnet", "Mostlylucid.dll"] ### Full DockerFile -
\ No newline at end of file + + + \ No newline at end of file diff --git a/Mostlylucid/Models/Blog/BlogPostViewModel.cs b/Mostlylucid/Models/Blog/BlogPostViewModel.cs index b660892..063e572 100644 --- a/Mostlylucid/Models/Blog/BlogPostViewModel.cs +++ b/Mostlylucid/Models/Blog/BlogPostViewModel.cs @@ -10,6 +10,17 @@ public class BlogPostViewModel public string Slug { get; set; }= string.Empty; + public string ReadingTime + { + get + { + var readCount = (float)WordCount / 200; + return readCount <1 ? "Less than a minute" : $"{Math.Round(readCount)} minute read"; + } + } + + public int WordCount { get; set; } + public DateTime UpdatedDate { get; set; } public DateTime CreatedDate { get; set; } diff --git a/Mostlylucid/Services/BlogService.cs b/Mostlylucid/Services/BlogService.cs index cd70d1a..27ec9f6 100644 --- a/Mostlylucid/Services/BlogService.cs +++ b/Mostlylucid/Services/BlogService.cs @@ -6,6 +6,7 @@ namespace Mostlylucid.Services; public class BlogService(ILogger logger) { + private MarkdownPipeline pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); private const string Path = "Markdown"; public BlogPostViewModel? GetPost(string postName) @@ -16,8 +17,8 @@ public class BlogService(ILogger logger) var page = GetPage(path, true); return new BlogPostViewModel { - Categories = page.categories, Content = page.text, UpdatedDate = page.lastWrite, - CreatedDate = page.created, Slug = page.slug, Title = page.title + Categories = page.categories,WordCount = WordCount(page.restOfTheLines) , Content = page.processed, UpdatedDate = page.lastWriteTime, + CreatedDate = page.creationTime, Slug = page.slug, Title = page.title }; } catch (Exception e) @@ -54,21 +55,39 @@ private static string[] GetCategories(string markdownText) .ToArray(); return categories; } - - public (string title, string slug, DateTime created, DateTime lastWrite, string text, string[] categories, string - restOfTheLines) GetPage(string page, bool html) + + public (string title, string slug, DateTime creationTime, DateTime lastWriteTime, string processed, string[] categories, string restOfTheLines) GetPage(string page, bool html) { var fileInfo = new FileInfo(page); - var lines = System.IO.File.ReadAllLines(page); - var title = Markdown.ToPlainText(lines[0].Trim()); + + // Ensure the file exists + if (!fileInfo.Exists) + { + throw new FileNotFoundException("The specified file does not exist.", page); + } - var restOfTheLines = string.Concat(lines.Skip(1)); + // Read all lines from the file + var lines = System.IO.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); + + // Remove category tags from the text restOfTheLines = CategoryRegex.Replace(restOfTheLines, ""); - var processed = html ? Markdown.ToHtml(restOfTheLines) : Markdown.ToPlainText(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, fileInfo.CreationTime, fileInfo.LastWriteTime, processed, categories, restOfTheLines); } @@ -84,8 +103,8 @@ public List GetPosts() pageModels.Add(new PostListModel { Categories = pageInfo.categories, Title = pageInfo.title, - Slug = pageInfo.slug, WordCount = WordCount(pageInfo.restOfTheLines), UpdatedDate = pageInfo.lastWrite, - CreatedDate = pageInfo.created, Summary = summary + Slug = pageInfo.slug, WordCount = WordCount(pageInfo.restOfTheLines), UpdatedDate = pageInfo.lastWriteTime, + CreatedDate = pageInfo.creationTime, Summary = summary }); } diff --git a/Mostlylucid/Views/Blog/Post.cshtml b/Mostlylucid/Views/Blog/Post.cshtml index 72afb68..f796c04 100644 --- a/Mostlylucid/Views/Blog/Post.cshtml +++ b/Mostlylucid/Views/Blog/Post.cshtml @@ -1,4 +1,4 @@ - +@model Mostlylucid.Models.Blog.BlogPostViewModel @{ ViewBag.Title = "title"; @@ -7,93 +7,30 @@
- category + @foreach(var category in Model.Categories) + { + @category + }

- Using Git Submodules for Private Content + class="block font-body text-3xl font-semibold leading-tight text-primary dark:text-white sm:text-4xl md:text-5xl"> + @Model.Title

- July 19, 2020 + @Model.CreatedDate.ToString("D")

//

- 4 min read + @Model.ReadingTime

-

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Augue ut lectus arcu bibendum at varius vel pharetra vel. Turpis nunc eget lorem dolor sed viverra ipsum nunc aliquet. Massa placerat duis ultricies lacus sed turpis tincidunt. Cursus sit amet dictum sit amet justo donec enim. Nec dui nunc mattis enim ut tellus elementum sagittis vitae. Id semper risus in hendrerit gravida rutrum quisque. Posuere ac ut consequat semper viverra nam libero justo laoreet. Turpis cursus in hac habitasse platea.

- -

Elit ullamcorper dignissim cras tincidunt lobortis feugiat vivamus at augue. Pellentesque massa placerat duis ultricies lacus sed. Interdum velit laoreet id donec ultrices tincidunt arcu non. Id diam maecenas ultricies mi eget mauris. Urna id volutpat lacus laoreet non. Amet est placerat in egestas erat imperdiet sed euismod. Dui sapien eget mi proin sed libero enim sed faucibus. Facilisis mauris sit amet massa vitae. Vitae congue mauris rhoncus aenean vel elit. Ut etiam sit amet nisl. Commodo odio aenean sed adipiscing diam donec adipiscing tristique.

- -

Non odio euismod lacinia at quis risus. Elit duis tristique sollicitudin nibh sit amet commodo. Aliquam ultrices sagittis orci a scelerisque purus semper eget duis. Ipsum consequat nisl vel pretium lectus. Pretium vulputate sapien nec sagittis aliquam. Morbi tincidunt augue interdum velit euismod in pellentesque massa. Nullam non nisi est sit.

- -
-

Note: Some of the earlier articles may be amateur and have information that I wouldn’t necessarily put into an article on the subject if I wrote it today.

-
- -

Lorem ipsum dolor sit amet

- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Amet aliquam id diam maecenas ultricies mi eget mauris pharetra. Congue nisi vitae suscipit tellus.

- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua:

- -
    -
  • Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
  • -
  • Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
  • -
  • Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
  • -
  • Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
  • -
  • Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
  • -
- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Risus viverra adipiscing at in tellus. Proin libero nunc consequat interdum. Sed arcu non odio euismod lacinia at quis.

- - - -

It’s great that I learned all that, because for my first real job in the industry, I was going to use practically none of it. No Git, no Node, no Sass, no Grunt nor Gulp, no command line, no Bootstrap. Just cold, hard WordPress. Nonetheless, this was going to present a and things to learn.</p>

- -

Sed do eiusmod tempor incididunt

- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Enim lobortis scelerisque fermentum dui faucibus in ornare quam viverra. Augue ut lectus arcu bibendum at varius vel. Lacus viverra vitae congue eu consequat ac felis.

- -

Image

- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Condimentum id venenatis a condimentum vitae sapien pellentesque habitant morbi. Semper feugiat nibh sed pulvinar.

- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Vel pretium lectus quam id leo in. Ullamcorper dignissim cras tincidunt lobortis feugiat vivamus at augue eget.

- -

Ullamcorper dignissim cras tincidunt

- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Laoreet non curabitur gravida arcu ac tortor dignissim convallis aenean. Integer enim neque volutpat ac tincidunt vitae semper quis lectus.

- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Leo a diam sollicitudin tempor id eu nisl nunc.

- -
const btn = document.getElementById('btn')
-let count = 0
-function render() {
-  btn.innerText = `Count: ${count}`
-}
-btn.addEventListener('click', () => {
-  // Count from 1 to 10.
-  if (count < 10) {
-    count += 1
-    render()
-  }
-
- -

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+ class="prose prose max-w-none border-b border-grey-lighter py-8 dark:prose-dark sm:py-12"> + @Html.Raw(Model.Content) +
+
@@ -120,5 +57,4 @@ class="bx bxl-reddit pl-2 text-2xl text-primary transition-colors hover:text-secondary dark:text-white dark:hover:text-secondary" > -
- \ No newline at end of file + \ No newline at end of file diff --git a/Mostlylucid/Views/Shared/_ListPost.cshtml b/Mostlylucid/Views/Shared/_ListPost.cshtml index 493a751..9d3dce7 100644 --- a/Mostlylucid/Views/Shared/_ListPost.cshtml +++ b/Mostlylucid/Views/Shared/_ListPost.cshtml @@ -8,8 +8,7 @@ @category } - @Model.Title
@Model.Summary
diff --git a/Mostlylucid/tailwind.config.js b/Mostlylucid/tailwind.config.js index afdd903..c2bbd31 100644 --- a/Mostlylucid/tailwind.config.js +++ b/Mostlylucid/tailwind.config.js @@ -160,11 +160,9 @@ }, }, plugins: [ - require("@tailwindcss/typography")({ - modifiers: [], - }), require("@tailwindcss/forms"), require("@tailwindcss/aspect-ratio"), + require("@tailwindcss/typography"), require('daisyui'), ], }; \ No newline at end of file diff --git a/Mostlylucid/wwwroot/css/dist/main.css b/Mostlylucid/wwwroot/css/dist/main.css index c5deb4e..95a875f 100644 --- a/Mostlylucid/wwwroot/css/dist/main.css +++ b/Mostlylucid/wwwroot/css/dist/main.css @@ -1526,117 +1526,6 @@ html { background-color: var(--fallback-p,oklch(var(--p)/)); } -.btn { - display: inline-flex; - height: 3rem; - min-height: 3rem; - flex-shrink: 0; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - flex-wrap: wrap; - align-items: center; - justify-content: center; - border-radius: var(--rounded-btn, 0.5rem); - border-color: transparent; - border-color: oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity)); - padding-left: 1rem; - padding-right: 1rem; - text-align: center; - font-size: 0.875rem; - line-height: 1em; - gap: 0.5rem; - font-weight: 600; - text-decoration-line: none; - transition-duration: 200ms; - transition-timing-function: cubic-bezier(0, 0, 0.2, 1); - border-width: var(--border-btn, 1px); - transition-property: color, background-color, border-color, opacity, box-shadow, transform; - --tw-text-opacity: 1; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - outline-color: var(--fallback-bc,oklch(var(--bc)/1)); - background-color: oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity)); - --tw-bg-opacity: 1; - --tw-border-opacity: 1; -} - -.btn-disabled, - .btn[disabled], - .btn:disabled { - pointer-events: none; -} - -:where(.btn:is(input[type="checkbox"])), -:where(.btn:is(input[type="radio"])) { - width: auto; - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; -} - -.btn:is(input[type="checkbox"]):after, -.btn:is(input[type="radio"]):after { - --tw-content: attr(aria-label); - content: var(--tw-content); -} - -@media (hover: hover) { - .btn:hover { - --tw-border-opacity: 1; - border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity))); - } - - @supports (color: color-mix(in oklab, black, black)) { - .btn:hover { - background-color: color-mix( - in oklab, - oklch(var(--btn-color, var(--b2)) / var(--tw-bg-opacity, 1)) 90%, - black - ); - border-color: color-mix( - in oklab, - oklch(var(--btn-color, var(--b2)) / var(--tw-border-opacity, 1)) 90%, - black - ); - } - } - - @supports not (color: oklch(0% 0 0)) { - .btn:hover { - background-color: var(--btn-color, var(--fallback-b2)); - border-color: var(--btn-color, var(--fallback-b2)); - } - } - - .btn.glass:hover { - --glass-opacity: 25%; - --glass-border-opacity: 15%; - } - - .btn-disabled:hover, - .btn[disabled]:hover, - .btn:disabled:hover { - --tw-border-opacity: 0; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-bg-opacity: 0.2; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; - } - - @supports (color: color-mix(in oklab, black, black)) { - .btn:is(input[type="checkbox"]:checked):hover, .btn:is(input[type="radio"]:checked):hover { - background-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); - border-color: color-mix(in oklab, var(--fallback-p,oklch(var(--p)/1)) 90%, black); - } - } -} - .link { cursor: pointer; text-decoration-line: underline; @@ -1693,71 +1582,12 @@ html { opacity: 1; } -@media (prefers-reduced-motion: no-preference) { - .btn { - animation: button-pop var(--animation-btn, 0.25s) ease-out; - } -} - -.btn:active:hover, - .btn:active:focus { - animation: button-pop 0s ease-out; - transform: scale(var(--btn-focus-scale, 0.97)); -} - @supports not (color: oklch(0% 0 0)) { - .btn { - background-color: var(--btn-color, var(--fallback-b2)); - border-color: var(--btn-color, var(--fallback-b2)); - } - .prose :where(code):not(:where([class~="not-prose"] *, pre *)) { background-color: var(--fallback-b3,oklch(var(--b3)/1)); } } -.btn:focus-visible { - outline-style: solid; - outline-width: 2px; - outline-offset: 2px; -} - -.btn.glass { - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - outline-color: currentColor; -} - -.btn.glass.btn-active { - --glass-opacity: 25%; - --glass-border-opacity: 15%; -} - -.btn.btn-disabled, - .btn[disabled], - .btn:disabled { - --tw-border-opacity: 0; - background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity))); - --tw-bg-opacity: 0.2; - color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity))); - --tw-text-opacity: 0.2; -} - -.btn:is(input[type="checkbox"]:checked), -.btn:is(input[type="radio"]:checked) { - --tw-border-opacity: 1; - border-color: var(--fallback-p,oklch(var(--p)/var(--tw-border-opacity))); - --tw-bg-opacity: 1; - background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity))); - --tw-text-opacity: 1; - color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity))); -} - -.btn:is(input[type="checkbox"]:checked):focus-visible, .btn:is(input[type="radio"]:checked):focus-visible { - outline-color: var(--fallback-p,oklch(var(--p)/1)); -} - @keyframes button-pop { 0% { transform: scale(var(--btn-focus-scale, 0.98)); @@ -1786,10 +1616,6 @@ html { } } -.join > :where(*:not(:first-child)):is(.btn) { - margin-inline-start: calc(var(--border-btn) * -1); -} - .link:focus { outline: 2px solid transparent; outline-offset: 2px; @@ -1915,14 +1741,6 @@ html { border-bottom-color: var(--fallback-bc,oklch(var(--bc)/0.2)); } -.join.join-vertical > :where(*:not(:first-child)):is(.btn) { - margin-top: calc(var(--border-btn) * -1); -} - -.join.join-horizontal > :where(*:not(:first-child)):is(.btn) { - margin-inline-start: calc(var(--border-btn) * -1); -} - .pointer-events-none { pointer-events: none; } diff --git a/docker-compose.yml b/docker-compose.yml index ade4e96..3a4e5aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,6 @@ services: mostlylucid: - image: mostlylucid - build: - context: . - dockerfile: Mostlylucid/Dockerfile + image: scottgal/mostlylucid:latest cloudflared: image: cloudflare/cloudflared:latest command: tunnel --no-autoupdate run --token ${CLOUDFLARED_TOKEN}