V2 #1
@ -3,6 +3,7 @@ module MyWebLog.DotLiquidBespoke
|
||||
|
||||
open System
|
||||
open System.IO
|
||||
open System.Web
|
||||
open DotLiquid
|
||||
open MyWebLog.ViewModels
|
||||
|
||||
@ -22,11 +23,13 @@ let permalink (ctx : Context) (item : obj) (linkFunc : WebLog -> Permalink -> st
|
||||
| Some link -> linkFunc (webLog ctx) (Permalink link)
|
||||
| None -> $"alert('unknown item type {item.GetType().Name}')"
|
||||
|
||||
|
||||
/// A filter to generate an absolute link
|
||||
type AbsoluteLinkFilter () =
|
||||
static member AbsoluteLink (ctx : Context, item : obj) =
|
||||
permalink ctx item WebLog.absoluteUrl
|
||||
|
||||
|
||||
/// A filter to generate a link with posts categorized under the given category
|
||||
type CategoryLinkFilter () =
|
||||
static member CategoryLink (ctx : Context, catObj : obj) =
|
||||
@ -50,6 +53,7 @@ type EditPageLinkFilter () =
|
||||
|> function
|
||||
| Some pageId -> WebLog.relativeUrl (webLog ctx) (Permalink $"admin/page/{pageId}/edit")
|
||||
| None -> $"alert('unknown page object type {pageObj.GetType().Name}')"
|
||||
|
||||
|
||||
/// A filter to generate a link that will edit a post
|
||||
type EditPostLinkFilter () =
|
||||
@ -62,6 +66,7 @@ type EditPostLinkFilter () =
|
||||
|> function
|
||||
| Some postId -> WebLog.relativeUrl (webLog ctx) (Permalink $"admin/post/{postId}/edit")
|
||||
| None -> $"alert('unknown post object type {postObj.GetType().Name}')"
|
||||
|
||||
|
||||
/// A filter to generate nav links, highlighting the active link (exact match)
|
||||
type NavLinkFilter () =
|
||||
@ -78,11 +83,62 @@ type NavLinkFilter () =
|
||||
}
|
||||
|> Seq.fold (+) ""
|
||||
|
||||
|
||||
/// Create various items in the page header based on the state of the page being generated
|
||||
type PageHeadTag () =
|
||||
inherit Tag ()
|
||||
|
||||
override this.Render (context : Context, result : TextWriter) =
|
||||
let webLog = webLog context
|
||||
// spacer
|
||||
let s = " "
|
||||
let getBool name =
|
||||
context.Environments[0].[name] |> Option.ofObj |> Option.map Convert.ToBoolean |> Option.defaultValue false
|
||||
|
||||
result.WriteLine $"""<meta name="generator" content="{context.Environments[0].["generator"]}">"""
|
||||
|
||||
// Theme assets
|
||||
let has fileName = File.Exists (Path.Combine ("wwwroot", "themes", webLog.themePath, fileName))
|
||||
if has "style.css" then
|
||||
result.WriteLine $"""{s}<link rel="stylesheet" href="/themes/{webLog.themePath}/style.css">"""
|
||||
if has "favicon.ico" then
|
||||
result.WriteLine $"""{s}<link rel="icon" href="/themes/{webLog.themePath}/favicon.ico">"""
|
||||
|
||||
// RSS feeds and canonical URLs
|
||||
let feedLink title url =
|
||||
let escTitle = HttpUtility.HtmlAttributeEncode title
|
||||
let relUrl = WebLog.relativeUrl webLog (Permalink url)
|
||||
$"""{s}<link rel="alternate" type="application/rss+xml" title="{escTitle}" href="{relUrl}">"""
|
||||
|
||||
if webLog.rss.feedEnabled && getBool "is_home" then
|
||||
result.WriteLine (feedLink webLog.name webLog.rss.feedName)
|
||||
result.WriteLine $"""{s}<link rel="canonical" href="{WebLog.absoluteUrl webLog Permalink.empty}">"""
|
||||
|
||||
if webLog.rss.categoryEnabled && getBool "is_category_home" then
|
||||
let slug = context.Environments[0].["slug"] :?> string
|
||||
result.WriteLine (feedLink webLog.name $"category/{slug}/{webLog.rss.feedName}")
|
||||
|
||||
if webLog.rss.tagEnabled && getBool "is_tag_home" then
|
||||
let slug = context.Environments[0].["slug"] :?> string
|
||||
result.WriteLine (feedLink webLog.name $"tag/{slug}/{webLog.rss.feedName}")
|
||||
|
||||
if getBool "is_post" then
|
||||
let post = context.Environments[0].["model"] :?> PostDisplay
|
||||
let url = WebLog.absoluteUrl webLog (Permalink post.posts[0].permalink)
|
||||
result.WriteLine $"""{s}<link rel="canonical" href="{url}">"""
|
||||
|
||||
if getBool "is_page" then
|
||||
let page = context.Environments[0].["page"] :?> DisplayPage
|
||||
let url = WebLog.absoluteUrl webLog (Permalink page.permalink)
|
||||
result.WriteLine $"""{s}<link rel="canonical" href="{url}">"""
|
||||
|
||||
|
||||
/// A filter to generate a relative link
|
||||
type RelativeLinkFilter () =
|
||||
static member RelativeLink (ctx : Context, item : obj) =
|
||||
permalink ctx item WebLog.relativeUrl
|
||||
|
||||
|
||||
/// A filter to generate a link with posts tagged with the given tag
|
||||
type TagLinkFilter () =
|
||||
static member TagLink (ctx : Context, tag : string) =
|
||||
@ -92,6 +148,7 @@ type TagLinkFilter () =
|
||||
| Some tagMap -> tagMap.urlValue
|
||||
| None -> tag.Replace (" ", "+")
|
||||
|> function tagUrl -> WebLog.relativeUrl (webLog ctx) (Permalink $"tag/{tagUrl}/")
|
||||
|
||||
|
||||
/// Create links for a user to log on or off, and a dashboard link if they are logged off
|
||||
type UserLinksTag () =
|
||||
|
@ -33,28 +33,16 @@ let deriveFeedType (ctx : HttpContext) feedPath : (FeedType * int) option =
|
||||
debug (fun () -> "Found standard feed")
|
||||
Some (StandardFeed feedPath, postCount)
|
||||
| false ->
|
||||
// Category feed
|
||||
match CategoryCache.get ctx |> Array.tryFind (fun cat -> cat.slug = feedPath.Replace (name, "")) with
|
||||
| Some cat ->
|
||||
debug (fun () -> "Found category feed")
|
||||
Some (CategoryFeed (CategoryId cat.id, feedPath), postCount)
|
||||
// Category and tag feeds are handled by defined routes; check for custom feed
|
||||
match webLog.rss.customFeeds
|
||||
|> List.tryFind (fun it -> feedPath.EndsWith (Permalink.toString it.path)) with
|
||||
| Some feed ->
|
||||
debug (fun () -> "Found custom feed")
|
||||
Some (Custom (feed, feedPath),
|
||||
feed.podcast |> Option.map (fun p -> p.itemsInFeed) |> Option.defaultValue postCount)
|
||||
| None ->
|
||||
// Tag feed
|
||||
match feedPath.StartsWith "/tag/" with
|
||||
| true ->
|
||||
debug (fun () -> "Found tag feed")
|
||||
Some (TagFeed (feedPath.Replace("/tag/", "").Replace(name, ""), feedPath), postCount)
|
||||
| false ->
|
||||
// Custom feed
|
||||
match webLog.rss.customFeeds
|
||||
|> List.tryFind (fun it -> feedPath.EndsWith (Permalink.toString it.path)) with
|
||||
| Some feed ->
|
||||
debug (fun () -> "Found custom feed")
|
||||
Some (Custom (feed, feedPath),
|
||||
feed.podcast |> Option.map (fun p -> p.itemsInFeed) |> Option.defaultValue postCount)
|
||||
| None ->
|
||||
debug (fun () -> $"No matching feed found")
|
||||
None
|
||||
debug (fun () -> $"No matching feed found")
|
||||
None
|
||||
|
||||
/// Determine the function to retrieve posts for the given feed
|
||||
let private getFeedPosts (webLog : WebLog) feedType =
|
||||
@ -252,12 +240,41 @@ let private selfAndLink webLog feedType =
|
||||
|> function
|
||||
| path -> Permalink path, Permalink (path.Replace ($"/{webLog.rss.feedName}", ""))
|
||||
|
||||
/// Set the title and description of the feed based on its source
|
||||
let private setTitleAndDescription feedType (webLog : WebLog) (cats : DisplayCategory[]) (feed : SyndicationFeed) =
|
||||
let cleanText opt def = TextSyndicationContent (stripHtml (defaultArg opt def))
|
||||
match feedType with
|
||||
| StandardFeed _ ->
|
||||
feed.Title <- cleanText None webLog.name
|
||||
feed.Description <- cleanText webLog.subtitle webLog.name
|
||||
| CategoryFeed (CategoryId catId, _) ->
|
||||
let cat = cats |> Array.find (fun it -> it.id = catId)
|
||||
feed.Title <- cleanText None $"""{webLog.name} - "{stripHtml cat.name}" Category"""
|
||||
feed.Description <- cleanText cat.description $"""Posts categorized under "{cat.name}" """
|
||||
| TagFeed (tag, _) ->
|
||||
feed.Title <- cleanText None $"""{webLog.name} - "{tag}" Tag"""
|
||||
feed.Description <- cleanText None $"""Posts with the "{tag}" tag"""
|
||||
| Custom (custom, _) ->
|
||||
match custom.podcast with
|
||||
| Some podcast ->
|
||||
feed.Title <- cleanText None podcast.title
|
||||
feed.Description <- cleanText podcast.subtitle podcast.title
|
||||
| None ->
|
||||
match custom.source with
|
||||
| Category (CategoryId catId) ->
|
||||
let cat = cats |> Array.find (fun it -> it.id = catId)
|
||||
feed.Title <- cleanText None $"""{webLog.name} - "{stripHtml cat.name}" Category"""
|
||||
feed.Description <- cleanText cat.description $"""Posts categorized under "{cat.name}" """
|
||||
| Tag tag ->
|
||||
feed.Title <- cleanText None $"""{webLog.name} - "{tag}" Tag"""
|
||||
feed.Description <- cleanText None $"""Posts with the "{tag}" tag"""
|
||||
|
||||
/// Create a feed with a known non-zero-length list of posts
|
||||
let createFeed (feedType : FeedType) posts : HttpHandler = fun next ctx -> backgroundTask {
|
||||
let webLog = ctx.WebLog
|
||||
let conn = ctx.Conn
|
||||
let! authors = Post.getAuthors webLog posts conn
|
||||
let! tagMaps = Post.getTagMappings webLog posts conn
|
||||
let! authors = getAuthors webLog posts conn
|
||||
let! tagMaps = getTagMappings webLog posts conn
|
||||
let cats = CategoryCache.get ctx
|
||||
let podcast = match feedType with Custom (feed, _) when Option.isSome feed.podcast -> Some feed | _ -> None
|
||||
let self, link = selfAndLink webLog feedType
|
||||
@ -274,14 +291,13 @@ let createFeed (feedType : FeedType) posts : HttpHandler = fun next ctx -> backg
|
||||
|
||||
let feed = SyndicationFeed ()
|
||||
addNamespace feed "content" Namespace.content
|
||||
setTitleAndDescription feedType webLog cats feed
|
||||
|
||||
feed.Title <- TextSyndicationContent webLog.name
|
||||
feed.Description <- TextSyndicationContent <| defaultArg webLog.subtitle webLog.name
|
||||
feed.LastUpdatedTime <- DateTimeOffset <| (List.head posts).updatedOn
|
||||
feed.Generator <- generator ctx
|
||||
feed.Items <- posts |> Seq.ofList |> Seq.map toItem
|
||||
feed.Language <- "en"
|
||||
feed.Id <- webLog.urlBase
|
||||
feed.Id <- WebLog.absoluteUrl webLog link
|
||||
webLog.rss.copyright |> Option.iter (fun copy -> feed.Copyright <- TextSyndicationContent copy)
|
||||
|
||||
feed.Links.Add (SyndicationLink (Uri (WebLog.absoluteUrl webLog self), "self", "", "application/rss+xml", 0L))
|
||||
|
@ -160,6 +160,32 @@ let templatesForTheme (ctx : HttpContext) (typ : string) =
|
||||
}
|
||||
|> Array.ofSeq
|
||||
|
||||
/// Get all authors for a list of posts as metadata items
|
||||
let getAuthors (webLog : WebLog) (posts : Post list) conn =
|
||||
posts
|
||||
|> List.map (fun p -> p.authorId)
|
||||
|> List.distinct
|
||||
|> Data.WebLogUser.findNames webLog.id conn
|
||||
|
||||
/// Get all tag mappings for a list of posts as metadata items
|
||||
let getTagMappings (webLog : WebLog) (posts : Post list) =
|
||||
posts
|
||||
|> List.map (fun p -> p.tags)
|
||||
|> List.concat
|
||||
|> List.distinct
|
||||
|> fun tags -> Data.TagMap.findMappingForTags tags webLog.id
|
||||
|
||||
/// Get all category IDs for the given slug (includes owned subcategories)
|
||||
let getCategoryIds slug ctx =
|
||||
let allCats = CategoryCache.get ctx
|
||||
let cat = allCats |> Array.find (fun cat -> cat.slug = slug)
|
||||
// Category pages include posts in subcategories
|
||||
allCats
|
||||
|> Seq.ofArray
|
||||
|> Seq.filter (fun c -> c.id = cat.id || Array.contains cat.name c.parentNames)
|
||||
|> Seq.map (fun c -> CategoryId c.id)
|
||||
|> List.ofSeq
|
||||
|
||||
open Microsoft.Extensions.Logging
|
||||
|
||||
/// Log level for debugging
|
||||
|
@ -2,10 +2,21 @@
|
||||
module MyWebLog.Handlers.Post
|
||||
|
||||
open System
|
||||
open MyWebLog
|
||||
|
||||
/// Parse a slug and page number from an "everything else" URL
|
||||
let private parseSlugAndPage (slugAndPage : string seq) =
|
||||
let slugs = (slugAndPage |> Seq.skip 1 |> Seq.head).Split "/" |> Array.filter (fun it -> it <> "")
|
||||
let private parseSlugAndPage webLog (slugAndPage : string seq) =
|
||||
let fullPath = slugAndPage |> Seq.head
|
||||
let slugPath = slugAndPage |> Seq.skip 1 |> Seq.head
|
||||
let slugs, isFeed =
|
||||
let feedName = $"/{webLog.rss.feedName}"
|
||||
let notBlank = Array.filter (fun it -> it <> "")
|
||||
if ( (webLog.rss.categoryEnabled && fullPath.StartsWith "/category/")
|
||||
|| (webLog.rss.tagEnabled && fullPath.StartsWith "/tag/" ))
|
||||
&& slugPath.EndsWith feedName then
|
||||
notBlank (slugPath.Replace(feedName, "").Split "/"), true
|
||||
else
|
||||
notBlank (slugPath.Split "/"), false
|
||||
let pageIdx = Array.IndexOf (slugs, "page")
|
||||
let pageNbr =
|
||||
match pageIdx with
|
||||
@ -13,7 +24,7 @@ let private parseSlugAndPage (slugAndPage : string seq) =
|
||||
| idx when idx + 2 = slugs.Length -> Some (int slugs[pageIdx + 1])
|
||||
| _ -> None
|
||||
let slugParts = if pageIdx > 0 then Array.truncate pageIdx slugs else slugs
|
||||
pageNbr, String.Join ("/", slugParts)
|
||||
pageNbr, String.Join ("/", slugParts), isFeed
|
||||
|
||||
/// The type of post list being prepared
|
||||
type ListType =
|
||||
@ -23,23 +34,6 @@ type ListType =
|
||||
| SinglePost
|
||||
| TagList
|
||||
|
||||
open MyWebLog
|
||||
|
||||
/// Get all authors for a list of posts as metadata items
|
||||
let getAuthors (webLog : WebLog) (posts : Post list) conn =
|
||||
posts
|
||||
|> List.map (fun p -> p.authorId)
|
||||
|> List.distinct
|
||||
|> Data.WebLogUser.findNames webLog.id conn
|
||||
|
||||
/// Get all tag mappings for a list of posts as metadata items
|
||||
let getTagMappings (webLog : WebLog) (posts : Post list) =
|
||||
posts
|
||||
|> List.map (fun p -> p.tags)
|
||||
|> List.concat
|
||||
|> List.distinct
|
||||
|> fun tags -> Data.TagMap.findMappingForTags tags webLog.id
|
||||
|
||||
open System.Threading.Tasks
|
||||
open DotLiquid
|
||||
open MyWebLog.ViewModels
|
||||
@ -91,7 +85,12 @@ let preparePostList webLog posts listType (url : string) pageNbr perPage ctx con
|
||||
olderLink = olderLink
|
||||
olderName = olderPost |> Option.map (fun p -> p.title)
|
||||
}
|
||||
return Hash.FromAnonymousObject {| model = model; categories = CategoryCache.get ctx; tag_mappings = tagMappings |}
|
||||
return Hash.FromAnonymousObject {|
|
||||
model = model
|
||||
categories = CategoryCache.get ctx
|
||||
tag_mappings = tagMappings
|
||||
is_post = match listType with SinglePost -> true | _ -> false
|
||||
|}
|
||||
}
|
||||
|
||||
open Giraffe
|
||||
@ -117,27 +116,30 @@ let pageOfPosts pageNbr : HttpHandler = fun next ctx -> task {
|
||||
let pageOfCategorizedPosts slugAndPage : HttpHandler = fun next ctx -> task {
|
||||
let webLog = ctx.WebLog
|
||||
let conn = ctx.Conn
|
||||
match parseSlugAndPage slugAndPage with
|
||||
| Some pageNbr, slug ->
|
||||
match parseSlugAndPage webLog slugAndPage with
|
||||
| Some pageNbr, slug, isFeed ->
|
||||
let allCats = CategoryCache.get ctx
|
||||
let cat = allCats |> Array.find (fun cat -> cat.slug = slug)
|
||||
// Category pages include posts in subcategories
|
||||
let catIds =
|
||||
allCats
|
||||
|> Seq.ofArray
|
||||
|> Seq.filter (fun c -> c.id = cat.id || Array.contains cat.name c.parentNames)
|
||||
|> Seq.map (fun c -> CategoryId c.id)
|
||||
|> List.ofSeq
|
||||
match! Data.Post.findPageOfCategorizedPosts webLog.id catIds pageNbr webLog.postsPerPage conn with
|
||||
| posts when List.length posts > 0 ->
|
||||
let! hash = preparePostList webLog posts CategoryList cat.slug pageNbr webLog.postsPerPage ctx conn
|
||||
let pgTitle = if pageNbr = 1 then "" else $""" <small class="archive-pg-nbr">(Page {pageNbr})</small>"""
|
||||
hash.Add ("page_title", $"{cat.name}: Category Archive{pgTitle}")
|
||||
hash.Add ("subtitle", cat.description.Value)
|
||||
hash.Add ("is_category", true)
|
||||
return! themedView "index" next ctx hash
|
||||
| _ -> return! Error.notFound next ctx
|
||||
| None, _ -> return! Error.notFound next ctx
|
||||
if isFeed then
|
||||
return! Feed.generate (Feed.CategoryFeed ((CategoryId cat.id), $"category/{slug}/{webLog.rss.feedName}"))
|
||||
(defaultArg webLog.rss.itemsInFeed webLog.postsPerPage) next ctx
|
||||
else
|
||||
let allCats = CategoryCache.get ctx
|
||||
let cat = allCats |> Array.find (fun cat -> cat.slug = slug)
|
||||
// Category pages include posts in subcategories
|
||||
match! Data.Post.findPageOfCategorizedPosts webLog.id (getCategoryIds slug ctx) pageNbr webLog.postsPerPage
|
||||
conn with
|
||||
| posts when List.length posts > 0 ->
|
||||
let! hash = preparePostList webLog posts CategoryList cat.slug pageNbr webLog.postsPerPage ctx conn
|
||||
let pgTitle = if pageNbr = 1 then "" else $""" <small class="archive-pg-nbr">(Page {pageNbr})</small>"""
|
||||
hash.Add ("page_title", $"{cat.name}: Category Archive{pgTitle}")
|
||||
hash.Add ("subtitle", defaultArg cat.description "")
|
||||
hash.Add ("is_category", true)
|
||||
hash.Add ("is_category_home", (pageNbr = 1))
|
||||
hash.Add ("slug", slug)
|
||||
return! themedView "index" next ctx hash
|
||||
| _ -> return! Error.notFound next ctx
|
||||
| None, _, _ -> return! Error.notFound next ctx
|
||||
}
|
||||
|
||||
open System.Web
|
||||
@ -147,33 +149,39 @@ open System.Web
|
||||
let pageOfTaggedPosts slugAndPage : HttpHandler = fun next ctx -> task {
|
||||
let webLog = ctx.WebLog
|
||||
let conn = ctx.Conn
|
||||
match parseSlugAndPage slugAndPage with
|
||||
| Some pageNbr, rawTag ->
|
||||
match parseSlugAndPage webLog slugAndPage with
|
||||
| Some pageNbr, rawTag, isFeed ->
|
||||
let urlTag = HttpUtility.UrlDecode rawTag
|
||||
let! tag = backgroundTask {
|
||||
match! Data.TagMap.findByUrlValue urlTag webLog.id conn with
|
||||
| Some m -> return m.tag
|
||||
| None -> return urlTag
|
||||
}
|
||||
match! Data.Post.findPageOfTaggedPosts webLog.id tag pageNbr webLog.postsPerPage conn with
|
||||
| posts when List.length posts > 0 ->
|
||||
let! hash = preparePostList webLog posts TagList rawTag pageNbr webLog.postsPerPage ctx conn
|
||||
let pgTitle = if pageNbr = 1 then "" else $""" <small class="archive-pg-nbr">(Page {pageNbr})</small>"""
|
||||
hash.Add ("page_title", $"Posts Tagged “{tag}”{pgTitle}")
|
||||
hash.Add ("is_tag", true)
|
||||
return! themedView "index" next ctx hash
|
||||
// Other systems use hyphens for spaces; redirect if this is an old tag link
|
||||
| _ ->
|
||||
let spacedTag = tag.Replace ("-", " ")
|
||||
match! Data.Post.findPageOfTaggedPosts webLog.id spacedTag pageNbr 1 conn with
|
||||
if isFeed then
|
||||
return! Feed.generate (Feed.TagFeed (tag, $"tag/{rawTag}/{webLog.rss.feedName}"))
|
||||
(defaultArg webLog.rss.itemsInFeed webLog.postsPerPage) next ctx
|
||||
else
|
||||
match! Data.Post.findPageOfTaggedPosts webLog.id tag pageNbr webLog.postsPerPage conn with
|
||||
| posts when List.length posts > 0 ->
|
||||
let endUrl = if pageNbr = 1 then "" else $"page/{pageNbr}"
|
||||
return!
|
||||
redirectTo true
|
||||
(WebLog.relativeUrl webLog (Permalink $"""tag/{spacedTag.Replace (" ", "+")}/{endUrl}"""))
|
||||
next ctx
|
||||
| _ -> return! Error.notFound next ctx
|
||||
| None, _ -> return! Error.notFound next ctx
|
||||
let! hash = preparePostList webLog posts TagList rawTag pageNbr webLog.postsPerPage ctx conn
|
||||
let pgTitle = if pageNbr = 1 then "" else $""" <small class="archive-pg-nbr">(Page {pageNbr})</small>"""
|
||||
hash.Add ("page_title", $"Posts Tagged “{tag}”{pgTitle}")
|
||||
hash.Add ("is_tag", true)
|
||||
hash.Add ("is_tag_home", (pageNbr = 1))
|
||||
hash.Add ("slug", rawTag)
|
||||
return! themedView "index" next ctx hash
|
||||
// Other systems use hyphens for spaces; redirect if this is an old tag link
|
||||
| _ ->
|
||||
let spacedTag = tag.Replace ("-", " ")
|
||||
match! Data.Post.findPageOfTaggedPosts webLog.id spacedTag pageNbr 1 conn with
|
||||
| posts when List.length posts > 0 ->
|
||||
let endUrl = if pageNbr = 1 then "" else $"page/{pageNbr}"
|
||||
return!
|
||||
redirectTo true
|
||||
(WebLog.relativeUrl webLog (Permalink $"""tag/{spacedTag.Replace (" ", "+")}/{endUrl}"""))
|
||||
next ctx
|
||||
| _ -> return! Error.notFound next ctx
|
||||
| None, _, _ -> return! Error.notFound next ctx
|
||||
}
|
||||
|
||||
// GET /
|
||||
|
@ -39,7 +39,11 @@ module CatchAll =
|
||||
| Some page ->
|
||||
debug (fun () -> $"Found page by permalink")
|
||||
yield fun next ctx ->
|
||||
Hash.FromAnonymousObject {| page = DisplayPage.fromPage webLog page; page_title = page.title |}
|
||||
Hash.FromAnonymousObject {|
|
||||
page = DisplayPage.fromPage webLog page
|
||||
page_title = page.title
|
||||
is_page = true
|
||||
|}
|
||||
|> themedView (defaultArg page.template "single-page") next ctx
|
||||
| None -> ()
|
||||
// RSS feed
|
||||
|
@ -12,8 +12,8 @@
|
||||
<Compile Include="Handlers\Error.fs" />
|
||||
<Compile Include="Handlers\Helpers.fs" />
|
||||
<Compile Include="Handlers\Admin.fs" />
|
||||
<Compile Include="Handlers\Post.fs" />
|
||||
<Compile Include="Handlers\Feed.fs" />
|
||||
<Compile Include="Handlers\Post.fs" />
|
||||
<Compile Include="Handlers\User.fs" />
|
||||
<Compile Include="Handlers\Routes.fs" />
|
||||
<Compile Include="DotLiquidBespoke.fs" />
|
||||
|
@ -215,6 +215,7 @@ let main args =
|
||||
]
|
||||
|> List.iter Template.RegisterFilter
|
||||
|
||||
Template.RegisterTag<PageHeadTag> "page_head"
|
||||
Template.RegisterTag<UserLinksTag> "user_links"
|
||||
|
||||
[ // Domain types
|
||||
|
@ -3,11 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="generator" content="{{ generator }}">
|
||||
<title>{{ page_title }} » Bit Badger Solutions</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Oswald|Raleway">
|
||||
<link rel="stylesheet" href="/themes/{{ web_log.theme_path }}/style.css">
|
||||
<link rel="icon" href="/themes/{{ web_log.theme_path }}/favicon.ico">
|
||||
{% page_head -%}
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
|
@ -4,25 +4,19 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="generator" content="{{ generator }}">
|
||||
<title>
|
||||
{%- if is_home %}
|
||||
{%- if is_home -%}
|
||||
{{ web_log.name }}{% if web_log.subtitle %} | {{ web_log.subtitle.value }}{% endif %}
|
||||
{%- else %}
|
||||
{%- else -%}
|
||||
{{ page_title | strip_html }}{% if page_title and page_title != "" %} » {% endif %}{{ web_log.name }}
|
||||
{%- endif %}
|
||||
{%- endif -%}
|
||||
</title>
|
||||
<link rel="preload" href="https://fonts.googleapis.com/css?family=Quicksand|Oswald" as="style">
|
||||
<link rel="preload" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.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="/themes/{{ web_log.theme_path }}/style.css">
|
||||
{%- if is_home %}
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ web_log.name | escape }}"
|
||||
href="{{ "feed.xml" | relative_link }}">
|
||||
<link rel="canonical" href="{{ "" | absolute_url }}">
|
||||
{%- endif %}
|
||||
<script src="/themes/{{ web_log.theme_path }}/djs.js"></script>
|
||||
{% page_head -%}
|
||||
</head>
|
||||
<body>
|
||||
<nav class="site-header" role="navigation">
|
||||
|
@ -3,11 +3,10 @@
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<meta name="generator" content="{{ generator }}">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css"
|
||||
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/themes/{{ web_log.theme_path }}/style.css">
|
||||
<title>{{ page_title | escape }}{% if page_title %} « {% endif %}{{ web_log.name | escape }}</title>
|
||||
{% page_head -%}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
|
@ -4,21 +4,14 @@
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="generator" content="{{ generator }}">
|
||||
<title>
|
||||
{%- if is_home %}
|
||||
{%- if is_home -%}
|
||||
{{ web_log.name }}{% if web_log.subtitle %} | {{ web_log.subtitle.value }}{% endif %}
|
||||
{%- else %}
|
||||
{%- else -%}
|
||||
{{ page_title | strip_html }}{% if page_title and page_title != "" %} » {% endif %}{{ web_log.name }}
|
||||
{%- endif %}
|
||||
{%- endif -%}
|
||||
</title>
|
||||
<link rel="stylesheet" href="/themes/{{ web_log.theme_path }}/style.css">
|
||||
{%- if is_home %}
|
||||
<link rel="alternate" type="application/rss+xml" title="{{ web_log.name }}"
|
||||
href="{{ "feed.xml" | absolute_link }}">
|
||||
<link rel="canonical" href="{{ "" | absolute_link }}">
|
||||
{%- endif %}
|
||||
<link rel="icon" href="/themes/bit-badger/favicon.ico">
|
||||
{% page_head -%}
|
||||
</head>
|
||||
<body>
|
||||
<header class="site-header">
|
||||
|
BIN
src/MyWebLog/wwwroot/themes/tech-blog/favicon.ico
Normal file
BIN
src/MyWebLog/wwwroot/themes/tech-blog/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.3 KiB |
Loading…
Reference in New Issue
Block a user