V2 #1
|
@ -308,14 +308,16 @@ type PostListItem =
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a post list item from a post
|
/// Create a post list item from a post
|
||||||
static member fromPost (post : Post) =
|
static member fromPost (webLog : WebLog) (post : Post) =
|
||||||
|
let tz = TimeZoneInfo.FindSystemTimeZoneById webLog.timeZone
|
||||||
|
let inTZ (it : DateTime) = TimeZoneInfo.ConvertTimeFromUtc (DateTime (it.Ticks, DateTimeKind.Utc), tz)
|
||||||
{ id = PostId.toString post.id
|
{ id = PostId.toString post.id
|
||||||
authorId = WebLogUserId.toString post.authorId
|
authorId = WebLogUserId.toString post.authorId
|
||||||
status = PostStatus.toString post.status
|
status = PostStatus.toString post.status
|
||||||
title = post.title
|
title = post.title
|
||||||
permalink = Permalink.toString post.permalink
|
permalink = Permalink.toString post.permalink
|
||||||
publishedOn = Option.toNullable post.publishedOn
|
publishedOn = post.publishedOn |> Option.map inTZ |> Option.toNullable
|
||||||
updatedOn = post.updatedOn
|
updatedOn = inTZ post.updatedOn
|
||||||
text = post.text
|
text = post.text
|
||||||
categoryIds = post.categoryIds |> List.map CategoryId.toString
|
categoryIds = post.categoryIds |> List.map CategoryId.toString
|
||||||
tags = post.tags
|
tags = post.tags
|
||||||
|
|
|
@ -52,6 +52,22 @@ module PageListCache =
|
||||||
_cache[Cache.makeKey ctx] <- pages |> List.map (DisplayPage.fromPage webLog) |> Array.ofList
|
_cache[Cache.makeKey ctx] <- pages |> List.map (DisplayPage.fromPage webLog) |> Array.ofList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Cache of all categories, indexed by web log
|
||||||
|
module CategoryCache =
|
||||||
|
|
||||||
|
open MyWebLog.ViewModels
|
||||||
|
|
||||||
|
/// The cache itself
|
||||||
|
let private _cache = ConcurrentDictionary<string, DisplayCategory[]> ()
|
||||||
|
|
||||||
|
/// Get the categories for the web log for this request
|
||||||
|
let get ctx = _cache[Cache.makeKey ctx]
|
||||||
|
|
||||||
|
/// Set the categories for the current web log
|
||||||
|
let set ctx cats = _cache[Cache.makeKey ctx] <- cats
|
||||||
|
|
||||||
|
|
||||||
/// Cache for parsed templates
|
/// Cache for parsed templates
|
||||||
module TemplateCache =
|
module TemplateCache =
|
||||||
|
|
||||||
|
|
|
@ -285,6 +285,12 @@ module Admin =
|
||||||
/// Handlers to manipulate categories
|
/// Handlers to manipulate categories
|
||||||
module Category =
|
module Category =
|
||||||
|
|
||||||
|
/// Update the category cache with flattened category hierarchy
|
||||||
|
let private updateCategoryCache webLogId ctx conn = task {
|
||||||
|
let! cats = Data.Category.findAllForView webLogId conn
|
||||||
|
CategoryCache.set ctx cats
|
||||||
|
}
|
||||||
|
|
||||||
// GET /categories
|
// GET /categories
|
||||||
let all : HttpHandler = requireUser >=> fun next ctx -> task {
|
let all : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
let! cats = Data.Category.findAllForView (webLogId ctx) (conn ctx)
|
let! cats = Data.Category.findAllForView (webLogId ctx) (conn ctx)
|
||||||
|
@ -305,7 +311,6 @@ module Category =
|
||||||
| Some cat -> return Some ("Edit Category", cat)
|
| Some cat -> return Some ("Edit Category", cat)
|
||||||
| None -> return None
|
| None -> return None
|
||||||
}
|
}
|
||||||
let! allCats = Data.Category.findAllForView webLogId conn
|
|
||||||
match result with
|
match result with
|
||||||
| Some (title, cat) ->
|
| Some (title, cat) ->
|
||||||
return!
|
return!
|
||||||
|
@ -313,7 +318,7 @@ module Category =
|
||||||
csrf = csrfToken ctx
|
csrf = csrfToken ctx
|
||||||
model = EditCategoryModel.fromCategory cat
|
model = EditCategoryModel.fromCategory cat
|
||||||
page_title = title
|
page_title = title
|
||||||
categories = allCats
|
categories = CategoryCache.get ctx
|
||||||
|}
|
|}
|
||||||
|> viewForTheme "admin" "category-edit" next ctx
|
|> viewForTheme "admin" "category-edit" next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
|
@ -339,6 +344,7 @@ module Category =
|
||||||
parentId = if model.parentId = "" then None else Some (CategoryId model.parentId)
|
parentId = if model.parentId = "" then None else Some (CategoryId model.parentId)
|
||||||
}
|
}
|
||||||
do! (match model.categoryId with "new" -> Data.Category.add | _ -> Data.Category.update) cat conn
|
do! (match model.categoryId with "new" -> Data.Category.add | _ -> Data.Category.update) cat conn
|
||||||
|
do! updateCategoryCache webLogId ctx conn
|
||||||
do! addMessage ctx { UserMessage.success with message = "Category saved successfully" }
|
do! addMessage ctx { UserMessage.success with message = "Category saved successfully" }
|
||||||
return! redirectToGet $"/category/{CategoryId.toString cat.id}/edit" next ctx
|
return! redirectToGet $"/category/{CategoryId.toString cat.id}/edit" next ctx
|
||||||
| None -> return! Error.notFound next ctx
|
| None -> return! Error.notFound next ctx
|
||||||
|
@ -346,8 +352,12 @@ module Category =
|
||||||
|
|
||||||
// POST /category/{id}/delete
|
// POST /category/{id}/delete
|
||||||
let delete catId : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
|
let delete catId : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
|
||||||
match! Data.Category.delete (CategoryId catId) (webLogId ctx) (conn ctx) with
|
let webLogId = webLogId ctx
|
||||||
| true -> do! addMessage ctx { UserMessage.success with message = "Category deleted successfully" }
|
let conn = conn ctx
|
||||||
|
match! Data.Category.delete (CategoryId catId) webLogId conn with
|
||||||
|
| true ->
|
||||||
|
do! updateCategoryCache webLogId ctx conn
|
||||||
|
do! addMessage ctx { UserMessage.success with message = "Category deleted successfully" }
|
||||||
| false -> do! addMessage ctx { UserMessage.error with message = "Category not found; cannot delete" }
|
| false -> do! addMessage ctx { UserMessage.error with message = "Category not found; cannot delete" }
|
||||||
return! redirectToGet "/categories" next ctx
|
return! redirectToGet "/categories" next ctx
|
||||||
}
|
}
|
||||||
|
@ -452,7 +462,7 @@ module Page =
|
||||||
module Post =
|
module Post =
|
||||||
|
|
||||||
/// Convert a list of posts into items ready to be displayed
|
/// Convert a list of posts into items ready to be displayed
|
||||||
let private preparePostList (webLog : WebLog) (posts : Post list) pageNbr perPage conn = task {
|
let private preparePostList (webLog : WebLog) (posts : Post list) pageNbr perPage ctx conn = task {
|
||||||
let! authors =
|
let! authors =
|
||||||
posts
|
posts
|
||||||
|> List.map (fun p -> p.authorId)
|
|> List.map (fun p -> p.authorId)
|
||||||
|
@ -468,7 +478,7 @@ module Post =
|
||||||
posts
|
posts
|
||||||
|> Seq.ofList
|
|> Seq.ofList
|
||||||
|> Seq.truncate perPage
|
|> Seq.truncate perPage
|
||||||
|> Seq.map PostListItem.fromPost
|
|> Seq.map (PostListItem.fromPost webLog)
|
||||||
|> Array.ofSeq
|
|> Array.ofSeq
|
||||||
let model =
|
let model =
|
||||||
{ posts = postItems
|
{ posts = postItems
|
||||||
|
@ -476,9 +486,9 @@ module Post =
|
||||||
categories = cats
|
categories = cats
|
||||||
subtitle = None
|
subtitle = None
|
||||||
hasNewer = pageNbr <> 1
|
hasNewer = pageNbr <> 1
|
||||||
hasOlder = posts |> List.length > perPage
|
hasOlder = List.length posts > perPage
|
||||||
}
|
}
|
||||||
return Hash.FromAnonymousObject {| model = model |}
|
return Hash.FromAnonymousObject {| model = model; categories = CategoryCache.get ctx |}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /page/{pageNbr}
|
// GET /page/{pageNbr}
|
||||||
|
@ -486,7 +496,7 @@ module Post =
|
||||||
let webLog = WebLogCache.get ctx
|
let webLog = WebLogCache.get ctx
|
||||||
let conn = conn ctx
|
let conn = conn ctx
|
||||||
let! posts = Data.Post.findPageOfPublishedPosts webLog.id pageNbr webLog.postsPerPage conn
|
let! posts = Data.Post.findPageOfPublishedPosts webLog.id pageNbr webLog.postsPerPage conn
|
||||||
let! hash = preparePostList webLog posts pageNbr webLog.postsPerPage conn
|
let! hash = preparePostList webLog posts pageNbr webLog.postsPerPage ctx conn
|
||||||
let title =
|
let title =
|
||||||
match pageNbr, webLog.defaultPage with
|
match pageNbr, webLog.defaultPage with
|
||||||
| 1, "posts" -> None
|
| 1, "posts" -> None
|
||||||
|
@ -518,7 +528,7 @@ module Post =
|
||||||
// Current post
|
// Current post
|
||||||
match! Data.Post.findByPermalink permalink webLog.id conn with
|
match! Data.Post.findByPermalink permalink webLog.id conn with
|
||||||
| Some post ->
|
| Some post ->
|
||||||
let! model = preparePostList webLog [ post ] 1 1 conn
|
let! model = preparePostList webLog [ post ] 1 1 ctx conn
|
||||||
model.Add ("page_title", post.title)
|
model.Add ("page_title", post.title)
|
||||||
return! themedView "single-post" next ctx model
|
return! themedView "single-post" next ctx model
|
||||||
| None ->
|
| None ->
|
||||||
|
@ -548,7 +558,7 @@ module Post =
|
||||||
let webLog = WebLogCache.get ctx
|
let webLog = WebLogCache.get ctx
|
||||||
let conn = conn ctx
|
let conn = conn ctx
|
||||||
let! posts = Data.Post.findPageOfPosts webLog.id pageNbr 25 conn
|
let! posts = Data.Post.findPageOfPosts webLog.id pageNbr 25 conn
|
||||||
let! hash = preparePostList webLog posts pageNbr 25 conn
|
let! hash = preparePostList webLog posts pageNbr 25 ctx conn
|
||||||
hash.Add ("page_title", "Posts")
|
hash.Add ("page_title", "Posts")
|
||||||
return! viewForTheme "admin" "post-list" next ctx hash
|
return! viewForTheme "admin" "post-list" next ctx hash
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@ type WebLogMiddleware (next : RequestDelegate) =
|
||||||
| Some webLog ->
|
| Some webLog ->
|
||||||
WebLogCache.set ctx webLog
|
WebLogCache.set ctx webLog
|
||||||
do! PageListCache.update ctx
|
do! PageListCache.update ctx
|
||||||
|
let! cats = Data.Category.findAllForView webLog.id conn
|
||||||
|
CategoryCache.set ctx cats
|
||||||
return! next.Invoke ctx
|
return! next.Invoke ctx
|
||||||
| None -> ctx.Response.StatusCode <- 404
|
| None -> ctx.Response.StatusCode <- 404
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{% for post in model.posts %}
|
{%- for post in model.posts %}
|
||||||
<article class="item">
|
<article class="item">
|
||||||
<h1 class="item-heading">
|
<h1 class="item-heading">
|
||||||
<a href="/{{ post.permalink }}" title="Permanent Link to "{{ post.title | escape }}"">
|
<a href="/{{ post.permalink }}" title="Permanent Link to "{{ post.title | escape }}"">
|
||||||
|
@ -8,11 +8,11 @@
|
||||||
</h1>
|
</h1>
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
<i class="fa fa-calendar" title="Date"></i> {{ post.published_on | date: "MMMM d, yyyy" }}
|
<i class="fa fa-calendar" title="Date"></i> {{ post.published_on | date: "MMMM d, yyyy" }}
|
||||||
<i class="fa fa-clock-o" title="Time"></i> {{ post.published_on | date: "h:mmtt" | downcase }}
|
<i class="fa fa-clock-o" title="Time"></i> {{ post.published_on | date: "h:mm tt" | downcase }}
|
||||||
</h4>
|
</h4>
|
||||||
{{ post.text }}
|
{{ post.text }}
|
||||||
</article>
|
</article>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
<nav aria-label="pagination">
|
<nav aria-label="pagination">
|
||||||
<ul class="pager">
|
<ul class="pager">
|
||||||
{% if model.newer_link -%}
|
{% if model.newer_link -%}
|
||||||
|
@ -52,8 +52,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h4 class="item-heading">Categories</h4>
|
<h4 class="item-heading">Categories</h4>
|
||||||
<div>
|
<ul class="cat-list">
|
||||||
TODO: list_categories({ class: 'cat' })
|
{% for cat in categories -%}
|
||||||
</div>
|
{%- assign indent = cat.parent_names | size -%}
|
||||||
|
<li class="cat-list-item"{% if indent > 0 %} style="padding-left:{{ indent }}rem;"{% endif %}>
|
||||||
|
<a href="/category/{{ cat.slug }}" class="cat-list-link">{{ cat.name }}</a>
|
||||||
|
</li>
|
||||||
|
{%- endfor %}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -11,7 +11,7 @@
|
||||||
<link rel="preload" href="/themes/{{ web_log.theme_path }}/style.css" as="style">
|
<link rel="preload" href="/themes/{{ web_log.theme_path }}/style.css" as="style">
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">
|
||||||
<link rel="stylesheet" href="/themes/{{ web_log.theme_path }}/style.css">
|
<link rel="stylesheet" href="/themes/{{ web_log.theme_path }}/style.css">
|
||||||
{% if is_home -%}
|
{%- if is_home %}
|
||||||
<link rel="alternate" type="application/rss+xml" title="{{ web_log.name | escape }}" href="/feed.xml">
|
<link rel="alternate" type="application/rss+xml" title="{{ web_log.name | escape }}" href="/feed.xml">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
<script src="/themes/{{ web_log.theme_path }}/djs.js"></script>
|
<script src="/themes/{{ web_log.theme_path }}/djs.js"></script>
|
||||||
|
@ -21,9 +21,9 @@
|
||||||
<p><a class="nav-home" href="/">{{ web_log.name }}</a></p>
|
<p><a class="nav-home" href="/">{{ web_log.name }}</a></p>
|
||||||
{%- if web_log.subtitle %}<p>{{ web_log.subtitle.value }}</p>{% endif -%}
|
{%- if web_log.subtitle %}<p>{{ web_log.subtitle.value }}</p>{% endif -%}
|
||||||
<p class="nav-spacer"></p>
|
<p class="nav-spacer"></p>
|
||||||
{% for page in page_list %}
|
{%- for page in page_list %}
|
||||||
<p class="desktop"><a href="/{{ page.permalink }}">{{ page.title }}</a></p>
|
<p class="desktop"><a href="/{{ page.permalink }}">{{ page.title }}</a></p>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
<p class="desktop">
|
<p class="desktop">
|
||||||
<a href="https://devotions.summershome.org" target="_blank" rel="noopener">A Word from the Word</a>
|
<a href="https://devotions.summershome.org" target="_blank" rel="noopener">A Word from the Word</a>
|
||||||
</p>
|
</p>
|
||||||
|
@ -31,9 +31,9 @@
|
||||||
</nav>
|
</nav>
|
||||||
{{ content }}
|
{{ content }}
|
||||||
<footer class="part-1" id="links">
|
<footer class="part-1" id="links">
|
||||||
{% for page in page_list %}
|
{%- for page in page_list %}
|
||||||
<p class="mobile"><a href="{{ page.permalink }}">{{ page.title }}</a></p>
|
<p class="mobile"><a href="{{ page.permalink }}">{{ page.title }}</a></p>
|
||||||
{% endfor %}
|
{%- endfor %}
|
||||||
<p class="mobile">
|
<p class="mobile">
|
||||||
<a href="https://devotions.summershome.org" target="_blank" rel="noopener">A Word from the Word</a>
|
<a href="https://devotions.summershome.org" target="_blank" rel="noopener">A Word from the Word</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<h4 class="text-center">
|
<h4 class="text-center">
|
||||||
{% if post.published_on -%}
|
{% if post.published_on -%}
|
||||||
<i class="fa fa-calendar" title="Date"></i> {{ post.published_on | date: "MMMM d, yyyy" }}
|
<i class="fa fa-calendar" title="Date"></i> {{ post.published_on | date: "MMMM d, yyyy" }}
|
||||||
<i class="fa fa-clock-o" title="Time"></i> {{ post.published_on | date: "h:mmtt" | downcase }}
|
<i class="fa fa-clock-o" title="Time"></i> {{ post.published_on | date: "h:mm tt" | downcase }}
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
**DRAFT**
|
**DRAFT**
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -15,12 +15,12 @@
|
||||||
{% if cat_count > 0 -%}
|
{% if cat_count > 0 -%}
|
||||||
<h4>
|
<h4>
|
||||||
Categorized
|
Categorized
|
||||||
{% for cat in post.category_ids -%}
|
{% for cat_id in post.category_ids -%}
|
||||||
{% assign cat_name = model.categories | value: cat %}
|
{% assign cat = categories | where: "id", cat_id | first %}
|
||||||
<span class="no-wrap">
|
<span class="no-wrap">
|
||||||
<i class="fa fa-folder-open-o" title="Category"></i>
|
<i class="fa fa-folder-open-o" title="Category"></i>
|
||||||
<a href="/category/{{ cat }}" title="Categorized under “{{ cat_name | escape }}”">
|
<a href="/category/{{ cat.slug }}" title="Categorized under “{{ cat.name | escape }}”">
|
||||||
{{ cat_name }}
|
{{ cat.name }}
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
|
|
|
@ -154,7 +154,7 @@ img {
|
||||||
.cat-list ul {
|
.cat-list ul {
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
}
|
}
|
||||||
.cat-list ul li {
|
.cat-list li {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
padding-bottom: .2rem;
|
padding-bottom: .2rem;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user