Compare commits
No commits in common. "a2c2db053ee54bf42afc643474f66ad9653068ef" and "8ffa4a39fec0e8811ee63ad03bebdff2c6b7f64b" have entirely different histories.
a2c2db053e
...
8ffa4a39fe
|
|
@ -5,11 +5,9 @@ namespace FastBlog.Core.Abstractions.Repositories.Blogs;
|
||||||
|
|
||||||
public interface IBlogMetaRepository
|
public interface IBlogMetaRepository
|
||||||
{
|
{
|
||||||
Task<BlogMeta?> GetPublished(string? slug);
|
|
||||||
Task<BlogMeta?> Get(string? slug);
|
Task<BlogMeta?> Get(string? slug);
|
||||||
Task<BlogMeta?> GetForEdit(int id);
|
Task<BlogMeta?> GetForEdit(int id);
|
||||||
Task<bool> Delete(int id);
|
Task<bool> Delete(int id);
|
||||||
Task<Result<BlogMeta, BusinessError>> Add(BlogMeta meta);
|
Task<Result<BlogMeta, BusinessError>> Add(BlogMeta meta);
|
||||||
Task<Result<BlogMeta, BusinessError>> Update(BlogMeta meta);
|
Task<Result<BlogMeta, BusinessError>> Update(BlogMeta meta);
|
||||||
Task<PagedResponse<BlogMeta>> List(BlogFilter filter, PagedRequest request);
|
|
||||||
}
|
}
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||||
<PackageReference Include="Dapper.SqlBuilder" Version="2.0.78" />
|
|
||||||
<PackageReference Include="FluentMigrator" Version="5.2.0" />
|
<PackageReference Include="FluentMigrator" Version="5.2.0" />
|
||||||
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="5.2.0" />
|
<PackageReference Include="FluentMigrator.Runner.SQLite" Version="5.2.0" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
|
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.8" />
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
namespace FastBlog.Core.Models.Blogs;
|
|
||||||
|
|
||||||
public sealed record BlogFilter(DateTime? From, DateTime? To, string? Search);
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Text;
|
using Dapper;
|
||||||
using Dapper;
|
|
||||||
using FastBlog.Core.Abstractions.Repositories.Blogs;
|
using FastBlog.Core.Abstractions.Repositories.Blogs;
|
||||||
using FastBlog.Core.Db;
|
using FastBlog.Core.Db;
|
||||||
using FastBlog.Core.Models;
|
using FastBlog.Core.Models;
|
||||||
|
|
@ -10,7 +9,7 @@ namespace FastBlog.Core.Repositories;
|
||||||
|
|
||||||
public sealed class BlogMetaRepository(SqliteConnectionFactory connectionFactory) : IBlogMetaRepository
|
public sealed class BlogMetaRepository(SqliteConnectionFactory connectionFactory) : IBlogMetaRepository
|
||||||
{
|
{
|
||||||
public async Task<BlogMeta?> GetPublished(string? slug)
|
public async Task<BlogMeta?> Get(string? slug)
|
||||||
{
|
{
|
||||||
using var connection = connectionFactory.Create();
|
using var connection = connectionFactory.Create();
|
||||||
|
|
||||||
|
|
@ -30,27 +29,6 @@ public sealed class BlogMetaRepository(SqliteConnectionFactory connectionFactory
|
||||||
return await connection.QueryFirstOrDefaultAsync<BlogMeta>(sqlNull);
|
return await connection.QueryFirstOrDefaultAsync<BlogMeta>(sqlNull);
|
||||||
return await connection.QueryFirstOrDefaultAsync<BlogMeta>(sql, new { slug });
|
return await connection.QueryFirstOrDefaultAsync<BlogMeta>(sql, new { slug });
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BlogMeta?> Get(string? slug)
|
|
||||||
{
|
|
||||||
using var connection = connectionFactory.Create();
|
|
||||||
|
|
||||||
const string sqlNull = """
|
|
||||||
select * from Blogs
|
|
||||||
where slug is null
|
|
||||||
""";
|
|
||||||
|
|
||||||
const string sql = """
|
|
||||||
select * from Blogs
|
|
||||||
where (slug = @slug or id = cast(@slug as integer))
|
|
||||||
""";
|
|
||||||
|
|
||||||
if (slug is null)
|
|
||||||
return await connection.QueryFirstOrDefaultAsync<BlogMeta>(sqlNull);
|
|
||||||
|
|
||||||
return await connection.QueryFirstOrDefaultAsync<BlogMeta>(sql, new { slug });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<BlogMeta?> GetForEdit(int id)
|
public async Task<BlogMeta?> GetForEdit(int id)
|
||||||
{
|
{
|
||||||
|
|
@ -120,46 +98,4 @@ public sealed class BlogMetaRepository(SqliteConnectionFactory connectionFactory
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PagedResponse<BlogMeta>> List(BlogFilter filter, PagedRequest request)
|
|
||||||
{
|
|
||||||
using var connection = connectionFactory.Create();
|
|
||||||
|
|
||||||
var parameters = new DynamicParameters();
|
|
||||||
|
|
||||||
parameters.Add("@Amount", request.Amount);
|
|
||||||
parameters.Add("@Offset", request.Offset);
|
|
||||||
|
|
||||||
var sql = new StringBuilder("""
|
|
||||||
select COUNT(*) from Files;
|
|
||||||
select * from Blogs ""
|
|
||||||
""");
|
|
||||||
|
|
||||||
if (filter.To.HasValue)
|
|
||||||
{
|
|
||||||
sql.AppendLine("where CreatedAt >= @To");
|
|
||||||
parameters.Add("@To", filter.To.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.From.HasValue)
|
|
||||||
{
|
|
||||||
sql.AppendLine("where CreatedAt <= @From");
|
|
||||||
parameters.Add("@From", filter.From.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filter.Search is not null)
|
|
||||||
{
|
|
||||||
sql.AppendLine("where Title like @Search");
|
|
||||||
parameters.Add("@Search", $"%{filter.Search}%");
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.AppendLine("order by CreatedAt desc");
|
|
||||||
sql.AppendLine("limit @Amount offset @Offset;");
|
|
||||||
|
|
||||||
await using var multi = await connection.QueryMultipleAsync(sql.ToString(), parameters);
|
|
||||||
var total = await multi.ReadFirstAsync<long>();
|
|
||||||
var result = (await multi.ReadAsync<BlogMeta>()).ToArray();
|
|
||||||
|
|
||||||
return PagedResponse<BlogMeta>.Create(total, request.Amount, request.Offset, result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,26 +6,6 @@ namespace FastBlog.Core.Services;
|
||||||
|
|
||||||
public sealed class BlogService(IBlogMetaRepository metaRepository, IBlogFileRepository blogFileRepository)
|
public sealed class BlogService(IBlogMetaRepository metaRepository, IBlogFileRepository blogFileRepository)
|
||||||
{
|
{
|
||||||
public async Task<Blog?> GetPublished(string? slug)
|
|
||||||
{
|
|
||||||
slug = slug?.ToLower();
|
|
||||||
var metaResult = await metaRepository.GetPublished(slug);
|
|
||||||
if (metaResult is null)
|
|
||||||
return null;
|
|
||||||
var sourceResult = await blogFileRepository.Read(metaResult.SourceLocation);
|
|
||||||
|
|
||||||
if (sourceResult is null)
|
|
||||||
{
|
|
||||||
throw new FileNotFoundException("File with metadata cannot be found");
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Blog
|
|
||||||
{
|
|
||||||
Metadata = metaResult,
|
|
||||||
Text = sourceResult
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<Blog?> Get(string? slug)
|
public async Task<Blog?> Get(string? slug)
|
||||||
{
|
{
|
||||||
slug = slug?.ToLower();
|
slug = slug?.ToLower();
|
||||||
|
|
@ -45,8 +25,6 @@ public sealed class BlogService(IBlogMetaRepository metaRepository, IBlogFileRep
|
||||||
Text = sourceResult
|
Text = sourceResult
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<PagedResponse<BlogMeta>> ListMetas(BlogFilter filter, PagedRequest request) => metaRepository.List(filter, request);
|
|
||||||
|
|
||||||
public async Task<Blog?> GetForEdit(int id)
|
public async Task<Blog?> GetForEdit(int id)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using FastBlog.Core.Models;
|
using System.Diagnostics;
|
||||||
|
using FastBlog.Core.Abstractions.Repositories.Blogs;
|
||||||
using FastBlog.Core.Models.Blogs;
|
using FastBlog.Core.Models.Blogs;
|
||||||
using FastBlog.Core.Services;
|
using FastBlog.Core.Services;
|
||||||
using FastBlog.Web.Models;
|
using FastBlog.Web.Models;
|
||||||
|
|
@ -17,7 +18,7 @@ public class BlogsController(BlogService service) : Controller
|
||||||
if (string.IsNullOrWhiteSpace(slug))
|
if (string.IsNullOrWhiteSpace(slug))
|
||||||
slug = null;
|
slug = null;
|
||||||
|
|
||||||
var blog = await service.GetPublished(slug);
|
var blog = await service.Get(slug);
|
||||||
|
|
||||||
if (blog is null)
|
if (blog is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
@ -29,7 +30,7 @@ public class BlogsController(BlogService service) : Controller
|
||||||
[Route("")]
|
[Route("")]
|
||||||
public async Task<IActionResult> Index()
|
public async Task<IActionResult> Index()
|
||||||
{
|
{
|
||||||
var blog = await service.GetPublished(null);
|
var blog = await service.Get(null);
|
||||||
|
|
||||||
if (blog is null)
|
if (blog is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
@ -37,40 +38,6 @@ public class BlogsController(BlogService service) : Controller
|
||||||
return View(blog);
|
return View(blog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
[Route("unpublished/{*slug}")]
|
|
||||||
public async Task<IActionResult> Unpublished(string? slug)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(slug))
|
|
||||||
slug = null;
|
|
||||||
|
|
||||||
var blog = await service.Get(slug);
|
|
||||||
|
|
||||||
if (blog is null)
|
|
||||||
return NotFound();
|
|
||||||
|
|
||||||
return View(blog);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("list/edit")]
|
|
||||||
public async Task<IActionResult> ListEdit(
|
|
||||||
[FromQuery(Name = "amount")] int amount = 25,
|
|
||||||
[FromQuery(Name = "offset")] int offset = 0,
|
|
||||||
[FromQuery(Name = "query")] string? query = null,
|
|
||||||
[FromQuery(Name = "from")] DateTime? from = null,
|
|
||||||
[FromQuery(Name = "to")] DateTime? to = null)
|
|
||||||
{
|
|
||||||
|
|
||||||
if(amount is < 1 or > 100)
|
|
||||||
return BadRequest("Amount must be between 1 and 100");
|
|
||||||
|
|
||||||
if(offset < 0)
|
|
||||||
return BadRequest("Offset must be greater than 0");
|
|
||||||
|
|
||||||
return View(await service.ListMetas(new BlogFilter(from,to, query), new PagedRequest(amount, offset)));
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("edit/{id:int?}")]
|
[Route("edit/{id:int?}")]
|
||||||
public async ValueTask<IActionResult> Edit(int? id)
|
public async ValueTask<IActionResult> Edit(int? id)
|
||||||
|
|
@ -81,21 +48,21 @@ public class BlogsController(BlogService service) : Controller
|
||||||
return View(
|
return View(
|
||||||
new EditBlog
|
new EditBlog
|
||||||
{
|
{
|
||||||
Text = "# My new blog",
|
Text = "# My new blog",
|
||||||
Title = "Blog from " + DateTime.UtcNow.ToString("g"),
|
Title = "Blog from " + DateTime.UtcNow.ToString("g"),
|
||||||
SourceLocation = $"{date:yyyy-MM-dd-HH-mm}_blog.md",
|
SourceLocation = $"{date:yyyy-MM-dd-HH-mm}_blog.md",
|
||||||
CreatedAt = date,
|
CreatedAt = date,
|
||||||
ModifiedAt = date,
|
ModifiedAt = date,
|
||||||
FullWidth = false,
|
FullWidth = false,
|
||||||
ShowDetails = true,
|
ShowDetails = true,
|
||||||
Slug = "blog-" + date.ToString("yyyy-MM-dd-HH-mm"),
|
Slug = "blog-" + date.ToString("yyyy-MM-dd-HH-mm"),
|
||||||
Visible = false
|
Visible = false
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var blog = await service.GetForEdit(id.Value);
|
var blog = await service.GetForEdit(id.Value);
|
||||||
|
|
||||||
if (blog is null)
|
if (blog is null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
|
@ -151,7 +118,7 @@ public class BlogsController(BlogService service) : Controller
|
||||||
|
|
||||||
return Redirect($"/blogs/{editBlog.Slug}");
|
return Redirect($"/blogs/{editBlog.Slug}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("preview")]
|
[Route("preview")]
|
||||||
public IActionResult Preview([FromForm] EditBlog editBlog)
|
public IActionResult Preview([FromForm] EditBlog editBlog)
|
||||||
|
|
@ -174,16 +141,16 @@ public class BlogsController(BlogService service) : Controller
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete]
|
[HttpDelete]
|
||||||
[Route("{id:int}")]
|
[Route("{id:int}")]
|
||||||
public async Task<IActionResult> Delete(int id)
|
public async Task<IActionResult> Delete(int id)
|
||||||
{
|
{
|
||||||
var result = await service.Delete(id);
|
var result = await service.Delete(id);
|
||||||
|
|
||||||
if (!result)
|
if (!result)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
HttpContext.Response.Headers.Append("Hx-Redirect", "/");
|
HttpContext.Response.Headers.Append("Hx-Redirect", "/");
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,4 @@
|
||||||
using System;
|
namespace FastBlog.Web.Models;
|
||||||
|
|
||||||
namespace FastBlog.Web.Models;
|
|
||||||
|
|
||||||
public sealed class EditBlog
|
public sealed class EditBlog
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using FastBlog.Web;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Add services to the container.
|
||||||
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Development)
|
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == Environments.Development)
|
||||||
builder.Services.AddControllersWithViews().AddRazorRuntimeCompilation();
|
builder.Services.AddControllersWithViews().AddRazorRuntimeCompilation();
|
||||||
else
|
else
|
||||||
|
|
@ -13,9 +14,11 @@ builder.Services.Configure<DisplayOptions>(builder.Configuration.GetSection("Dis
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseExceptionHandler("/Home/Error");
|
app.UseExceptionHandler("/Home/Error");
|
||||||
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,31 @@
|
||||||
ViewBag.Title = Model.Metadata.Title;
|
ViewBag.Title = Model.Metadata.Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@await Html.PartialAsync("Blogs/Content", Model)
|
@await Html.PartialAsync("Blogs/BlogContent", Model)
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<hr/>
|
<hr/>
|
||||||
@await Html.PartialAsync("Blogs/ManageCard", Model.Metadata)
|
<article>
|
||||||
</div>
|
<header>
|
||||||
|
<h3>Editor's controls</h3>
|
||||||
|
</header>
|
||||||
|
<body>
|
||||||
|
<div class="grid">
|
||||||
|
<div>
|
||||||
|
<a href="/edit/@Model.Metadata.Id">
|
||||||
|
<button class="btn-fw">
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn-fw secondary" hx-delete="/blogs/@Model.Metadata.Id">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
@using FastBlog.Web.Helpers
|
|
||||||
@model FastBlog.Core.Models.PagedResponse<FastBlog.Core.Models.Blogs.BlogMeta>
|
|
||||||
|
|
||||||
|
|
||||||
@{
|
|
||||||
ViewBag.Title = "Blogs";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="container" id="file-list">
|
|
||||||
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<a href="/edit">
|
|
||||||
<button class="btn-fw secondary">
|
|
||||||
New
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<h3>List of Blogs</h3>
|
|
||||||
</header>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
@if (Model.Data.Length is 0)
|
|
||||||
{
|
|
||||||
<h4>No blogs have been found</h4>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="grid">
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p>Name</p>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p>Published</p>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p>Visible</p>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p>Actions</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@foreach (var blog in Model.Data)
|
|
||||||
{
|
|
||||||
<div class="grid">
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p class="no-margin" style="margin-right: 10px; font-weight: bold">
|
|
||||||
@if (blog.CreatedAt < DateTime.UtcNow)
|
|
||||||
{
|
|
||||||
<a target="_blank" href="/@blog.Slug">@blog.Title</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a class="pico-color-purple-600" target="_blank" href="/unpublished/@blog.Slug">@blog.Title</a>
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="no-margin">
|
|
||||||
@if (blog.CreatedAt < DateTime.UtcNow)
|
|
||||||
{
|
|
||||||
@blog.CreatedAt.ToString("yy-MM-dd HH:mm:ss")
|
|
||||||
;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="pico-color-purple-400">@blog.CreatedAt.ToString("yy-MM-dd HH:mm:ss")</span>
|
|
||||||
;
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="no-margin">
|
|
||||||
<strong>
|
|
||||||
@if (blog.Visible)
|
|
||||||
{
|
|
||||||
<spans>Yes</spans>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="pico-color-yellow">No</span>
|
|
||||||
}
|
|
||||||
</strong>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="no-margin">
|
|
||||||
<a class="pico-color-red" hx-delete="/blogs/@blog.Id"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-target="#file-list">
|
|
||||||
[ Delete ]
|
|
||||||
</a>
|
|
||||||
<a class="pico-color-blue-100" target="_blank" href="/edit/@blog.Id">[ Edit ]</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
}
|
|
||||||
</body>
|
|
||||||
<footer>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
hx-get="/edit-list?offset=@(Model.Offset - 25)"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-target="#file-list"
|
|
||||||
@PropertyHelper.If(Math.Clamp(Model.Offset, 0, Model.Amount) == 0, "disabled")>
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
hx-get="/edit-list?offset=@(Model.Offset + 25)"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-target="#file-list"
|
|
||||||
@PropertyHelper.If(Math.Clamp(Model.Offset, 0, Model.Amount) >= Model.Amount - 25, "disabled")>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
@using FastBlog.Web.Helpers
|
|
||||||
@model FastBlog.Core.Models.PagedResponse<FastBlog.Core.Models.Blogs.BlogMeta>
|
|
||||||
|
|
||||||
|
|
||||||
@{
|
|
||||||
ViewBag.Title = "Blogs";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="container" id="file-list">
|
|
||||||
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<a href="/edit">
|
|
||||||
<button class="btn-fw secondary">
|
|
||||||
New
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<h3>List of Blogs</h3>
|
|
||||||
</header>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
@if (Model.Data.Length is 0)
|
|
||||||
{
|
|
||||||
<h4>No blogs have been found</h4>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="grid">
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p>Name</p>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p>Published</p>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p>Visible</p>
|
|
||||||
</div>
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p>Actions</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@foreach (var blog in Model.Data)
|
|
||||||
{
|
|
||||||
<div class="grid">
|
|
||||||
<div style="display: flex; font-size: 16px">
|
|
||||||
<p class="no-margin" style="margin-right: 10px; font-weight: bold">
|
|
||||||
@if (blog.CreatedAt < DateTime.UtcNow)
|
|
||||||
{
|
|
||||||
<a target="_blank" href="/@blog.Slug">@blog.Title</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a class="pico-color-purple-600" target="_blank" href="/unpublished/@blog.Slug">@blog.Title</a>
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="no-margin">
|
|
||||||
@if (blog.CreatedAt < DateTime.UtcNow)
|
|
||||||
{
|
|
||||||
@blog.CreatedAt.ToString("yy-MM-dd HH:mm:ss")
|
|
||||||
;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="pico-color-purple-400">@blog.CreatedAt.ToString("yy-MM-dd HH:mm:ss")</span>
|
|
||||||
;
|
|
||||||
}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="no-margin">
|
|
||||||
<strong>
|
|
||||||
@if (blog.Visible)
|
|
||||||
{
|
|
||||||
<spans>Yes</spans>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<span class="pico-color-yellow">No</span>
|
|
||||||
}
|
|
||||||
</strong>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p class="no-margin">
|
|
||||||
<a class="pico-color-red" hx-delete="/blogs/@blog.Id"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-target="#file-list">
|
|
||||||
[ Delete ]
|
|
||||||
</a>
|
|
||||||
<a class="pico-color-blue-100" target="_blank" href="/edit/@blog.Id">[ Edit ]</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr/>
|
|
||||||
}
|
|
||||||
</body>
|
|
||||||
<footer>
|
|
||||||
<div>
|
|
||||||
<button
|
|
||||||
hx-get="/edit-list?offset=@(Model.Offset - 25)"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-target="#file-list"
|
|
||||||
@PropertyHelper.If(Math.Clamp(Model.Offset, 0, Model.Amount) == 0, "disabled")>
|
|
||||||
Previous
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
hx-get="/edit-list?offset=@(Model.Offset + 25)"
|
|
||||||
hx-swap="outerHTML"
|
|
||||||
hx-target="#file-list"
|
|
||||||
@PropertyHelper.If(Math.Clamp(Model.Offset, 0, Model.Amount) >= Model.Amount - 25, "disabled")>
|
|
||||||
Next
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
@model FastBlog.Core.Models.Blogs.Blog
|
@model FastBlog.Core.Models.Blogs.Blog
|
||||||
|
|
||||||
@await Html.PartialAsync("Blogs/Content", Model)
|
@await Html.PartialAsync("Blogs/BlogContent", Model)
|
||||||
|
|
||||||
<hr/>
|
<hr/>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
@model FastBlog.Core.Models.Blogs.Blog
|
|
||||||
|
|
||||||
@{
|
|
||||||
ViewBag.Title = Model.Metadata.Title;
|
|
||||||
}
|
|
||||||
|
|
||||||
@if(DateTime.UtcNow < Model.Metadata.CreatedAt)
|
|
||||||
{
|
|
||||||
<div class="alert alert-important">
|
|
||||||
<p class="alert-title">Unpublished blog</p>
|
|
||||||
<p>This blog would not be visible and accessible until @Model.Metadata.CreatedAt.ToString("g")</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@await Html.PartialAsync("Blogs/Content", Model)
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<hr/>
|
|
||||||
@await Html.PartialAsync("Blogs/ManageCard", Model.Metadata)
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
@model FastBlog.Core.Models.Blogs.BlogMeta
|
|
||||||
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<h3>Editor's controls</h3>
|
|
||||||
</header>
|
|
||||||
<body>
|
|
||||||
<div class="grid">
|
|
||||||
<div>
|
|
||||||
<a href="/edit/@Model.Id">
|
|
||||||
<button class="btn-fw">
|
|
||||||
Edit
|
|
||||||
</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button class="btn-fw secondary" hx-delete="/blogs/@Model.Id">
|
|
||||||
Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</article>
|
|
||||||
|
|
@ -30,7 +30,7 @@ return;
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="~/"><h1>@options.Value.Title</h1></a></li>
|
<li><a href="~/"><h1>@options.Value.Title</h1></a></li>
|
||||||
<li><a href="~/blogs/list">Blog</a></li>
|
<li><a href="~/blog/list">Blog</a></li>
|
||||||
<li><a href="~/edit">New</a></li>
|
<li><a href="~/edit">New</a></li>
|
||||||
<li><a href="~/files">Files</a></li>
|
<li><a href="~/files">Files</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -63,10 +63,6 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
--pico-font-family: 'Fira Code', Roboto, sans-serif;
|
--pico-font-family: 'Fira Code', Roboto, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
hr {
|
|
||||||
border-color: var(--pico-secondary-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration-color: inherit;
|
text-decoration-color: inherit;
|
||||||
|
|
@ -135,7 +131,7 @@ body {
|
||||||
margin-top: 35px;
|
margin-top: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-alert, .alert {
|
.markdown-alert {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
border-radius: var(--pico-border-radius);
|
border-radius: var(--pico-border-radius);
|
||||||
|
|
@ -145,41 +141,41 @@ body {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-alert p, .alert p {
|
.markdown-alert p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-alert-title, .alert-title {
|
.markdown-alert-title {
|
||||||
font-family: 'Fira Code', Roboto, sans-serif;
|
font-family: 'Fira Code', Roboto, sans-serif;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-alert-note, .alert-note {
|
.markdown-alert-note {
|
||||||
color: var(--pico-primary);
|
color: var(--pico-primary);
|
||||||
background: var(--pico-primary-background);
|
background: var(--pico-primary-background);
|
||||||
border-color: var(--pico-primary-background);
|
border-color: var(--pico-primary-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-alert-tip, .alert-tip {
|
.markdown-alert-tip {
|
||||||
color: var(--pico-tip);
|
color: var(--pico-tip);
|
||||||
background: var(--pico-tip-background);
|
background: var(--pico-tip-background);
|
||||||
border-color: var(--pico-tip-background);
|
border-color: var(--pico-tip-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-alert-important, .alert-important {
|
.markdown-alert-important {
|
||||||
color: var(--pico-important);
|
color: var(--pico-important);
|
||||||
background: var(--pico-important-background);
|
background: var(--pico-important-background);
|
||||||
border-color: var(--pico-important-background);
|
border-color: var(--pico-important-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-alert-warning, .alert-warning {
|
.markdown-alert-warning {
|
||||||
color: var(--pico-warning);
|
color: var(--pico-warning);
|
||||||
background: var(--pico-warning-background);
|
background: var(--pico-warning-background);
|
||||||
border-color: var(--pico-warning-background);
|
border-color: var(--pico-warning-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-alert-caution, .alert-caution {
|
.markdown-alert-caution {
|
||||||
color: var(--pico-danger);
|
color: var(--pico-danger);
|
||||||
background: var(--pico-danger-background);
|
background: var(--pico-danger-background);
|
||||||
border-color: var(--pico-danger-background);
|
border-color: var(--pico-danger-background);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue