V2 #1
|
@ -352,6 +352,14 @@ module Page =
|
||||||
/// Functions to manipulate posts
|
/// Functions to manipulate posts
|
||||||
module Post =
|
module Post =
|
||||||
|
|
||||||
|
/// Add a post
|
||||||
|
let add (post : Post) =
|
||||||
|
rethink {
|
||||||
|
withTable Table.Post
|
||||||
|
insert post
|
||||||
|
write; withRetryDefault; ignoreResult
|
||||||
|
}
|
||||||
|
|
||||||
/// Count posts for a web log by their status
|
/// Count posts for a web log by their status
|
||||||
let countByStatus (status : PostStatus) (webLogId : WebLogId) =
|
let countByStatus (status : PostStatus) (webLogId : WebLogId) =
|
||||||
rethink<int> {
|
rethink<int> {
|
||||||
|
@ -373,6 +381,15 @@ module Post =
|
||||||
}
|
}
|
||||||
|> tryFirst
|
|> tryFirst
|
||||||
|
|
||||||
|
/// Find a post by its ID, including all revisions and prior permalinks
|
||||||
|
let findByFullId (postId : PostId) webLogId =
|
||||||
|
rethink<Post> {
|
||||||
|
withTable Table.Post
|
||||||
|
get postId
|
||||||
|
resultOption; withRetryOptionDefault
|
||||||
|
}
|
||||||
|
|> verifyWebLog webLogId (fun p -> p.webLogId)
|
||||||
|
|
||||||
/// Find the current permalink for a post by a prior permalink
|
/// Find the current permalink for a post by a prior permalink
|
||||||
let findCurrentPermalink (permalink : Permalink) (webLogId : WebLogId) =
|
let findCurrentPermalink (permalink : Permalink) (webLogId : WebLogId) =
|
||||||
rethink<Permalink list> {
|
rethink<Permalink list> {
|
||||||
|
@ -410,6 +427,15 @@ module Post =
|
||||||
result; withRetryDefault
|
result; withRetryDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update a post (all fields are updated)
|
||||||
|
let update (post : Post) =
|
||||||
|
rethink {
|
||||||
|
withTable Table.Post
|
||||||
|
get post.id
|
||||||
|
replace post
|
||||||
|
write; withRetryDefault; ignoreResult
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Functions to manipulate web logs
|
/// Functions to manipulate web logs
|
||||||
module WebLog =
|
module WebLog =
|
||||||
|
|
|
@ -153,6 +153,54 @@ type EditPageModel =
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// View model to edit a post
|
||||||
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
|
type EditPostModel =
|
||||||
|
{ /// The ID of the post being edited
|
||||||
|
postId : string
|
||||||
|
|
||||||
|
/// The title of the post
|
||||||
|
title : string
|
||||||
|
|
||||||
|
/// The permalink for the post
|
||||||
|
permalink : string
|
||||||
|
|
||||||
|
/// The source format for the text
|
||||||
|
source : string
|
||||||
|
|
||||||
|
/// The text of the post
|
||||||
|
text : string
|
||||||
|
|
||||||
|
/// The tags for the post
|
||||||
|
tags : string
|
||||||
|
|
||||||
|
/// The category IDs for the post
|
||||||
|
categoryIds : string[]
|
||||||
|
|
||||||
|
/// The post status
|
||||||
|
status : string
|
||||||
|
|
||||||
|
/// Whether this post should be published
|
||||||
|
doPublish : bool
|
||||||
|
}
|
||||||
|
/// Create an edit model from an existing past
|
||||||
|
static member fromPost (post : Post) =
|
||||||
|
let latest =
|
||||||
|
match post.revisions |> List.sortByDescending (fun r -> r.asOf) |> List.tryHead with
|
||||||
|
| Some rev -> rev
|
||||||
|
| None -> Revision.empty
|
||||||
|
{ postId = PostId.toString post.id
|
||||||
|
title = post.title
|
||||||
|
permalink = Permalink.toString post.permalink
|
||||||
|
source = MarkupText.sourceType latest.text
|
||||||
|
text = MarkupText.text latest.text
|
||||||
|
tags = String.Join (", ", post.tags)
|
||||||
|
categoryIds = post.categoryIds |> List.map CategoryId.toString |> Array.ofList
|
||||||
|
status = PostStatus.toString post.status
|
||||||
|
doPublish = false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// The model to use to allow a user to log on
|
/// The model to use to allow a user to log on
|
||||||
[<CLIMutable; NoComparison; NoEquality>]
|
[<CLIMutable; NoComparison; NoEquality>]
|
||||||
type LogOnModel =
|
type LogOnModel =
|
||||||
|
@ -173,6 +221,9 @@ type PostListItem =
|
||||||
/// The ID of the user who authored the post
|
/// The ID of the user who authored the post
|
||||||
authorId : string
|
authorId : string
|
||||||
|
|
||||||
|
/// The name of the user who authored the post
|
||||||
|
authorName : string
|
||||||
|
|
||||||
/// The status of the post
|
/// The status of the post
|
||||||
status : string
|
status : string
|
||||||
|
|
||||||
|
@ -202,6 +253,7 @@ type PostListItem =
|
||||||
static member fromPost (post : Post) =
|
static member fromPost (post : Post) =
|
||||||
{ id = PostId.toString post.id
|
{ id = PostId.toString post.id
|
||||||
authorId = WebLogUserId.toString post.authorId
|
authorId = WebLogUserId.toString post.authorId
|
||||||
|
authorName = ""
|
||||||
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
|
||||||
|
@ -221,8 +273,8 @@ type PostDisplay =
|
||||||
/// Category ID -> name lookup
|
/// Category ID -> name lookup
|
||||||
categories : IDictionary<string, string>
|
categories : IDictionary<string, string>
|
||||||
|
|
||||||
/// Author ID -> name lookup
|
/// A subtitle for the page
|
||||||
authors : IDictionary<string, string>
|
subtitle : string option
|
||||||
|
|
||||||
/// Whether there are newer posts than the ones in this model
|
/// Whether there are newer posts than the ones in this model
|
||||||
hasNewer : bool
|
hasNewer : bool
|
||||||
|
|
|
@ -246,7 +246,7 @@ module Admin =
|
||||||
let updated =
|
let updated =
|
||||||
{ webLog with
|
{ webLog with
|
||||||
name = model.name
|
name = model.name
|
||||||
subtitle = match model.subtitle with "" -> None | it -> Some it
|
subtitle = if model.subtitle = "" then None else Some model.subtitle
|
||||||
defaultPage = model.defaultPage
|
defaultPage = model.defaultPage
|
||||||
postsPerPage = model.postsPerPage
|
postsPerPage = model.postsPerPage
|
||||||
timeZone = model.timeZone
|
timeZone = model.timeZone
|
||||||
|
@ -315,8 +315,8 @@ module Category =
|
||||||
{ cat with
|
{ cat with
|
||||||
name = model.name
|
name = model.name
|
||||||
slug = model.slug
|
slug = model.slug
|
||||||
description = match model.description with "" -> None | it -> Some it
|
description = if model.description = "" then None else Some model.description
|
||||||
parentId = match model.parentId with "" -> None | it -> Some (CategoryId it)
|
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! addMessage ctx { UserMessage.success with message = "Category saved successfully" }
|
do! addMessage ctx { UserMessage.success with message = "Category saved successfully" }
|
||||||
|
@ -383,10 +383,10 @@ module Page =
|
||||||
| "new" ->
|
| "new" ->
|
||||||
return Some
|
return Some
|
||||||
{ Page.empty with
|
{ Page.empty with
|
||||||
id = PageId.create ()
|
id = PageId.create ()
|
||||||
webLogId = webLogId
|
webLogId = webLogId
|
||||||
authorId = userId ctx
|
authorId = userId ctx
|
||||||
publishedOn = now
|
publishedOn = now
|
||||||
}
|
}
|
||||||
| pgId -> return! Data.Page.findByFullId (PageId pgId) webLogId conn
|
| pgId -> return! Data.Page.findByFullId (PageId pgId) webLogId conn
|
||||||
}
|
}
|
||||||
|
@ -421,16 +421,41 @@ module Page =
|
||||||
/// Handlers to manipulate posts
|
/// Handlers to manipulate posts
|
||||||
module Post =
|
module Post =
|
||||||
|
|
||||||
|
/// Convert a list of posts into items ready to be displayed
|
||||||
|
let private preparePostList (webLog : WebLog) (posts : Post list) pageNbr perPage conn = task {
|
||||||
|
let! authors =
|
||||||
|
Data.WebLogUser.findNames (posts |> List.map (fun p -> p.authorId) |> List.distinct) webLog.id conn
|
||||||
|
let! cats =
|
||||||
|
Data.Category.findNames (posts |> List.map (fun c -> c.categoryIds) |> List.concat |> List.distinct)
|
||||||
|
webLog.id conn
|
||||||
|
let postItems =
|
||||||
|
posts
|
||||||
|
|> Seq.ofList
|
||||||
|
|> Seq.truncate perPage
|
||||||
|
|> Seq.map PostListItem.fromPost
|
||||||
|
|> Seq.map (fun pi -> { pi with authorName = authors[pi.authorId] })
|
||||||
|
|> Array.ofSeq
|
||||||
|
let model =
|
||||||
|
{ posts = postItems
|
||||||
|
categories = cats
|
||||||
|
subtitle = None
|
||||||
|
hasNewer = pageNbr <> 1
|
||||||
|
hasOlder = posts |> List.length > perPage
|
||||||
|
}
|
||||||
|
return Hash.FromAnonymousObject {| model = model |}
|
||||||
|
}
|
||||||
|
|
||||||
// GET /page/{pageNbr}
|
// GET /page/{pageNbr}
|
||||||
let pageOfPosts (pageNbr : int) : HttpHandler = fun next ctx -> task {
|
let pageOfPosts (pageNbr : int) : HttpHandler = fun next ctx -> task {
|
||||||
let webLog = WebLogCache.get ctx
|
let webLog = WebLogCache.get ctx
|
||||||
let! posts = Data.Post.findPageOfPublishedPosts webLog.id pageNbr webLog.postsPerPage (conn ctx)
|
let conn = conn ctx
|
||||||
let hash = Hash.FromAnonymousObject {| posts = posts |}
|
let! posts = Data.Post.findPageOfPublishedPosts webLog.id pageNbr webLog.postsPerPage conn
|
||||||
|
let! hash = preparePostList webLog posts pageNbr webLog.postsPerPage conn
|
||||||
let title =
|
let title =
|
||||||
match pageNbr, webLog.defaultPage with
|
match pageNbr, webLog.defaultPage with
|
||||||
| 1, "posts" -> None
|
| 1, "posts" -> None
|
||||||
| _, "posts" -> Some $"Page {pageNbr}"
|
| _, "posts" -> Some $"Page {pageNbr}"
|
||||||
| _, _ -> Some $"Page {pageNbr} « Posts"
|
| _, _ -> Some $"Page {pageNbr} « Posts"
|
||||||
match title with Some ttl -> hash.Add ("page_title", ttl) | None -> ()
|
match title with Some ttl -> hash.Add ("page_title", ttl) | None -> ()
|
||||||
return! themedView "index" next ctx hash
|
return! themedView "index" next ctx hash
|
||||||
}
|
}
|
||||||
|
@ -482,39 +507,86 @@ module Post =
|
||||||
// GET /posts
|
// GET /posts
|
||||||
// GET /posts/page/{pageNbr}
|
// GET /posts/page/{pageNbr}
|
||||||
let all pageNbr : HttpHandler = requireUser >=> fun next ctx -> task {
|
let all pageNbr : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
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! authors =
|
let! hash = preparePostList webLog posts pageNbr 25 conn
|
||||||
Data.WebLogUser.findNames (posts |> List.map (fun p -> p.authorId) |> List.distinct) webLog.id conn
|
hash.Add ("page_title", "Posts")
|
||||||
let! cats =
|
return! viewForTheme "admin" "post-list" next ctx hash
|
||||||
Data.Category.findNames (posts |> List.map (fun c -> c.categoryIds) |> List.concat |> List.distinct)
|
|
||||||
webLog.id conn
|
|
||||||
let tags = posts
|
|
||||||
|> List.map (fun p -> PostId.toString p.id, p.tags |> List.fold (fun t tag -> $"{t}, {tag}") "")
|
|
||||||
|> dict
|
|
||||||
let model =
|
|
||||||
{ posts = posts |> Seq.ofList |> Seq.truncate 25 |> Seq.map PostListItem.fromPost |> Array.ofSeq
|
|
||||||
authors = authors
|
|
||||||
categories = cats
|
|
||||||
hasNewer = pageNbr <> 1
|
|
||||||
hasOlder = posts |> List.length > webLog.postsPerPage
|
|
||||||
}
|
|
||||||
return!
|
|
||||||
Hash.FromAnonymousObject {| model = model; tags = tags; page_title = "Posts" |}
|
|
||||||
|> viewForTheme "admin" "post-list" next ctx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /post/{id}/edit
|
// GET /post/{id}/edit
|
||||||
let edit _ : HttpHandler = requireUser >=> fun next ctx -> task {
|
let edit postId : HttpHandler = requireUser >=> fun next ctx -> task {
|
||||||
// TODO: write handler
|
let webLogId = webLogId ctx
|
||||||
return! Error.notFound next ctx
|
let conn = conn ctx
|
||||||
|
let! result = task {
|
||||||
|
match postId with
|
||||||
|
| "new" -> return Some ("Write a New Post", { Post.empty with id = PostId "new" })
|
||||||
|
| _ ->
|
||||||
|
match! Data.Post.findByFullId (PostId postId) webLogId conn with
|
||||||
|
| Some post -> return Some ("Edit Post", post)
|
||||||
|
| None -> return None
|
||||||
|
}
|
||||||
|
match result with
|
||||||
|
| Some (title, post) ->
|
||||||
|
let! cats = Data.Category.findAllForView webLogId conn
|
||||||
|
return!
|
||||||
|
Hash.FromAnonymousObject {|
|
||||||
|
csrf = csrfToken ctx
|
||||||
|
model = EditPostModel.fromPost post
|
||||||
|
page_title = title
|
||||||
|
categories = cats
|
||||||
|
|}
|
||||||
|
|> viewForTheme "admin" "post-edit" next ctx
|
||||||
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST /post/{id}/edit
|
// POST /post/save
|
||||||
let save : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
|
let save : HttpHandler = requireUser >=> validateCsrf >=> fun next ctx -> task {
|
||||||
// TODO: write handler
|
let! model = ctx.BindFormAsync<EditPostModel> ()
|
||||||
return! Error.notFound next ctx
|
let webLogId = webLogId ctx
|
||||||
|
let conn = conn ctx
|
||||||
|
let now = DateTime.UtcNow
|
||||||
|
let! pst = task {
|
||||||
|
match model.postId with
|
||||||
|
| "new" ->
|
||||||
|
return Some
|
||||||
|
{ Post.empty with
|
||||||
|
id = PostId.create ()
|
||||||
|
webLogId = webLogId
|
||||||
|
authorId = userId ctx
|
||||||
|
}
|
||||||
|
| postId -> return! Data.Post.findByFullId (PostId postId) webLogId conn
|
||||||
|
}
|
||||||
|
match pst with
|
||||||
|
| Some post ->
|
||||||
|
let revision = { asOf = now; text = MarkupText.parse $"{model.source}: {model.text}" }
|
||||||
|
// Detect a permalink change, and add the prior one to the prior list
|
||||||
|
let page =
|
||||||
|
match Permalink.toString post.permalink with
|
||||||
|
| "" -> post
|
||||||
|
| link when link = model.permalink -> post
|
||||||
|
| _ -> { post with priorPermalinks = post.permalink :: post.priorPermalinks }
|
||||||
|
let post =
|
||||||
|
{ post with
|
||||||
|
title = model.title
|
||||||
|
permalink = Permalink model.permalink
|
||||||
|
publishedOn = if model.doPublish then Some now else post.publishedOn
|
||||||
|
updatedOn = now
|
||||||
|
text = MarkupText.toHtml revision.text
|
||||||
|
tags = model.tags.Split ","
|
||||||
|
|> Seq.ofArray
|
||||||
|
|> Seq.map (fun it -> it.Trim().ToLower ())
|
||||||
|
|> Seq.sort
|
||||||
|
|> List.ofSeq
|
||||||
|
categoryIds = model.categoryIds |> Array.map CategoryId |> List.ofArray
|
||||||
|
status = if model.doPublish then Published else post.status
|
||||||
|
revisions = revision :: page.revisions
|
||||||
|
}
|
||||||
|
do! (match model.postId with "new" -> Data.Post.add | _ -> Data.Post.update) post conn
|
||||||
|
do! addMessage ctx { UserMessage.success with message = "Post saved successfully" }
|
||||||
|
return! redirectToGet $"/post/{PostId.toString post.id}/edit" next ctx
|
||||||
|
| None -> return! Error.notFound next ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -194,23 +194,16 @@ let main args =
|
||||||
Template.RegisterFilter typeof<DotLiquidBespoke.NavLinkFilter>
|
Template.RegisterFilter typeof<DotLiquidBespoke.NavLinkFilter>
|
||||||
Template.RegisterTag<DotLiquidBespoke.UserLinksTag> "user_links"
|
Template.RegisterTag<DotLiquidBespoke.UserLinksTag> "user_links"
|
||||||
|
|
||||||
let all = [| "*" |]
|
[ // Domain types
|
||||||
Template.RegisterSafeType (typeof<Page>, all)
|
typeof<Page>; typeof<WebLog>
|
||||||
Template.RegisterSafeType (typeof<WebLog>, all)
|
// View models
|
||||||
|
typeof<DashboardModel>; typeof<DisplayCategory>; typeof<DisplayPage>; typeof<EditCategoryModel>
|
||||||
Template.RegisterSafeType (typeof<DashboardModel>, all)
|
typeof<EditPageModel>; typeof<EditPostModel>; typeof<PostDisplay>; typeof<PostListItem>
|
||||||
Template.RegisterSafeType (typeof<DisplayCategory>, all)
|
typeof<SettingsModel>; typeof<UserMessage>
|
||||||
Template.RegisterSafeType (typeof<DisplayPage>, all)
|
// Framework types
|
||||||
Template.RegisterSafeType (typeof<EditCategoryModel>, all)
|
typeof<AntiforgeryTokenSet>; typeof<string option>; typeof<KeyValuePair>
|
||||||
Template.RegisterSafeType (typeof<EditPageModel>, all)
|
]
|
||||||
Template.RegisterSafeType (typeof<PostDisplay>, all)
|
|> List.iter (fun it -> Template.RegisterSafeType (it, [| "*" |]))
|
||||||
Template.RegisterSafeType (typeof<PostListItem>, all)
|
|
||||||
Template.RegisterSafeType (typeof<SettingsModel>, all)
|
|
||||||
Template.RegisterSafeType (typeof<UserMessage>, all)
|
|
||||||
|
|
||||||
Template.RegisterSafeType (typeof<AntiforgeryTokenSet>, all)
|
|
||||||
Template.RegisterSafeType (typeof<string option>, all)
|
|
||||||
Template.RegisterSafeType (typeof<KeyValuePair>, all)
|
|
||||||
|
|
||||||
let app = builder.Build ()
|
let app = builder.Build ()
|
||||||
|
|
||||||
|
|
67
src/MyWebLog/themes/admin/post-edit.liquid
Normal file
67
src/MyWebLog/themes/admin/post-edit.liquid
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
<h2 class="my-3">{{ page_title }}</h2>
|
||||||
|
<article>
|
||||||
|
<form action="/post/save" method="post">
|
||||||
|
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
|
||||||
|
<input type="hidden" name="postId" value="{{ model.post_id }}">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12 col-lg-9">
|
||||||
|
<div class="form-floating pb-3">
|
||||||
|
<input type="text" name="title" id="title" class="form-control" placeholder="Title" autofocus required
|
||||||
|
value="{{ model.title }}">
|
||||||
|
<label for="title">Title</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating pb-3">
|
||||||
|
<input type="text" name="permalink" id="permalink" class="form-control" placeholder="Permalink" required
|
||||||
|
value="{{ model.permalink }}">
|
||||||
|
<label for="permalink">Permalink</label>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2">
|
||||||
|
<label for="text">Text</label>
|
||||||
|
<input type="radio" name="source" id="source_html" class="btn-check" value="HTML"
|
||||||
|
{%- if model.source == "HTML" %} checked="checked"{% endif %}>
|
||||||
|
<label class="btn btn-sm btn-outline-secondary" for="source_html">HTML</label>
|
||||||
|
<input type="radio" name="source" id="source_md" class="btn-check" value="Markdown"
|
||||||
|
{%- if model.source == "Markdown" %} checked="checked"{% endif %}>
|
||||||
|
<label class="btn btn-sm btn-outline-secondary" for="source_md">Markdown</label>
|
||||||
|
</div>
|
||||||
|
<div class="pb-3">
|
||||||
|
<textarea name="text" id="text" class="form-control" rows="10">{{ model.text }}</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-floating pb-3">
|
||||||
|
<input type="text" name="tags" id="tags" class="form-control" placeholder="Tags" required
|
||||||
|
value="{{ model.tags }}">
|
||||||
|
<label for="tags">Tags</label>
|
||||||
|
<div class="form-text">comma-delimited</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-lg-3">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Categories</legend>
|
||||||
|
{% for cat in categories %}
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" name="categoryIds" id="categoryId_{{ cat.id }}" class="form-check-input"
|
||||||
|
value="{{ cat.id }}" {% if model.category_ids contains cat.id %} checked="checked"{% endif %}>
|
||||||
|
<label for="categoryId_{{ cat.id }}" class="form-check-label"
|
||||||
|
{%- if cat.description %} title="{{ cat.description.value | escape }}"{% endif %}>
|
||||||
|
{%- for it in cat.parent_names %} ⟩ {% endfor %}{{ cat.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
{% if model.status == "Draft" %}
|
||||||
|
<div class="form-check pb-2">
|
||||||
|
<input type="checkbox" name="doPublish" id="doPublish" class="form-check-input" value="true">
|
||||||
|
<label for="doPublish" class="form-check-label">Publish This Post</label>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</article>
|
|
@ -31,9 +31,9 @@
|
||||||
<a href="#" class="text-danger">Delete</a>
|
<a href="#" class="text-danger">Delete</a>
|
||||||
</small>
|
</small>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ model.authors[post.author_id] }}</td>
|
<td>{{ post.author_name }}</td>
|
||||||
<td>{{ post.status }}</td>
|
<td>{{ post.status }}</td>
|
||||||
<td>{{ tags[post.id] }}</td>
|
<td>{{ post.tags | join: ", " }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%- endfor %}
|
{%- endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|
23
src/MyWebLog/themes/default/index.liquid
Normal file
23
src/MyWebLog/themes/default/index.liquid
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% if model.subtitle %}
|
||||||
|
<h2>{{ model.subtitle.value }}</h2>
|
||||||
|
{% endif %}
|
||||||
|
<section class="container" aria-label="The posts for the page">
|
||||||
|
{% for post in model.posts %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<article>
|
||||||
|
<h1>
|
||||||
|
<a href="/{{ post.permalink }}" title="Permanent link to "{{ post.title | escape }}"">
|
||||||
|
{{ post.title }}
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
Published on {{ post.published_on | date: "MMMM d, yyyy" }}
|
||||||
|
at {{ post.published_on | date: "h:mmtt" | downcase }}
|
||||||
|
</p>
|
||||||
|
{{ post.text }}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
|
@ -7,7 +7,7 @@
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
||||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="/themes/{{ web_log.theme_path }}/style.css">
|
<link rel="stylesheet" href="/themes/{{ web_log.theme_path }}/style.css">
|
||||||
<title>{{ page_title | escape }} « {{ web_log.name | escape }}</title>
|
<title>{{ page_title | escape }}{% if page_title %} « {% endif %}{{ web_log.name | escape }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
|
|
|
@ -11,3 +11,12 @@
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
fieldset {
|
||||||
|
border: solid 1px lightgray;
|
||||||
|
border-radius: .5rem;
|
||||||
|
padding: 0 1rem 1rem;
|
||||||
|
}
|
||||||
|
legend {
|
||||||
|
float: unset;
|
||||||
|
width: unset;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user