187 lines
7.3 KiB
C#
187 lines
7.3 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using PlotLine.Services;
|
|
using PlotLine.ViewModels;
|
|
using System.Net;
|
|
|
|
namespace PlotLine.Controllers;
|
|
|
|
[Route("help")]
|
|
[AllowAnonymous]
|
|
public sealed class HelpController(IHelpService help, IHelpMarkdownRenderer markdown, IConfiguration configuration) : Controller
|
|
{
|
|
[HttpGet("", Name = PublicRouteNames.Help)]
|
|
public IActionResult Index(string? q)
|
|
{
|
|
if (!IsCanonicalPath("/help"))
|
|
{
|
|
return RedirectPermanent(PreserveQuery("/help"));
|
|
}
|
|
|
|
var canonicalUrl = PublicSeo.AbsoluteUrl(configuration, "/help");
|
|
const string description = "Browse PlotDirector help articles covering projects, books, chapters, scenes, timelines, continuity, characters, locations, and writing tools.";
|
|
PublicSeo.Apply(ViewData, configuration, new SeoMetadata
|
|
{
|
|
Title = "Help Centre",
|
|
Description = description,
|
|
Keywords = "PlotDirector help, writing software help, story planning help, novel planning guides",
|
|
CanonicalPath = "/help",
|
|
JsonLd =
|
|
[
|
|
PublicSeo.JsonLd(new Dictionary<string, object?>
|
|
{
|
|
["@context"] = "https://schema.org",
|
|
["@type"] = "CollectionPage",
|
|
["name"] = "Help Centre",
|
|
["url"] = canonicalUrl,
|
|
["description"] = description
|
|
}),
|
|
PublicSeo.BreadcrumbJsonLd(
|
|
("Help", canonicalUrl))
|
|
]
|
|
});
|
|
var articles = help.GetAllArticles();
|
|
return View(new HelpCentreIndexViewModel
|
|
{
|
|
Query = q,
|
|
ArticleCount = articles.Count,
|
|
Categories = help.GetCategories()
|
|
.Select(category => new HelpCategoryViewModel
|
|
{
|
|
CategoryName = category,
|
|
CategorySlug = HelpService.Slugify(category),
|
|
ArticleCount = help.GetByCategory(category).Count
|
|
})
|
|
.ToList(),
|
|
SearchResults = string.IsNullOrWhiteSpace(q) ? [] : help.Search(q),
|
|
RecentArticles = articles.OrderByDescending(x => x.RelativePath).Take(5).ToList()
|
|
});
|
|
}
|
|
|
|
[HttpGet("{categorySlug}", Name = PublicRouteNames.HelpCategory)]
|
|
public IActionResult Category(string categorySlug)
|
|
{
|
|
var category = help.GetCategoryBySlug(categorySlug);
|
|
if (category is null)
|
|
{
|
|
return NotFound();
|
|
}
|
|
|
|
var canonicalPath = $"/help/{HelpService.Slugify(category)}";
|
|
if (!IsCanonicalPath(canonicalPath))
|
|
{
|
|
return RedirectPermanent(canonicalPath);
|
|
}
|
|
|
|
var articles = help.GetByCategorySlug(categorySlug);
|
|
var canonicalUrl = PublicSeo.AbsoluteUrl(configuration, canonicalPath);
|
|
var description = CategoryDescription(category);
|
|
PublicSeo.Apply(ViewData, configuration, new SeoMetadata
|
|
{
|
|
Title = $"{category} Help",
|
|
Description = description,
|
|
Keywords = PublicSeo.Keywords([category, "PlotDirector help", "novel planning guides"], articles.SelectMany(article => article.Tags)),
|
|
CanonicalPath = canonicalPath,
|
|
JsonLd =
|
|
[
|
|
PublicSeo.JsonLd(new Dictionary<string, object?>
|
|
{
|
|
["@context"] = "https://schema.org",
|
|
["@type"] = "CollectionPage",
|
|
["name"] = $"{category} Help",
|
|
["url"] = canonicalUrl,
|
|
["description"] = description
|
|
}),
|
|
PublicSeo.BreadcrumbJsonLd(
|
|
("Help", PublicSeo.AbsoluteUrl(configuration, "/help")),
|
|
(category, canonicalUrl))
|
|
]
|
|
});
|
|
return View(new HelpCategoryArticlesViewModel
|
|
{
|
|
CategoryName = category,
|
|
CategorySlug = HelpService.Slugify(category),
|
|
Articles = articles
|
|
});
|
|
}
|
|
|
|
[HttpGet("{categorySlug}/{articleSlug}", Name = PublicRouteNames.HelpArticle)]
|
|
public IActionResult Article(string categorySlug, string articleSlug)
|
|
{
|
|
var article = help.GetBySlugs(categorySlug, articleSlug);
|
|
if (article is null)
|
|
{
|
|
return View("ArticleNotFound", $"{categorySlug}/{articleSlug}");
|
|
}
|
|
|
|
if (!IsCanonicalPath(article.Url))
|
|
{
|
|
return RedirectPermanent(article.Url);
|
|
}
|
|
|
|
var canonicalUrl = PublicSeo.AbsoluteUrl(configuration, article.Url);
|
|
var description = PublicSeo.HelpArticleDescription(article, help.GetAllArticles());
|
|
PublicSeo.Apply(ViewData, configuration, new SeoMetadata
|
|
{
|
|
Title = article.Title,
|
|
Description = description,
|
|
Keywords = PublicSeo.Keywords([article.Category, article.Title, "PlotDirector help"], article.Tags),
|
|
CanonicalPath = article.Url,
|
|
OgType = "article",
|
|
JsonLd =
|
|
[
|
|
PublicSeo.JsonLd(new Dictionary<string, object?>
|
|
{
|
|
["@context"] = "https://schema.org",
|
|
["@type"] = "TechArticle",
|
|
["headline"] = article.Title,
|
|
["name"] = article.Title,
|
|
["url"] = canonicalUrl,
|
|
["description"] = description,
|
|
["dateModified"] = article.LastModifiedUtc == default ? null : article.LastModifiedUtc.ToString("O")
|
|
}),
|
|
PublicSeo.BreadcrumbJsonLd(
|
|
("Help", PublicSeo.AbsoluteUrl(configuration, "/help")),
|
|
(article.Category, PublicSeo.AbsoluteUrl(configuration, article.CategoryUrl)),
|
|
(article.Title, canonicalUrl))
|
|
]
|
|
});
|
|
return View(new HelpCentreArticleViewModel
|
|
{
|
|
Article = article,
|
|
RenderedHtml = markdown.Render(article.MarkdownContent),
|
|
RelatedArticles = help.GetRelatedArticles(article)
|
|
});
|
|
}
|
|
|
|
[HttpGet("/Help/Category/{category}")]
|
|
public IActionResult LegacyCategory(string category)
|
|
{
|
|
var articles = help.GetByCategory(WebUtility.UrlDecode(category));
|
|
var firstArticle = articles.FirstOrDefault();
|
|
return firstArticle is null
|
|
? NotFound()
|
|
: RedirectPermanent(firstArticle.CategoryUrl);
|
|
}
|
|
|
|
[HttpGet("/Help/Article/{key}")]
|
|
public IActionResult LegacyArticle(string key)
|
|
{
|
|
var article = help.GetByContextKey(WebUtility.UrlDecode(key));
|
|
return article is null
|
|
? View("ArticleNotFound", key)
|
|
: RedirectPermanent(article.Url);
|
|
}
|
|
|
|
private bool IsCanonicalPath(string path) =>
|
|
string.Equals(HttpContext.Request.Path.Value, path, StringComparison.Ordinal);
|
|
|
|
private string PreserveQuery(string path) =>
|
|
string.IsNullOrWhiteSpace(HttpContext.Request.QueryString.Value)
|
|
? path
|
|
: $"{path}{HttpContext.Request.QueryString.Value}";
|
|
|
|
private static string CategoryDescription(string category) =>
|
|
$"Find PlotDirector help articles for {category.ToLowerInvariant()}, including practical guidance for organising story details, reviewing continuity, and keeping your novel on track.";
|
|
}
|