diff options
| author | Paweł Bernaciak <pawelbernaciak@zohomail.eu> | 2023-12-29 09:41:56 +0100 |
|---|---|---|
| committer | Paweł Bernaciak <pawelbernaciak@zohomail.eu> | 2023-12-29 09:41:56 +0100 |
| commit | 79f5eaf40b93d64be74d8b4f1ef80d19fadbfbe1 (patch) | |
| tree | ddf121e78bdd50673379b223abfdb448c4f81291 /CoreWiki/Pages | |
| parent | 1a96616699ab41bf6343bc1acc45a836c3e6caf3 (diff) | |
Diffstat (limited to 'CoreWiki/Pages')
| -rw-r--r-- | CoreWiki/Pages/All.cshtml | 36 | ||||
| -rw-r--r-- | CoreWiki/Pages/All.cshtml.cs | 2 | ||||
| -rw-r--r-- | CoreWiki/Pages/Create.cshtml | 48 | ||||
| -rw-r--r-- | CoreWiki/Pages/Delete.cshtml | 41 | ||||
| -rw-r--r-- | CoreWiki/Pages/Details.cshtml | 28 | ||||
| -rw-r--r-- | CoreWiki/Pages/Details.cshtml.cs | 86 | ||||
| -rw-r--r-- | CoreWiki/Pages/Edit.cshtml | 19 | ||||
| -rw-r--r-- | CoreWiki/Pages/LatestChanges.cshtml | 18 | ||||
| -rw-r--r-- | CoreWiki/Pages/LatestChanges.cshtml.cs | 7 | ||||
| -rw-r--r-- | CoreWiki/Pages/Shared/_Layout.cshtml | 78 | ||||
| -rw-r--r-- | CoreWiki/Pages/_ViewImports.cshtml | 3 | ||||
| -rw-r--r-- | CoreWiki/Pages/_articleList.cshtml | 20 | ||||
| -rw-r--r-- | CoreWiki/Pages/_comments.cshtml | 35 | ||||
| -rw-r--r-- | CoreWiki/Pages/_commentsForm.cshtml | 32 | ||||
| -rw-r--r-- | CoreWiki/Pages/_commentsList.cshtml | 25 |
15 files changed, 318 insertions, 160 deletions
diff --git a/CoreWiki/Pages/All.cshtml b/CoreWiki/Pages/All.cshtml index ca06cb5..6e3430e 100644 --- a/CoreWiki/Pages/All.cshtml +++ b/CoreWiki/Pages/All.cshtml @@ -5,40 +5,28 @@ ViewData["Title"] = "All articles"; } -<h2>All articles</h2> +<h1>All articles</h1> -@foreach (var item in Model.Articles) -{ - <div class="card border-primary my-2 mx-1"> - <div class="card-body"> - <h3 class="card-title"> - <a href="~/@item.Slug">@item.Topic</a> - </h3> - <h6 class="card-subtitle mb-2 text-muted"> - <span data-value="@item.Published" class="timeStampValue">@item.Published</span> - </h6> +<partial name="_articleList" model="Model.Articles"/> - <a class="card-link" asp-page="./Edit" asp-route-slug="@item.Slug">Edit</a> - <a class="card-link" asp-page="./Delete" asp-route-slug="@item.Slug">Delete</a> - </div> - </div> -} - -<div class="col-md-12 d-flex"> - <nav class="flex-grow-1"> - <ul class="pagination"> +<div class="col-md-12 flex"> + <nav class="flex-1"> + <ul class="flex -space-x-px"> @for (var i = 1; i <= Model.TotalPages; i++) { var i1 = i; - <li class="page-item @(i1 == Model.PageNumber ? "active" : "")"> - <a class="page-link" asp-page="All" asp-route-PageNumber="@i1" asp-route-PageSize="@Model.PageSize">@i1</a> + <li> + <a class="block p-3 text-sm leading-tight border border-gray-300 bg-white @(i1 == Model.PageNumber ? "bg-blue-100 text-blue-700" : "hover:bg-gray-50")" + asp-page="All" + asp-route-PageNumber="@i1" + asp-route-PageSize="@Model.PageSize">@i1</a> </li> } </ul> </nav> - <form class="ms-auto" method="get" asp-page="All"> + <form class="ml-auto" method="get" asp-page="All"> <input hidden name="@nameof(Model.PageNumber)" value="@Model.PageNumber"/> - <select class="form-select" asp-for="@Model.PageSize" onchange="this.form.submit()"> + <select class="bg-gray-50 border border-gray-300 text-gray-900 text-sm focus:ring-blue-500 focus:border-blue-500 block w-full p-3" asp-for="@Model.PageSize" onchange="this.form.submit()"> <option value="2">2</option> <option value="10">10</option> <option value="25">25</option> diff --git a/CoreWiki/Pages/All.cshtml.cs b/CoreWiki/Pages/All.cshtml.cs index c41e216..d54e75b 100644 --- a/CoreWiki/Pages/All.cshtml.cs +++ b/CoreWiki/Pages/All.cshtml.cs @@ -14,7 +14,7 @@ public class All : PageModel public int PageNumber { get; set; } = 1; [BindProperty(SupportsGet = true)] public int PageSize { get; set; } = 25; - public IEnumerable<Article>? Articles { get; set; } + public IEnumerable<Article> Articles { get; set; } public int TotalPages { get; set; } public All(ApplicationDbContext context) diff --git a/CoreWiki/Pages/Create.cshtml b/CoreWiki/Pages/Create.cshtml index 4abbf11..153035a 100644 --- a/CoreWiki/Pages/Create.cshtml +++ b/CoreWiki/Pages/Create.cshtml @@ -5,45 +5,33 @@ ViewData["Title"] = "Create";
}
-<h1>Create</h1>
-
-<h4>Article</h4>
-<hr />
-<div class="row d-flex justify-content-center">
- <div class="col-md-8">
- <form method="post">
- <div asp-validation-summary="ModelOnly" class="text-danger"></div>
- <div class="form-group">
- <label asp-for="Article.Topic" class="control-label"></label>
- <input asp-for="Article.Topic" class="form-control" />
- <span asp-validation-for="Article.Topic" class="text-danger"></span>
- </div>
- <div class="form-group">
- <label asp-for="Article.Content" class="control-label"></label>
- <textarea asp-for="Article.Content" class="form-control"></textarea>
- <span asp-validation-for="Article.Content" class="text-danger"></span>
- </div>
- <div class="form-group">
- <input type="submit" value="Create" class="btn btn-primary" />
- </div>
- </form>
+<h1>Create Article</h1>
+<form class="space-y-3" method="post">
+ <div asp-validation-summary="ModelOnly" class="text-red-600"></div>
+ <div>
+ <label asp-for="Article.Topic" class="block mb-2 text-sm font-medium text-gray-900"></label>
+ <input asp-for="Article.Topic" class="block bg-gray-50 border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500 text-sm p-2 w-full"/>
+ <span asp-validation-for="Article.Topic" class="text-red-600"></span>
</div>
-</div>
-
-<div>
- <a asp-page="Index">Back to List</a>
-</div>
+ <div>
+ <label asp-for="Article.Content" class="block mb-2 text-sm font-medium text-gray-900"></label>
+ <textarea asp-for="Article.Content"></textarea>
+ <span asp-validation-for="Article.Content" class="text-red-600"></span>
+ </div>
+ <div class="flex space-x-2">
+ <input type="submit" value="Create" class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-1.5 focus:outline-none"/>
+ <a class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-2 focus:outline-none" asp-page="Details">Back to List</a>
+ </div>
+</form>
@section Scripts {
<script src="~/lib/simplemde/simplemde.min.js"></script>
<script>
var simplemde = new SimpleMDE();
</script>
- @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
-
}
@section Styles
{
<link href="~/lib/simplemde/simplemde.min.css" rel="stylesheet"/>
-}
+}
\ No newline at end of file diff --git a/CoreWiki/Pages/Delete.cshtml b/CoreWiki/Pages/Delete.cshtml index 0b680b8..0c721bd 100644 --- a/CoreWiki/Pages/Delete.cshtml +++ b/CoreWiki/Pages/Delete.cshtml @@ -5,30 +5,19 @@ ViewData["Title"] = "Delete";
}
-<h1>Delete</h1>
-
-<h3>Are you sure you want to delete this?</h3>
-<div>
- <h4>Article</h4>
- <hr />
- <dl class="row">
- <dt class="col-sm-2">
- @Html.DisplayNameFor(model => model.Article.Published)
- </dt>
- <dd class="col-sm-10">
- @Html.DisplayFor(model => model.Article.Published)
- </dd>
- <dt class="col-sm-2">
- @Html.DisplayNameFor(model => model.Article.Content)
- </dt>
- <dd class="col-sm-10">
- @Html.DisplayFor(model => model.Article.Content)
- </dd>
- </dl>
-
- <form method="post">
- <input type="hidden" asp-for="Article.Slug" />
- <input type="submit" value="Delete" class="btn btn-danger" /> |
- <a href="/">Back to Home Page</a>
- </form>
+<h2 class="mb-3">Are you sure you want to delete this article?</h2>
+<div class="mb-3">
+ <div class="flex flex-col">
+ <span>@Html.DisplayNameFor(model => model.Article.Published)</span>
+ <span>@Html.DisplayFor(model => model.Article.Published)</span>
+ </div>
+ <div class="flex flex-col">
+ <span>@Html.DisplayNameFor(model => model.Article.Content)</span>
+ <span>@Html.DisplayFor(model => model.Article.Content)</span>
+ </div>
</div>
+<form method="post">
+ <input type="hidden" asp-for="Article.Id"/>
+ <input type="submit" value="Delete" class="text-white bg-red-500 hover:bg-red-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-1.5 focus:outline-none"/>
+ <a class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-2 focus:outline-none" href="/">Back to Home Page</a>
+</form>
\ No newline at end of file diff --git a/CoreWiki/Pages/Details.cshtml b/CoreWiki/Pages/Details.cshtml index 9f48f9f..3b7dff1 100644 --- a/CoreWiki/Pages/Details.cshtml +++ b/CoreWiki/Pages/Details.cshtml @@ -5,16 +5,32 @@ ViewData["Title"] = Model.Article.Topic;
}
-<h1>@Model.Article.Topic</h1>
-<h5>Last Published: <span data-value="@Model.Article.Published" class="timeStampValue">@Model.Article.Published</span></h5>
+<div class="h-full">
+ <h2>@Model.Article.Topic</h2>
+ <p class="text-sm text-gray-500">Last Published: <span data-value="@Model.Article.Published" class="timeStampValue">@Model.Article.Published</span></p>
+ <p class="text-sm text-gray-500">Reading time: @Model.Article.EstimatedReadingTime minutes</p>
+ <p class="text-sm text-gray-500">Views: @Model.Article.ViewCount</p>
-<markdown markdown="Article.Content"/>
+ <markdown markdown="Article.Content"/>
+</div>
-<div>
- <a asp-page="./Edit" asp-route-slug="@Model.Article.Slug">Edit</a>
+<div class="mt-4">
+ <a class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-2 focus:outline-none"
+ asp-page="./Edit"
+ asp-route-slug="@Model.Article.Slug">Edit</a>
@if (Model.Article.Slug != "home-page")
{
- <text>| </text> <a href="~/">Back to Home</a>
+ <a class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-2 focus:outline-none"
+ href="~/">Back to Home</a>
}
</div>
+<br/>
+<div class="mt-auto cursor-pointer" x-data="{ formOpen: false, commentsOpen: false }" id="comments">
+ <partial name="_comments" model="@Model"/>
+</div>
+
+@section Styles
+{
+ <link href="~/lib/simplemde/simplemde.min.css" rel="stylesheet"/>
+}
\ No newline at end of file diff --git a/CoreWiki/Pages/Details.cshtml.cs b/CoreWiki/Pages/Details.cshtml.cs index e4100ac..bdd576f 100644 --- a/CoreWiki/Pages/Details.cshtml.cs +++ b/CoreWiki/Pages/Details.cshtml.cs @@ -6,32 +6,108 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.EntityFrameworkCore;
using CoreWiki.Models;
+using Htmx;
+using Microsoft.AspNetCore.Http.HttpResults;
+using NodaTime;
namespace CoreWiki.Pages
{
public class DetailsModel : PageModel
{
+ private readonly IClock _clock;
private readonly ApplicationDbContext _context;
- public DetailsModel(ApplicationDbContext context)
+ public DetailsModel(ApplicationDbContext context, IClock clock)
{
+ _clock = clock;
_context = context;
}
- public Article Article { get; set; } = default!;
+ private const int CommentsPerPage = 10;
- public async Task<IActionResult> OnGetAsync(string? slug)
+ public Article Article { get; set; } = default!;
+ [BindProperty] public Comment Comment { get; set; }
+
+ public async Task<IActionResult> OnGetAsync(string? slug, [FromQuery] int? pageNumber)
{
slug ??= "home-page";
+ var pagesToSkip = CommentsPerPage * ((pageNumber ?? 1) - 1);
- var article = await _context.Articles.FirstOrDefaultAsync(a => a.Slug == slug);
+ var article = await _context.Articles
+ .Include(a => a.Comments
+ .OrderByDescending(c => c.SubmittedDateTime)
+ .Skip(pagesToSkip)
+ .Take(CommentsPerPage))
+ .FirstOrDefaultAsync(a => a.Slug == slug);
if (article == null)
{
return NotFound();
}
Article = article;
+ if (Request.Cookies[Article.Slug] == null)
+ {
+ Article.ViewCount++;
+ Response.Cookies.Append(Article.Slug, "viewed", new CookieOptions()
+ {
+ Expires = DateTime.UtcNow.AddMinutes(5)
+ });
+
+ await _context.SaveChangesAsync();
+ }
+
+ if (Request.IsHtmx() && !Request.IsHtmxBoosted())
+ {
+ return Partial("_commentsList", new ArticleCommentListViewModel
+ {
+ Comments = Article.Comments,
+ Slug = slug,
+ PageNumber = pageNumber ?? 1
+ });
+ }
+
return Page();
}
+
+ public async Task<IActionResult> OnPostAsync(string? slug)
+ {
+ slug ??= "home-page";
+ var article = await _context.Articles.FirstOrDefaultAsync(a => a.Slug == slug);
+ if (article == null)
+ {
+ return NotFound();
+ }
+
+ Article = article;
+
+ ModelState.Remove("Comment.Article");
+ if (!ModelState.IsValid)
+ {
+ if (Request.IsHtmx() && !Request.IsHtmxBoosted())
+ {
+ return Partial("_commentsForm", this);
+ }
+
+ return Page();
+ }
+
+ Comment.Submitted = _clock.GetCurrentInstant();
+ article.Comments.Add(Comment);
+ await _context.SaveChangesAsync();
+
+ if (Request.IsHtmx() && !Request.IsHtmxBoosted())
+ {
+ Response.Htmx(h => { h.WithTrigger("commentsChanged"); });
+
+ Comment.DisplayName = String.Empty;
+ Comment.EMail = String.Empty;
+ Comment.Content = String.Empty;
+ ModelState.Clear();
+
+ return Partial("_commentsForm", this);
+ }
+
+ return RedirectToPage("Details", new { slug = slug == "home-page" ? "" : slug });
+ }
}
-}
+}
\ No newline at end of file diff --git a/CoreWiki/Pages/Edit.cshtml b/CoreWiki/Pages/Edit.cshtml index 8793034..38c4891 100644 --- a/CoreWiki/Pages/Edit.cshtml +++ b/CoreWiki/Pages/Edit.cshtml @@ -8,24 +8,22 @@ <h1>Edit</h1>
-<br />
<div class="row">
<div class="col-md-12">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
- <input type="hidden" asp-for="Article.Slug" />
- <input type="hidden" asp-for="Article.Topic" />
+ <input type="hidden" asp-for="Article.Id"/>
+ <input type="hidden" asp-for="Article.Slug"/>
+ <input type="hidden" asp-for="Article.Topic"/>
+ <input type="hidden" asp-for="Article.ViewCount"/>
+ <h3 class="mb-2">@Model.Article.Topic</h3>
<div class="form-group">
- <h3>@Model.Article.Topic</h3>
- </div>
- <div class="form-group">
- <label asp-for="Article.Content" class="control-label"></label>
<textarea asp-for="Article.Content" class="form-control"></textarea>
<span asp-validation-for="Article.Content" class="text-danger"></span>
</div>
<div class="form-group">
- <input type="submit" value="Save" class="btn btn-primary" />
- <a class="btn btn-outline-secondary" href="~/">Back to Home</a>
+ <input type="submit" value="Save" class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-1.5 focus:outline-none"/>
+ <a class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-2 focus:outline-none" href="~/">Back to Home</a>
</div>
</form>
</div>
@@ -36,10 +34,9 @@ <script>
var simplemde = new SimpleMDE();
</script>
- @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}
@section Styles
{
<link href="~/lib/simplemde/simplemde.min.css" rel="stylesheet"/>
-}
+}
\ No newline at end of file diff --git a/CoreWiki/Pages/LatestChanges.cshtml b/CoreWiki/Pages/LatestChanges.cshtml index 677887b..8657edb 100644 --- a/CoreWiki/Pages/LatestChanges.cshtml +++ b/CoreWiki/Pages/LatestChanges.cshtml @@ -7,19 +7,7 @@ <h1>Latest Changes</h1>
-<p>
- <a asp-page="Create">Create New</a>
-</p>
+<a class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-2 focus:outline-none my-3 w-fit"
+ asp-page="Create">Create New</a>
-@foreach (var item in Model.Article)
-{
- <div class="card border-primary m-1">
- <div class="card-body">
- <h3 class="card-title"><a href="~/@item.Slug">@item.Topic</a></h3>
- <h6 class="card-subtitle mb-2 text-muted"><span data-value="@item.Published" class="timeStampValue">@item.Published</span></h6>
-
- <a class="card-link" asp-page="./Edit" asp-route-slug="@item.Slug">Edit</a>
- <a class="card-link" asp-page="./Delete" asp-route-slug="@item.Slug">Delete</a>
- </div>
- </div>
-}
\ No newline at end of file +<partial name="_articleList" model="Model.Article"/>
\ No newline at end of file diff --git a/CoreWiki/Pages/LatestChanges.cshtml.cs b/CoreWiki/Pages/LatestChanges.cshtml.cs index 83a70ab..21dde4c 100644 --- a/CoreWiki/Pages/LatestChanges.cshtml.cs +++ b/CoreWiki/Pages/LatestChanges.cshtml.cs @@ -18,14 +18,11 @@ namespace CoreWiki.Pages _context = context;
}
- public IList<Article> Article { get;set; } = default!;
+ public IList<Article> Article { get; set; } = default!;
public async Task OnGetAsync()
{
- if (_context.Articles != null)
- {
- Article = await _context.Articles.OrderByDescending(a => a.PublishedDateTime).Take(10).ToListAsync();
- }
+ Article = await _context.Articles.OrderByDescending(a => a.PublishedDateTime).Take(10).ToListAsync();
}
}
}
diff --git a/CoreWiki/Pages/Shared/_Layout.cshtml b/CoreWiki/Pages/Shared/_Layout.cshtml index ac74eba..03d8607 100644 --- a/CoreWiki/Pages/Shared/_Layout.cshtml +++ b/CoreWiki/Pages/Shared/_Layout.cshtml @@ -1,59 +1,65 @@ -<!DOCTYPE html> +@using Htmx.TagHelpers +<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> + <meta name="htmx-config" includeAspNetAntiforgeryToken="true"/> <title>@ViewData["Title"] - CoreWiki</title> - <link rel="stylesheet" href="~/lib/bootstrap/css/bootstrap.min.css"/> - <link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/> - <link rel="stylesheet" href="~/CoreWiki.styles.css" asp-append-version="true"/> - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.3.0/css/all.min.css" integrity="sha512-SzlrxWUlpfuzQ+pcUCosxcglQRNAq/DZjVsC0lE40xsADsfeQoEypE+enwcOiGjk/bSuGGKHEyjSoQ1zVisanQ==" crossorigin="anonymous" referrerpolicy="no-referrer"/> + <link rel="stylesheet" href="~/css/output.css"/> @await RenderSectionAsync("Styles", required: false) </head> <body> -<header> - <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-light border-bottom box-shadow mb-3"> - <div class="container"> - <a class="navbar-brand" asp-page="Details">CoreWiki</a> - <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" - aria-expanded="false" aria-label="Toggle navigation"> - <span class="navbar-toggler-icon"></span> - </button> - <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> - <ul class="navbar-nav flex-grow-1"> - <li class="nav-item"> - <a class="nav-link" asp-page="Details">Home</a> +<script src="https://unpkg.com/htmx.org@1.9.10"></script> +<script src="//unpkg.com/alpinejs" defer></script> +<div class="flex flex-col min-h-screen"> + <header x-data="{ open: false }"> + <nav class="flex flex-col md:flex-row w-full px-4 py-3 bg-gray-50 border-bottom shadow"> + <div class="flex w-full md:w-fit"> + <a class="text-lg mr-1" asp-page="Details">CoreWiki</a> + <button @@click="open = !open" class="inline md:hidden ml-auto"> + <span class="ml-auto">Toggle</span> + </button> + </div> + <div :class="open || 'hidden'" class="w-full mt-4 md:block md:mt-0 md:w-fit md:ml-auto"> + <ul class="flex flex-col space-y-3 md:flex-row md:space-x-4 md:space-y-0"> + <li> + <a asp-page="Details">Home</a> </li> - <li class="nav-item"> - <a class="nav-link" asp-area="" asp-page="Create">Create</a> + <li> + <a asp-area="" asp-page="Create">Create</a> </li> - <li class="nav-item"> - <a class="nav-link" asp-page="All">All Articles</a> + <li> + <a hx-boost="true" asp-page="All">All Articles</a> </li> - <li class="nav-item"> - <a class="nav-link" asp-page="LatestChanges">Latest Changes</a> + <li> + <a hx-boost="true" asp-page="LatestChanges">Latest Changes</a> </li> </ul> </div> - </div> - </nav> -</header> -<div class="container"> - <main role="main" class="pb-3"> + </nav> + </header> + <main role="main" class="flex-1 flex flex-col px-8 md:px-48 py-8"> @RenderBody() </main> + <footer class="flex justify-center text-gray-500 shadow border-t py-4 px-2"> + <span>© 2023 - CoreWiki - <a asp-area="" asp-page="/Privacy">Privacy</a></span> + </footer> </div> -<footer class="border-top footer text-muted"> - <div class="container"> - © 2023 - CoreWiki - <a asp-area="" asp-page="/Privacy">Privacy</a> - </div> -</footer> - -<script src="~/lib/jquery/dist/jquery.min.js"></script> -<script src="~/lib/bootstrap/js/bootstrap.bundle.min.js"></script> +@Html.HtmxAntiforgeryScript() <script src="~/lib/momentjs/moment.min.js"></script> <script src="~/js/site.js" asp-append-version="true"></script> +<script> + document.addEventListener("htmx:afterRequest", function (e) { + const timeStamps = document.querySelectorAll(".timeStampValue"); + for (const ts of timeStamps) { + const thisTimeStamp = ts.getAttribute("data-value"); + const date = new Date(thisTimeStamp); + ts.textContent = moment(date).format('LLL'); + } + }); +</script> @await RenderSectionAsync("Scripts", required: false) </body> diff --git a/CoreWiki/Pages/_ViewImports.cshtml b/CoreWiki/Pages/_ViewImports.cshtml index 802f152..86f4a07 100644 --- a/CoreWiki/Pages/_ViewImports.cshtml +++ b/CoreWiki/Pages/_ViewImports.cshtml @@ -1,4 +1,5 @@ @using CoreWiki @namespace CoreWiki.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers -@addTagHelper *, Westwind.AspNetCore.Markdown
\ No newline at end of file +@addTagHelper *, Westwind.AspNetCore.Markdown +@addTagHelper *, Htmx.TagHelpers
\ No newline at end of file diff --git a/CoreWiki/Pages/_articleList.cshtml b/CoreWiki/Pages/_articleList.cshtml new file mode 100644 index 0000000..7b8b038 --- /dev/null +++ b/CoreWiki/Pages/_articleList.cshtml @@ -0,0 +1,20 @@ +@model IEnumerable<CoreWiki.Models.Article> + +@foreach (var item in Model) +{ + <div class="flex flex-col border rounded mb-3"> + <h3 class="bg-gray-50 p-2 border-b"> + <a href="~/@item.Slug">@item.Topic</a> + </h3> + <div class="flex flex-col p-2"> + <h6 class="font-normal card-subtitle mb-2 text-gray-500"> + <span data-value="@item.Published" class="timeStampValue">@item.Published</span> + </h6> + + <div class="flex space-x-2"> + <a class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-2 focus:outline-none" asp-page="./Edit" asp-route-slug="@item.Slug">Edit</a> + <a class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-2 focus:outline-none" asp-page="./Delete" asp-route-slug="@item.Slug">Delete</a> + </div> + </div> + </div> +}
\ No newline at end of file diff --git a/CoreWiki/Pages/_comments.cshtml b/CoreWiki/Pages/_comments.cshtml new file mode 100644 index 0000000..3abc114 --- /dev/null +++ b/CoreWiki/Pages/_comments.cshtml @@ -0,0 +1,35 @@ +@using CoreWiki.Models +@model DetailsModel + +<div role="button" @@click="formOpen = !formOpen" class="flex items-center mb-2"> + <span class="text-2xl">Add comment</span> + <svg x-show="!formOpen" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 1.25rem; height: 1.25rem;" class="ms-auto"> + <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/> + </svg> + <svg x-show="formOpen" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 1.25rem; height: 1.25rem;" class="ms-auto"> + <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5"/> + </svg> +</div> +<hr class="mb-2"/> +<div x-cloak x-show="formOpen"> + <script src="~/lib/simplemde/simplemde.min.js"></script> + <script> + let simplemde; + </script> + <partial name="_commentsForm" model="Model"/> +</div> +<div role="button" @@click="commentsOpen = !commentsOpen" class="flex items-center mb-2"> + <span class="text-2xl">Comments</span> + <svg x-show="!commentsOpen" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 1.25rem; height: 1.25rem;" class="ml-auto"> + <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5"/> + </svg> + <svg x-show="commentsOpen" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 1.25rem; height: 1.25rem;" class="ml-auto"> + <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 15.75 7.5-7.5 7.5 7.5"/> + </svg> +</div> +<hr/> +<div x-cloak x-show="commentsOpen"> + <div hx-get hx-trigger="commentsChanged from:body" hx-page="Details" hx-route-slug="@Model.Article.Slug" class="d-flex flex-column gap-3 mt-2"> + <partial name="_commentsList" model="@(new ArticleCommentListViewModel {Comments = Model.Article.Comments, Slug = Model.Article.Slug, PageNumber = 1})"/> + </div> +</div>
\ No newline at end of file diff --git a/CoreWiki/Pages/_commentsForm.cshtml b/CoreWiki/Pages/_commentsForm.cshtml new file mode 100644 index 0000000..f87da25 --- /dev/null +++ b/CoreWiki/Pages/_commentsForm.cshtml @@ -0,0 +1,32 @@ +@using Htmx.TagHelpers +@using Microsoft.AspNetCore.Mvc.TagHelpers +@model DetailsModel +<form + hx-post + hx-page="Details" + hx-route-slug="@Model.Article.Slug" + hx-swap="outerHTML" + class="col-12 mb-3" + method="post"> + <div class="mb-2 flex w-full gap-3"> + <div class="flex-1 min-w-0"> + <label class="block mb-2 text-sm font-medium text-gray-900" asp-for="Comment.DisplayName"></label> + <input class="block bg-gray-50 border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500 text-sm p-2 w-full" asp-for="Comment.DisplayName"/> + <span asp-validation-for="Comment.DisplayName" class="text-danger"></span> + </div> + <div class="flex-1 min-w-0"> + <label class="block mb-2 text-sm font-medium text-gray-900" asp-for="Comment.EMail"></label> + <input class="block bg-gray-50 border border-gray-300 rounded focus:ring-blue-500 focus:border-blue-500 text-sm p-2 w-full" asp-for="Comment.EMail"/> + <span asp-validation-for="Comment.EMail" class="text-danger"></span> + </div> + </div> + <div class="form-group"> + <label asp-for="Comment.Content" class="block mb-2 text-sm font-medium text-gray-900"></label> + <textarea id="contentInput" asp-for="Comment.Content"></textarea> + <span asp-validation-for="Comment.Content" class="text-danger"></span> + </div> + <input type="submit" class="text-white bg-blue-500 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded text-sm px-3.5 py-2 focus:outline-none" value="Submit"/> +</form> +<script> + simplemde = new SimpleMDE({ element: document.getElementById("contentInput"), forceSync: true }); +</script>
\ No newline at end of file diff --git a/CoreWiki/Pages/_commentsList.cshtml b/CoreWiki/Pages/_commentsList.cshtml new file mode 100644 index 0000000..ba0eb85 --- /dev/null +++ b/CoreWiki/Pages/_commentsList.cshtml @@ -0,0 +1,25 @@ +@model CoreWiki.Models.ArticleCommentListViewModel + +@foreach (var item in Model.Comments) +{ + <div class="flex flex-col border rounded mb-3"> + <div class="bg-gray-50 p-2 border-b flex items-center"> + <img class="me-2" width="40" height="40" src="https://gravatar.com/avatar/@item.GravatarHash?d=identicon" alt="avatar"/> + <div class="d-flex flex-column"> + <span class="text-blue-600">@item.DisplayName</span> + <div class="flex"> + <span class="text-sm text-gray-500">Commented on <span data-value="@item.Submitted" class="timeStampValue text-muted">@item.Submitted</span></span> + </div> + </div> + </div> + <div class="py-4 px-2"> + <markdown>@item.Content</markdown> + </div> + </div> +} +@if (Model.Comments.Count() == 10) +{ + <div class="h-100 align-items-center d-flex" hx-get hx-page="Details" hx-route-slug="@Model.Slug" hx-route-pageNumber="@(Model.PageNumber + 1)" hx-swap="outerHTML" hx-trigger="intersect once"> + <div id="spinner" class="htmx-indicator spinner-border"></div> + </div> +}
\ No newline at end of file |
