WIP on Fluid model/filters

This commit is contained in:
Daniel J. Summers 2024-08-26 21:42:47 -04:00
parent d047035173
commit 95be82cc84
4 changed files with 69 additions and 17 deletions

View File

@ -23,7 +23,6 @@ type ISession with
[<Literal>] [<Literal>]
let MESSAGES = "messages" let MESSAGES = "messages"
/// The HTTP item key for loading the session /// The HTTP item key for loading the session
let private sessionLoadedKey = "session-loaded" let private sessionLoadedKey = "session-loaded"
@ -132,6 +131,7 @@ let redirectToGet url : HttpHandler = fun _ ctx -> task {
} }
/// The MIME type for podcast episode JSON chapters /// The MIME type for podcast episode JSON chapters
[<Literal>]
let JSON_CHAPTERS = "application/json+chapters" let JSON_CHAPTERS = "application/json+chapters"
@ -185,10 +185,10 @@ let viewForTheme themeId template next ctx (viewCtx: AppViewContext) = task {
// Render view content... // Render view content...
match! Template.Cache.get themeId template ctx.Data with match! Template.Cache.get themeId template ctx.Data with
| Ok contentTemplate -> | Ok contentTemplate ->
let forLayout = { updated with Content = Template.render contentTemplate updated } let forLayout = { updated with Content = Template.render contentTemplate updated ctx.Data }
// ...then render that content with its layout // ...then render that content with its layout
match! Template.Cache.get themeId (if isHtmx ctx then "layout-partial" else "layout") ctx.Data with match! Template.Cache.get themeId (if isHtmx ctx then "layout-partial" else "layout") ctx.Data with
| Ok layoutTemplate -> return! htmlString (Template.render layoutTemplate forLayout) next ctx | Ok layoutTemplate -> return! htmlString (Template.render layoutTemplate forLayout ctx.Data) next ctx
| Error message -> return! Error.server message next ctx | Error message -> return! Error.server message next ctx
| Error message -> return! Error.server message next ctx | Error message -> return! Error.server message next ctx
} }
@ -199,7 +199,7 @@ let bareForTheme themeId template next ctx viewCtx = task {
let withContent = task { let withContent = task {
if updated.Content = "" then if updated.Content = "" then
match! Template.Cache.get themeId template ctx.Data with match! Template.Cache.get themeId template ctx.Data with
| Ok contentTemplate -> return Ok { updated with Content = Template.render contentTemplate updated } | Ok contentTemplate -> return Ok { updated with Content = Template.render contentTemplate updated ctx.Data }
| Error message -> return Error message | Error message -> return Error message
else else
return Ok viewCtx return Ok viewCtx
@ -210,7 +210,7 @@ let bareForTheme themeId template next ctx viewCtx = task {
match! Template.Cache.get themeId "layout-bare" ctx.Data with match! Template.Cache.get themeId "layout-bare" ctx.Data with
| Ok layoutTemplate -> | Ok layoutTemplate ->
return! return!
(messagesToHeaders completeCtx.Messages >=> htmlString (Template.render layoutTemplate completeCtx)) (messagesToHeaders completeCtx.Messages >=> htmlString (Template.render layoutTemplate completeCtx ctx.Data))
next ctx next ctx
| Error message -> return! Error.server message next ctx | Error message -> return! Error.server message next ctx
| Error message -> return! Error.server message next ctx | Error message -> return! Error.server message next ctx

View File

@ -27,7 +27,7 @@ type WebLogMiddleware(next: RequestDelegate, log: ILogger<WebLogMiddleware>) =
/// Middleware to check redirects for the current web log /// Middleware to check redirects for the current web log
type RedirectRuleMiddleware(next: RequestDelegate, log: ILogger<RedirectRuleMiddleware>) = type RedirectRuleMiddleware(next: RequestDelegate, _log: ILogger<RedirectRuleMiddleware>) =
/// Shorthand for case-insensitive string equality /// Shorthand for case-insensitive string equality
let ciEquals str1 str2 = let ciEquals str1 str2 =

View File

@ -1,8 +1,14 @@
module MyWebLog.Template module MyWebLog.Template
open System
open System.Collections.Generic
open System.IO
open System.Text
open Fluid open Fluid
open Fluid.Values open Fluid.Values
open Giraffe.ViewEngine open Giraffe.ViewEngine
open Microsoft.AspNetCore.Antiforgery
open Microsoft.Extensions.FileProviders
open MyWebLog open MyWebLog
open MyWebLog.ViewModels open MyWebLog.ViewModels
@ -48,10 +54,21 @@ module private Helpers =
/// Fluid template options customized with myWebLog filters /// Fluid template options customized with myWebLog filters
let options = let options () =
let sValue = StringValue >> VTask<FluidValue> let sValue = StringValue >> VTask<FluidValue>
let it = TemplateOptions.Default let it = TemplateOptions.Default
it.MemberAccessStrategy.MemberNameStrategy <- MemberNameStrategies.SnakeCase it.MemberAccessStrategy.MemberNameStrategy <- MemberNameStrategies.SnakeCase
[ // Domain types
typeof<CustomFeed>; typeof<Episode>; typeof<Episode option>; typeof<MetaItem>; typeof<Page>; typeof<RssOptions>
typeof<TagMap>; typeof<WebLog>
// View models
typeof<AppViewContext>; typeof<DisplayCategory>; typeof<DisplayPage>; typeof<EditPageModel>; typeof<PostDisplay>
typeof<PostListItem>; typeof<UserMessage>
// Framework types
typeof<AntiforgeryTokenSet>; typeof<DateTime option>; typeof<int option>; typeof<KeyValuePair>
typeof<MetaItem list>; typeof<string list>; typeof<string option>; typeof<TagMap list> ]
|> List.iter it.MemberAccessStrategy.Register
// A filter to generate an absolute link // A filter to generate an absolute link
it.Filters.AddFilter("absolute_link", fun input _ ctx -> sValue (permalink input ctx.App.WebLog.AbsoluteUrl)) it.Filters.AddFilter("absolute_link", fun input _ ctx -> sValue (permalink input ctx.App.WebLog.AbsoluteUrl))
@ -132,12 +149,14 @@ let options =
// A filter to retrieve the value of a meta item from a list // A filter to retrieve the value of a meta item from a list
// (shorter than `{% assign item = list | where: "Name", [name] | first %}{{ item.value }}`) // (shorter than `{% assign item = list | where: "Name", [name] | first %}{{ item.value }}`)
it.Filters.AddFilter("value", it.Filters.AddFilter("value",
fun input args _ -> fun input args ctx ->
let items = input.ToObjectValue() :?> MetaItem list let name = args.At(0).ToStringValue()
let name = args.At(0).ToStringValue() let picker (value: FluidValue) =
match items |> List.tryFind (fun it -> it.Name = name) with let item = value.ToObjectValue() :?> MetaItem
| Some item -> item.Value if item.Name = name then Some item.Value else None
| None -> $"-- {name} not found --" (input :?> ArrayValue).Values
|> Seq.tryPick picker
|> Option.defaultValue $"-- {name} not found --"
|> sValue) |> sValue)
it it
@ -234,11 +253,13 @@ let parser =
it it
open MyWebLog.Data
/// Cache for parsed templates /// Cache for parsed templates
module Cache = module Cache =
open System.Collections.Concurrent open System.Collections.Concurrent
open MyWebLog.Data
/// Cache of parsed templates /// Cache of parsed templates
let private _cache = ConcurrentDictionary<string, IFluidTemplate> () let private _cache = ConcurrentDictionary<string, IFluidTemplate> ()
@ -277,6 +298,37 @@ module Cache =
_cache.Clear() _cache.Clear()
/// A file provider to retrieve files by theme
type ThemeFileProvider(themeId: ThemeId, data: IData) =
interface IFileProvider with
member _.GetDirectoryContents _ =
raise <| NotImplementedException "The theme file provider does not support directory listings"
member _.GetFileInfo path =
match data.Theme.FindById themeId |> Async.AwaitTask |> Async.RunSynchronously with
| Some theme ->
match theme.Templates |> List.tryFind (fun t -> t.Name = path) with
| Some template ->
{ new IFileInfo with
member _.Exists = true
member _.IsDirectory = false
member _.LastModified = DateTimeOffset.Now
member _.Length = int64 template.Text.Length
member _.Name = template.Name.Split '/' |> Array.last
member _.PhysicalPath = null
member _.CreateReadStream() =
new MemoryStream(Encoding.UTF8.GetBytes template.Text) }
| None -> NotFoundFileInfo path
| None -> NotFoundFileInfo path
member _.Watch _ =
raise <| NotImplementedException "The theme file provider does not support watching for changes"
/// Render a template to a string /// Render a template to a string
let render (template: IFluidTemplate) (viewCtx: AppViewContext) = let render (template: IFluidTemplate) (viewCtx: AppViewContext) data =
template.Render(TemplateContext(viewCtx, options, true)) let opts = options ()
opts.FileProvider <- ThemeFileProvider(viewCtx.WebLog.ThemeId, data)
template.Render(TemplateContext(viewCtx, opts, true))

View File

@ -1,5 +1,5 @@
{ {
"Generator": "myWebLog 2.2", "Generator": "myWebLog 3",
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"MyWebLog.Handlers": "Information" "MyWebLog.Handlers": "Information"