summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.idea/.idea.CoreWiki/.idea/.name1
-rw-r--r--.idea/.idea.CoreWiki/.idea/efCoreCommonOptions.xml15
-rw-r--r--.idea/.idea.CoreWiki/.idea/efCoreDialogsState.xml14
-rw-r--r--.idea/.idea.CoreWiki/.idea/vcs.xml6
-rw-r--r--CoreWiki/Migrations/20231222134125_Initial.Designer.cs (renamed from CoreWiki/Migrations/20230209093124_initial.Designer.cs)25
-rw-r--r--CoreWiki/Migrations/20231222134125_Initial.cs (renamed from CoreWiki/Migrations/20230209093124_initial.cs)12
-rw-r--r--CoreWiki/Migrations/ApplicationDbContextModelSnapshot.cs21
-rw-r--r--CoreWiki/Models/ApplicationDbContext.cs9
-rw-r--r--CoreWiki/Models/Article.cs13
-rw-r--r--CoreWiki/Pages/All.cshtml48
-rw-r--r--CoreWiki/Pages/All.cshtml.cs39
-rw-r--r--CoreWiki/Pages/Create.cshtml4
-rw-r--r--CoreWiki/Pages/Create.cshtml.cs14
-rw-r--r--CoreWiki/Pages/Delete.cshtml6
-rw-r--r--CoreWiki/Pages/Delete.cshtml.cs33
-rw-r--r--CoreWiki/Pages/Details.cshtml8
-rw-r--r--CoreWiki/Pages/Details.cshtml.cs23
-rw-r--r--CoreWiki/Pages/Edit.cshtml12
-rw-r--r--CoreWiki/Pages/Edit.cshtml.cs17
-rw-r--r--CoreWiki/Pages/LatestChanges.cshtml6
-rw-r--r--CoreWiki/Pages/Shared/_Layout.cshtml15
-rw-r--r--CoreWiki/Program.cs14
-rw-r--r--CoreWiki/Utils/SafeUrl.cs104
-rw-r--r--CoreWiki/wiki.dbbin20480 -> 20480 bytes
-rw-r--r--CoreWiki/wiki.db-shmbin32768 -> 0 bytes
-rw-r--r--CoreWiki/wiki.db-walbin4152 -> 0 bytes
-rw-r--r--CoreWiki/wwwroot/js/site.js8
-rw-r--r--package-lock.json6
28 files changed, 376 insertions, 97 deletions
diff --git a/.idea/.idea.CoreWiki/.idea/.name b/.idea/.idea.CoreWiki/.idea/.name
new file mode 100644
index 0000000..5329c0b
--- /dev/null
+++ b/.idea/.idea.CoreWiki/.idea/.name
@@ -0,0 +1 @@
+CoreWiki \ No newline at end of file
diff --git a/.idea/.idea.CoreWiki/.idea/efCoreCommonOptions.xml b/.idea/.idea.CoreWiki/.idea/efCoreCommonOptions.xml
new file mode 100644
index 0000000..a92379a
--- /dev/null
+++ b/.idea/.idea.CoreWiki/.idea/efCoreCommonOptions.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="EfCoreCommonOptions">
+ <option name="migrationsToStartupProjects">
+ <map>
+ <entry key="3cd4853b-7d70-415b-a4a0-22ceea1860e7" value="3cd4853b-7d70-415b-a4a0-22ceea1860e7" />
+ </map>
+ </option>
+ <option name="startupToMigrationsProjects">
+ <map>
+ <entry key="3cd4853b-7d70-415b-a4a0-22ceea1860e7" value="3cd4853b-7d70-415b-a4a0-22ceea1860e7" />
+ </map>
+ </option>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/.idea.CoreWiki/.idea/efCoreDialogsState.xml b/.idea/.idea.CoreWiki/.idea/efCoreDialogsState.xml
new file mode 100644
index 0000000..e4d84e3
--- /dev/null
+++ b/.idea/.idea.CoreWiki/.idea/efCoreDialogsState.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="EfCoreDialogsState">
+ <option name="keyValueStorage">
+ <map>
+ <entry key="Common:3cd4853b-7d70-415b-a4a0-22ceea1860e7:dbContext" value="CoreWiki.Models.ApplicationDbContext" />
+ <entry key="Common:buildConfiguration" value="Debug" />
+ <entry key="Common:enableDiagnosticLogging" value="false" />
+ <entry key="Common:noBuild" value="false" />
+ <entry key="Common:outputFolder" value="Migrations" />
+ </map>
+ </option>
+ </component>
+</project> \ No newline at end of file
diff --git a/.idea/.idea.CoreWiki/.idea/vcs.xml b/.idea/.idea.CoreWiki/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/.idea.CoreWiki/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project> \ No newline at end of file
diff --git a/CoreWiki/Migrations/20230209093124_initial.Designer.cs b/CoreWiki/Migrations/20231222134125_Initial.Designer.cs
index 11af7b7..8c6556e 100644
--- a/CoreWiki/Migrations/20230209093124_initial.Designer.cs
+++ b/CoreWiki/Migrations/20231222134125_Initial.Designer.cs
@@ -11,8 +11,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace CoreWiki.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
- [Migration("20230209093124_initial")]
- partial class initial
+ [Migration("20231222134125_Initial")]
+ partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
@@ -22,19 +22,34 @@ namespace CoreWiki.Migrations
modelBuilder.Entity("CoreWiki.Models.Article", b =>
{
- b.Property<string>("Topic")
+ b.Property<string>("Slug")
.HasColumnType("TEXT");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
- b.Property<DateTime>("Published")
+ b.Property<DateTime>("PublishedDateTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("Published");
+
+ b.Property<string>("Topic")
+ .IsRequired()
+ .HasMaxLength(100)
.HasColumnType("TEXT");
- b.HasKey("Topic");
+ b.HasKey("Slug");
b.ToTable("Articles");
+
+ b.HasData(
+ new
+ {
+ Slug = "home-page",
+ Content = "Welcome to your new CoreWiki installation",
+ PublishedDateTime = new DateTime(2023, 12, 22, 13, 41, 24, 978, DateTimeKind.Utc).AddTicks(110),
+ Topic = "Home Page"
+ });
});
#pragma warning restore 612, 618
}
diff --git a/CoreWiki/Migrations/20230209093124_initial.cs b/CoreWiki/Migrations/20231222134125_Initial.cs
index 84a7453..80c0c6e 100644
--- a/CoreWiki/Migrations/20230209093124_initial.cs
+++ b/CoreWiki/Migrations/20231222134125_Initial.cs
@@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
namespace CoreWiki.Migrations
{
/// <inheritdoc />
- public partial class initial : Migration
+ public partial class Initial : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
@@ -15,14 +15,20 @@ namespace CoreWiki.Migrations
name: "Articles",
columns: table => new
{
- Topic = table.Column<string>(type: "TEXT", nullable: false),
+ Slug = table.Column<string>(type: "TEXT", nullable: false),
+ Topic = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
Published = table.Column<DateTime>(type: "TEXT", nullable: false),
Content = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
- table.PrimaryKey("PK_Articles", x => x.Topic);
+ table.PrimaryKey("PK_Articles", x => x.Slug);
});
+
+ migrationBuilder.InsertData(
+ table: "Articles",
+ columns: new[] { "Slug", "Content", "Published", "Topic" },
+ values: new object[] { "home-page", "Welcome to your new CoreWiki installation", new DateTime(2023, 12, 22, 13, 41, 24, 978, DateTimeKind.Utc).AddTicks(110), "Home Page" });
}
/// <inheritdoc />
diff --git a/CoreWiki/Migrations/ApplicationDbContextModelSnapshot.cs b/CoreWiki/Migrations/ApplicationDbContextModelSnapshot.cs
index fb063f5..4808c70 100644
--- a/CoreWiki/Migrations/ApplicationDbContextModelSnapshot.cs
+++ b/CoreWiki/Migrations/ApplicationDbContextModelSnapshot.cs
@@ -19,19 +19,34 @@ namespace CoreWiki.Migrations
modelBuilder.Entity("CoreWiki.Models.Article", b =>
{
- b.Property<string>("Topic")
+ b.Property<string>("Slug")
.HasColumnType("TEXT");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
- b.Property<DateTime>("Published")
+ b.Property<DateTime>("PublishedDateTime")
+ .HasColumnType("TEXT")
+ .HasColumnName("Published");
+
+ b.Property<string>("Topic")
+ .IsRequired()
+ .HasMaxLength(100)
.HasColumnType("TEXT");
- b.HasKey("Topic");
+ b.HasKey("Slug");
b.ToTable("Articles");
+
+ b.HasData(
+ new
+ {
+ Slug = "home-page",
+ Content = "Welcome to your new CoreWiki installation",
+ PublishedDateTime = new DateTime(2023, 12, 22, 13, 41, 24, 978, DateTimeKind.Utc).AddTicks(110),
+ Topic = "Home Page"
+ });
});
#pragma warning restore 612, 618
}
diff --git a/CoreWiki/Models/ApplicationDbContext.cs b/CoreWiki/Models/ApplicationDbContext.cs
index 48b5dcf..38c3787 100644
--- a/CoreWiki/Models/ApplicationDbContext.cs
+++ b/CoreWiki/Models/ApplicationDbContext.cs
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
+using NodaTime;
namespace CoreWiki.Models;
@@ -8,7 +9,13 @@ public class ApplicationDbContext : DbContext
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
-
}
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ modelBuilder.Entity<Article>().HasData(
+ new Article { Topic = "Home Page", Slug = "home-page", Content = "Welcome to your new CoreWiki installation" }
+ );
+ }
} \ No newline at end of file
diff --git a/CoreWiki/Models/Article.cs b/CoreWiki/Models/Article.cs
index 53c2706..b989573 100644
--- a/CoreWiki/Models/Article.cs
+++ b/CoreWiki/Models/Article.cs
@@ -2,23 +2,32 @@ using NodaTime;
using NodaTime.Extensions;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
+using CoreWiki.Utils;
namespace CoreWiki.Models;
public class Article
{
- [Required, Key]
+ [Key]
+ public string Slug { get; set; }
+
+ [Required, MaxLength(100)]
public string Topic { get; set; }
+
[NotMapped]
public Instant Published { get; set; } = SystemClock.Instance.GetCurrentInstant();
+
[Obsolete("This property is only for serialization")]
[DataType(DataType.DateTime)]
[Column("Published")]
+ [Required]
public DateTime PublishedDateTime
{
get => Published.ToDateTimeUtc();
set => Published = DateTime.SpecifyKind(value, DateTimeKind.Utc).ToInstant();
}
+
[DataType(DataType.MultilineText)]
- public string Content { get; set; }
+ [Required]
+ public string Content { get; set; } = default!;
} \ No newline at end of file
diff --git a/CoreWiki/Pages/All.cshtml b/CoreWiki/Pages/All.cshtml
new file mode 100644
index 0000000..ca06cb5
--- /dev/null
+++ b/CoreWiki/Pages/All.cshtml
@@ -0,0 +1,48 @@
+@page
+@model CoreWiki.Pages.All
+
+@{
+ ViewData["Title"] = "All articles";
+}
+
+<h2>All articles</h2>
+
+@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>
+
+ <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">
+ @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>
+ }
+ </ul>
+ </nav>
+ <form class="ms-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()">
+ <option value="2">2</option>
+ <option value="10">10</option>
+ <option value="25">25</option>
+ <option value="50">50</option>
+ </select>
+ </form>
+</div> \ No newline at end of file
diff --git a/CoreWiki/Pages/All.cshtml.cs b/CoreWiki/Pages/All.cshtml.cs
new file mode 100644
index 0000000..c41e216
--- /dev/null
+++ b/CoreWiki/Pages/All.cshtml.cs
@@ -0,0 +1,39 @@
+using CoreWiki.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Internal;
+
+namespace CoreWiki.Pages;
+
+public class All : PageModel
+{
+ private readonly ApplicationDbContext _context;
+
+ [BindProperty(SupportsGet = true)]
+ public int PageNumber { get; set; } = 1;
+ [BindProperty(SupportsGet = true)]
+ public int PageSize { get; set; } = 25;
+ public IEnumerable<Article>? Articles { get; set; }
+ public int TotalPages { get; set; }
+
+ public All(ApplicationDbContext context)
+ {
+ _context = context;
+ }
+
+ public async Task<IActionResult> OnGetAsync()
+ {
+ TotalPages = (int)Math.Ceiling(await _context.Articles.CountAsync() / (float)PageSize);
+ if (PageNumber > TotalPages) PageNumber = 1;
+
+ Articles = await _context.Articles
+ .AsNoTracking()
+ .OrderBy(a => a.PublishedDateTime)
+ .Skip(PageSize * (PageNumber - 1))
+ .Take(PageSize)
+ .ToArrayAsync();
+
+ return Page();
+ }
+} \ No newline at end of file
diff --git a/CoreWiki/Pages/Create.cshtml b/CoreWiki/Pages/Create.cshtml
index fc74317..4abbf11 100644
--- a/CoreWiki/Pages/Create.cshtml
+++ b/CoreWiki/Pages/Create.cshtml
@@ -9,8 +9,8 @@
<h4>Article</h4>
<hr />
-<div class="row">
- <div class="col-md-4">
+<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">
diff --git a/CoreWiki/Pages/Create.cshtml.cs b/CoreWiki/Pages/Create.cshtml.cs
index d89b97b..0bf9327 100644
--- a/CoreWiki/Pages/Create.cshtml.cs
+++ b/CoreWiki/Pages/Create.cshtml.cs
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using CoreWiki.Models;
+using CoreWiki.Utils;
using NodaTime;
namespace CoreWiki.Pages
@@ -30,19 +31,26 @@ namespace CoreWiki.Pages
public Article Article { get; set; } = default!;
- // To protect from overposting attacks, see https://aka.ms/RazorPagesCRUD
public async Task<IActionResult> OnPostAsync()
{
- if (!ModelState.IsValid || _context.Articles == null || Article == null)
+ ModelState.Remove("Article.Slug");
+ if (!ModelState.IsValid)
{
return Page();
}
+ Article.Slug = SafeUrl.Create(true, Article.Topic);
Article.Published = _clock.GetCurrentInstant();
+ if (_context.Articles.Any(a => a.Slug == Article.Slug))
+ {
+ ModelState.AddModelError("Article.Topic", "Article already exists");
+ return Page();
+ }
+
_context.Articles.Add(Article);
await _context.SaveChangesAsync();
- return Redirect($"/{Article.Topic}");
+ return Redirect($"/{Article.Slug}");
}
}
}
diff --git a/CoreWiki/Pages/Delete.cshtml b/CoreWiki/Pages/Delete.cshtml
index ec40822..0b680b8 100644
--- a/CoreWiki/Pages/Delete.cshtml
+++ b/CoreWiki/Pages/Delete.cshtml
@@ -1,4 +1,4 @@
-@page
+@page "/Delete/{slug}"
@model CoreWiki.Pages.DeleteModel
@{
@@ -27,8 +27,8 @@
</dl>
<form method="post">
- <input type="hidden" asp-for="Article.Topic" />
+ <input type="hidden" asp-for="Article.Slug" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
- <a asp-page="./Index">Back to List</a>
+ <a href="/">Back to Home Page</a>
</form>
</div>
diff --git a/CoreWiki/Pages/Delete.cshtml.cs b/CoreWiki/Pages/Delete.cshtml.cs
index 9de563e..584dc48 100644
--- a/CoreWiki/Pages/Delete.cshtml.cs
+++ b/CoreWiki/Pages/Delete.cshtml.cs
@@ -19,42 +19,39 @@ namespace CoreWiki.Pages
}
[BindProperty]
- public Article Article { get; set; } = default!;
+ public Article Article { get; set; } = default!;
- public async Task<IActionResult> OnGetAsync(string id)
+ public async Task<IActionResult> OnGetAsync(string slug)
{
- if (id == null || _context.Articles == null)
+ if (slug == "home-page")
{
return NotFound();
}
-
- var article = await _context.Articles.FirstOrDefaultAsync(m => m.Topic == id);
+
+ var article = await _context.Articles.FirstOrDefaultAsync(a => a.Slug == slug);
if (article == null)
{
return NotFound();
}
- else
- {
- Article = article;
- }
+
+ Article = article;
return Page();
}
- public async Task<IActionResult> OnPostAsync(string id)
+ public async Task<IActionResult> OnPostAsync(string slug)
{
- if (id == null || _context.Articles == null)
+ if (slug == "home-page")
{
return NotFound();
}
- var article = await _context.Articles.FindAsync(id);
+
+ var article = await _context.Articles.FirstOrDefaultAsync(a => a.Slug == slug);
- if (article != null)
- {
- Article = article;
- _context.Articles.Remove(Article);
- await _context.SaveChangesAsync();
- }
+ if (article == null) return NotFound();
+ Article = article;
+ _context.Articles.Remove(Article);
+ await _context.SaveChangesAsync();
return Redirect("/");
}
diff --git a/CoreWiki/Pages/Details.cshtml b/CoreWiki/Pages/Details.cshtml
index bf1a616..9f48f9f 100644
--- a/CoreWiki/Pages/Details.cshtml
+++ b/CoreWiki/Pages/Details.cshtml
@@ -1,8 +1,8 @@
-@page "{topicName?}"
+@page "/{slug?}"
@model CoreWiki.Pages.DetailsModel
@{
- ViewData["Title"] = "Details";
+ ViewData["Title"] = Model.Article.Topic;
}
<h1>@Model.Article.Topic</h1>
@@ -11,8 +11,8 @@
<markdown markdown="Article.Content"/>
<div>
- <a asp-page="./Edit" asp-route-id="@Model.Article?.Topic">Edit</a>
- @if (Model.Article.Topic != "HomePage")
+ <a 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>
}
diff --git a/CoreWiki/Pages/Details.cshtml.cs b/CoreWiki/Pages/Details.cshtml.cs
index 25ff6c0..e4100ac 100644
--- a/CoreWiki/Pages/Details.cshtml.cs
+++ b/CoreWiki/Pages/Details.cshtml.cs
@@ -11,33 +11,26 @@ namespace CoreWiki.Pages
{
public class DetailsModel : PageModel
{
- private readonly CoreWiki.Models.ApplicationDbContext _context;
+ private readonly ApplicationDbContext _context;
- public DetailsModel(CoreWiki.Models.ApplicationDbContext context)
+ public DetailsModel(ApplicationDbContext context)
{
_context = context;
}
- public Article Article { get; set; } = default!;
+ public Article Article { get; set; } = default!;
- public async Task<IActionResult> OnGetAsync(string? topicName)
+ public async Task<IActionResult> OnGetAsync(string? slug)
{
- topicName ??= "HomePage";
-
- if (_context.Articles == null)
- {
- return NotFound();
- }
+ slug ??= "home-page";
- var article = await _context.Articles.FirstOrDefaultAsync(m => m.Topic == topicName);
+ var article = await _context.Articles.FirstOrDefaultAsync(a => a.Slug == slug);
if (article == null)
{
return NotFound();
}
- else
- {
- Article = article;
- }
+
+ Article = article;
return Page();
}
}
diff --git a/CoreWiki/Pages/Edit.cshtml b/CoreWiki/Pages/Edit.cshtml
index 7609348..8793034 100644
--- a/CoreWiki/Pages/Edit.cshtml
+++ b/CoreWiki/Pages/Edit.cshtml
@@ -1,4 +1,4 @@
-@page
+@page "/Edit/{slug}"
@using CoreWiki.Models
@model CoreWiki.Pages.EditModel
@@ -8,17 +8,15 @@
<h1>Edit</h1>
-<h3>@Model.Article.Topic</h3>
-<hr />
+<br />
<div class="row">
- <div class="col-md-4">
+ <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" />
<div class="form-group">
- <label asp-for="Article.Published" class="control-label"></label>
- <input disabled asp-for="Article.Published" class="form-control" />
- <span asp-validation-for="Article.Published" class="text-danger"></span>
+ <h3>@Model.Article.Topic</h3>
</div>
<div class="form-group">
<label asp-for="Article.Content" class="control-label"></label>
diff --git a/CoreWiki/Pages/Edit.cshtml.cs b/CoreWiki/Pages/Edit.cshtml.cs
index e3b11c2..46920cb 100644
--- a/CoreWiki/Pages/Edit.cshtml.cs
+++ b/CoreWiki/Pages/Edit.cshtml.cs
@@ -25,14 +25,9 @@ namespace CoreWiki.Pages
[BindProperty]
public Article Article { get; set; } = default!;
- public async Task<IActionResult> OnGetAsync(string id)
+ public async Task<IActionResult> OnGetAsync(string slug)
{
- if (id == null || _context.Articles == null)
- {
- return NotFound();
- }
-
- var article = await _context.Articles.FirstOrDefaultAsync(m => m.Topic == id);
+ var article = await _context.Articles.FirstOrDefaultAsync(m => m.Slug == slug);
if (article == null)
{
return NotFound();
@@ -40,9 +35,7 @@ namespace CoreWiki.Pages
Article = article;
return Page();
}
-
- // To protect from overposting attacks, enable the specific properties you want to bind to.
- // For more details, see https://aka.ms/RazorPagesCRUD.
+
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
@@ -69,12 +62,12 @@ namespace CoreWiki.Pages
}
}
- return Redirect($"./{(Article.Topic == "HomePage" ? "" : Article.Topic)}");
+ return Redirect($"/{(Article.Slug == "home-page" ? "" : Article.Slug)}");
}
private bool ArticleExists(string id)
{
- return (_context.Articles?.Any(e => e.Topic == id)).GetValueOrDefault();
+ return (_context.Articles?.Any(e => e.Slug == id)).GetValueOrDefault();
}
}
}
diff --git a/CoreWiki/Pages/LatestChanges.cshtml b/CoreWiki/Pages/LatestChanges.cshtml
index 390c8ab..677887b 100644
--- a/CoreWiki/Pages/LatestChanges.cshtml
+++ b/CoreWiki/Pages/LatestChanges.cshtml
@@ -15,11 +15,11 @@
{
<div class="card border-primary m-1">
<div class="card-body">
- <h3 class="card-title"><a href="~/@item.Topic">@item.Topic</a></h3>
+ <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-id="@item.Topic">Edit</a>
- <a class="card-link" asp-page="./Delete" asp-route-id="@item.Topic">Delete</a>
+ <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
diff --git a/CoreWiki/Pages/Shared/_Layout.cshtml b/CoreWiki/Pages/Shared/_Layout.cshtml
index dbe5e47..ac74eba 100644
--- a/CoreWiki/Pages/Shared/_Layout.cshtml
+++ b/CoreWiki/Pages/Shared/_Layout.cshtml
@@ -7,14 +7,14 @@
<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="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"/>
@await RenderSectionAsync("Styles", required: false)
</head>
<body>
<header>
- <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-dark bg-dark border-bottom box-shadow mb-3">
+ <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-area="" asp-page="/Index">CoreWiki</a>
+ <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>
@@ -22,13 +22,16 @@
<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 text-light" href="~/Index">Home</a>
+ <a class="nav-link" asp-page="Details">Home</a>
</li>
<li class="nav-item">
- <a class="nav-link text-light" asp-area="" asp-page="/Create">Create</a>
+ <a class="nav-link" asp-area="" asp-page="Create">Create</a>
</li>
<li class="nav-item">
- <a class="nav-link text-light" href="~/LatestChanges">Latest Changes</a>
+ <a class="nav-link" asp-page="All">All Articles</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link" asp-page="LatestChanges">Latest Changes</a>
</li>
</ul>
</div>
diff --git a/CoreWiki/Program.cs b/CoreWiki/Program.cs
index d31ad67..2a26753 100644
--- a/CoreWiki/Program.cs
+++ b/CoreWiki/Program.cs
@@ -8,18 +8,20 @@ var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
-builder.Services.AddEntityFrameworkSqlite()
+builder.Services
.AddDbContext<ApplicationDbContext>(options => options.UseSqlite("Data Source=./wiki.db"));
-
+builder.Services.AddRouting(options =>
+{
+ options.LowercaseUrls = true;
+});
builder.Services.AddSingleton<IClock>(SystemClock.Instance);
builder.Services.AddMvc().AddRazorPagesOptions(options =>
{
- options.Conventions.AddPageRoute("/Details", "{topicName?}");
- options.Conventions.AddPageRoute("/Details", "Index");
+ options.Conventions.AddPageRoute("/Details", "/");
});
- var app = builder.Build();
+var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
@@ -40,4 +42,4 @@ app.UseAuthorization();
app.MapRazorPages();
-app.Run();
+app.Run(); \ No newline at end of file
diff --git a/CoreWiki/Utils/SafeUrl.cs b/CoreWiki/Utils/SafeUrl.cs
new file mode 100644
index 0000000..b2de803
--- /dev/null
+++ b/CoreWiki/Utils/SafeUrl.cs
@@ -0,0 +1,104 @@
+using System.Text;
+
+namespace CoreWiki.Utils;
+
+public class SafeUrl
+{
+ public static string Create(bool toLower, params string[] values)
+ {
+ return Create(toLower, String.Join("-", values));
+ }
+
+ public static string Create(bool toLower, string value)
+ {
+ var normalised = value.Normalize(NormalizationForm.FormKD);
+
+ const int maxlen = 80;
+ int len = normalised.Length;
+ bool prevDash = false;
+ var sb = new StringBuilder(len);
+ char c;
+
+ for (int i = 0; i < len; i++)
+ {
+ c = normalised[i];
+ if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'))
+ {
+ if (prevDash)
+ {
+ sb.Append('-');
+ prevDash = false;
+ }
+ sb.Append(c);
+ }
+ else if (c >= 'A' && c <= 'Z')
+ {
+ if (prevDash)
+ {
+ sb.Append('-');
+ prevDash = false;
+ }
+ // Tricky way to convert to lowercase
+ if (toLower)
+ sb.Append((char)(c | 32));
+ else
+ sb.Append(c);
+ }
+ else if (c == ' ' || c == ',' || c == '.' || c == '/' || c == '\\' || c == '-' || c == '_' || c == '=')
+ {
+ if (!prevDash && sb.Length > 0)
+ {
+ prevDash = true;
+ }
+ }
+ else
+ {
+ string swap = ConvertEdgeCases(c, toLower);
+
+ if (swap != null)
+ {
+ if (prevDash)
+ {
+ sb.Append('-');
+ prevDash = false;
+ }
+ sb.Append(swap);
+ }
+ }
+
+ if (sb.Length == maxlen)
+ break;
+ }
+ return sb.ToString();
+ }
+
+ static string ConvertEdgeCases(char c, bool toLower)
+ {
+ string swap = null;
+ switch (c)
+ {
+ case 'ı':
+ swap = "i";
+ break;
+ case 'ł':
+ swap = "l";
+ break;
+ case 'Ł':
+ swap = toLower ? "l" : "L";
+ break;
+ case 'đ':
+ swap = "d";
+ break;
+ case 'ß':
+ swap = "ss";
+ break;
+ case 'ø':
+ swap = "o";
+ break;
+ case 'Þ':
+ swap = "th";
+ break;
+ }
+ return swap;
+ }
+} \ No newline at end of file
diff --git a/CoreWiki/wiki.db b/CoreWiki/wiki.db
index 4722ef3..fe3d8a0 100644
--- a/CoreWiki/wiki.db
+++ b/CoreWiki/wiki.db
Binary files differ
diff --git a/CoreWiki/wiki.db-shm b/CoreWiki/wiki.db-shm
deleted file mode 100644
index c7890fa..0000000
--- a/CoreWiki/wiki.db-shm
+++ /dev/null
Binary files differ
diff --git a/CoreWiki/wiki.db-wal b/CoreWiki/wiki.db-wal
deleted file mode 100644
index 2cce672..0000000
--- a/CoreWiki/wiki.db-wal
+++ /dev/null
Binary files differ
diff --git a/CoreWiki/wwwroot/js/site.js b/CoreWiki/wwwroot/js/site.js
index 02fb148..244a5c5 100644
--- a/CoreWiki/wwwroot/js/site.js
+++ b/CoreWiki/wwwroot/js/site.js
@@ -3,10 +3,10 @@
// Write your JavaScript code.
(function () {
- var timeStamps = document.querySelectorAll(".timeStampValue");
- for (var ts of timeStamps) {
- var thisTimeStamp = ts.getAttribute("data-value");
- var date = new Date(thisTimeStamp);
+ 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');
}
})(); \ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..06b73c7
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "corewiki",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}