Category / tag lists, logo

Category and tag list pages now work; added footer logo to default theme
with version and page load time tooltip
This commit is contained in:
Daniel J. Summers 2016-07-25 19:54:45 -05:00
parent 7c99da8cb5
commit d3712dc562
23 changed files with 186 additions and 98 deletions

View File

@ -120,7 +120,7 @@ let deleteCategory conn cat =
/// Get a category by its slug /// Get a category by its slug
let tryFindCategoryBySlug conn (webLogId : string) (slug : string) = let tryFindCategoryBySlug conn (webLogId : string) (slug : string) =
r.Table(Table.Category) r.Table(Table.Category)
.GetAll(webLogId, slug).OptArg("index", "slug") .GetAll(r.Array(webLogId, slug)).OptArg("index", "slug")
.RunCursorAsync<Category>(conn) .RunCursorAsync<Category>(conn)
|> await |> await
|> Seq.tryHead |> Seq.tryHead

View File

@ -31,7 +31,7 @@ let tryFindPageWithoutRevisions conn webLogId pageId : Page option =
/// Find a page by its permalink /// Find a page by its permalink
let tryFindPageByPermalink conn (webLogId : string) (permalink : string) = let tryFindPageByPermalink conn (webLogId : string) (permalink : string) =
r.Table(Table.Page) r.Table(Table.Page)
.GetAll(webLogId, permalink).OptArg("index", "permalink") .GetAll(r.Array(webLogId, permalink)).OptArg("index", "permalink")
.Without("revisions") .Without("revisions")
.RunCursorAsync<Page>(conn) .RunCursorAsync<Page>(conn)
|> await |> await

View File

@ -48,13 +48,13 @@ let findPageOfPublishedPosts conn webLogId pageNbr nbrPerPage =
/// Get a page of published posts assigned to a given category /// Get a page of published posts assigned to a given category
let findPageOfCategorizedPosts conn webLogId (categoryId : string) pageNbr nbrPerPage = let findPageOfCategorizedPosts conn webLogId (categoryId : string) pageNbr nbrPerPage =
(publishedPosts webLogId) (publishedPosts webLogId)
.Filter(fun p -> p.["categoryIds"].Contains(categoryId)) .Filter(ReqlFunction1(fun p -> upcast p.["categoryIds"].Contains(categoryId)))
|> toPostList conn pageNbr nbrPerPage |> toPostList conn pageNbr nbrPerPage
/// Get a page of published posts tagged with a given tag /// Get a page of published posts tagged with a given tag
let findPageOfTaggedPosts conn webLogId (tag : string) pageNbr nbrPerPage = let findPageOfTaggedPosts conn webLogId (tag : string) pageNbr nbrPerPage =
(publishedPosts webLogId) (publishedPosts webLogId)
.Filter(fun p -> p.["tags"].Contains(tag)) .Filter(ReqlFunction1(fun p -> upcast p.["tags"].Contains(tag)))
|> toPostList conn pageNbr nbrPerPage |> toPostList conn pageNbr nbrPerPage
/// Try to get the next newest post from the given post /// Try to get the next newest post from the given post
@ -103,23 +103,30 @@ let tryFindPost conn webLogId postId : Post option =
| post -> Some <| unbox post | post -> Some <| unbox post
/// Try to find a post by its permalink /// Try to find a post by its permalink
// TODO: see if we can make .Merge work for page list even though the attribute is ignored
// (needs to be ignored for serialization, but included for deserialization)
let tryFindPostByPermalink conn webLogId permalink = let tryFindPostByPermalink conn webLogId permalink =
r.Table(Table.Post) match r.Table(Table.Post)
.GetAll(r.Array(webLogId, permalink)).OptArg("index", "permalink") .GetAll(r.Array(webLogId, permalink)).OptArg("index", "permalink")
.Filter(fun p -> p.["status"].Eq(PostStatus.Published)) .Filter(fun p -> p.["status"].Eq(PostStatus.Published))
.Without("revisions") .Without("revisions")
.Merge(fun post -> r.HashMap("categories", .RunCursorAsync<Post>(conn)
post.["categoryIds"] |> await
.Map(ReqlFunction1(fun cat -> upcast r.Table(Table.Category).Get(cat).Without("children"))) |> Seq.tryHead with
.CoerceTo("array"))) | Some p -> Some { p with categories = r.Table(Table.Category)
.Merge(fun post -> r.HashMap("comments", .GetAll(p.categoryIds |> List.toArray)
r.Table(Table.Comment) .Without("children")
.GetAll(post.["id"]).OptArg("index", "postId") .OrderBy("name")
.OrderBy("postedOn") .RunListAsync<Category>(conn)
.CoerceTo("array"))) |> await
.RunCursorAsync<Post>(conn) |> Seq.toList
|> await comments = r.Table(Table.Comment)
|> Seq.tryHead .GetAll(p.id).OptArg("index", "postId")
.OrderBy("postedOn")
.RunListAsync<Comment>(conn)
|> await
|> Seq.toList }
| None -> None
/// Try to find a post by its prior permalink /// Try to find a post by its prior permalink
let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) = let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) =

View File

@ -22,7 +22,7 @@
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel> <WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\myWebLog.Data.XML</DocumentationFile> <DocumentationFile>bin\Debug\myWebLog.Data.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>

View File

@ -303,6 +303,15 @@ namespace myWebLog {
} }
} }
/// <summary>
/// Looks up a localized string similar to Loaded in.
/// </summary>
public static string LoadedIn {
get {
return ResourceManager.GetString("LoadedIn", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Log Off. /// Looks up a localized string similar to Log Off.
/// </summary> /// </summary>
@ -537,6 +546,15 @@ namespace myWebLog {
} }
} }
/// <summary>
/// Looks up a localized string similar to Powered by.
/// </summary>
public static string PoweredBy {
get {
return ResourceManager.GetString("PoweredBy", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Previous Post. /// Looks up a localized string similar to Previous Post.
/// </summary> /// </summary>
@ -573,6 +591,15 @@ namespace myWebLog {
} }
} }
/// <summary>
/// Looks up a localized string similar to Seconds.
/// </summary>
public static string Seconds {
get {
return ResourceManager.GetString("Seconds", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Show in Page List. /// Looks up a localized string similar to Show in Page List.
/// </summary> /// </summary>

View File

@ -318,4 +318,13 @@
<data name="Warning" xml:space="preserve"> <data name="Warning" xml:space="preserve">
<value>Warning</value> <value>Warning</value>
</data> </data>
<data name="LoadedIn" xml:space="preserve">
<value>Loaded in</value>
</data>
<data name="PoweredBy" xml:space="preserve">
<value>Powered by</value>
</data>
<data name="Seconds" xml:space="preserve">
<value>Seconds</value>
</data>
</root> </root>

View File

@ -10,11 +10,11 @@ type AdminModule(conn : IConnection) as this =
inherit NancyModule("/admin") inherit NancyModule("/admin")
do do
this.Get.["/"] <- fun _ -> upcast this.Dashboard () this.Get.["/"] <- fun _ -> this.Dashboard ()
/// Admin dashboard /// Admin dashboard
member this.Dashboard () = member this.Dashboard () =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let model = DashboardModel(this.Context, this.WebLog, findDashboardCounts conn this.WebLog.id) let model = DashboardModel(this.Context, this.WebLog, findDashboardCounts conn this.WebLog.id)
model.pageTitle <- Resources.Dashboard model.pageTitle <- Resources.Dashboard
this.View.["admin/dashboard", model] upcast this.View.["admin/dashboard", model]

View File

@ -12,10 +12,10 @@ type CategoryModule(conn : IConnection) as this =
inherit NancyModule() inherit NancyModule()
do do
this.Get .["/categories" ] <- fun _ -> upcast this.CategoryList () this.Get .["/categories" ] <- fun _ -> this.CategoryList ()
this.Get .["/category/{id}/edit" ] <- fun parms -> upcast this.EditCategory (downcast parms) this.Get .["/category/{id}/edit" ] <- fun parms -> this.EditCategory (downcast parms)
this.Post .["/category/{id}/edit" ] <- fun parms -> upcast this.SaveCategory (downcast parms) this.Post .["/category/{id}/edit" ] <- fun parms -> this.SaveCategory (downcast parms)
this.Delete.["/category/{id}/delete"] <- fun parms -> upcast this.DeleteCategory (downcast parms) this.Delete.["/category/{id}/delete"] <- fun parms -> this.DeleteCategory (downcast parms)
/// Display a list of categories /// Display a list of categories
member this.CategoryList () = member this.CategoryList () =
@ -23,7 +23,7 @@ type CategoryModule(conn : IConnection) as this =
let model = CategoryListModel(this.Context, this.WebLog, let model = CategoryListModel(this.Context, this.WebLog,
(getAllCategories conn this.WebLog.id (getAllCategories conn this.WebLog.id
|> List.map (fun cat -> IndentedCategory.create cat (fun _ -> false)))) |> List.map (fun cat -> IndentedCategory.create cat (fun _ -> false))))
this.View.["/admin/category/list", model] upcast this.View.["/admin/category/list", model]
/// Edit a category /// Edit a category
member this.EditCategory (parameters : DynamicDictionary) = member this.EditCategory (parameters : DynamicDictionary) =
@ -39,7 +39,7 @@ type CategoryModule(conn : IConnection) as this =
model.categories <- getAllCategories conn this.WebLog.id model.categories <- getAllCategories conn this.WebLog.id
|> List.map (fun cat -> IndentedCategory.create cat |> List.map (fun cat -> IndentedCategory.create cat
(fun c -> c = defaultArg (fst cat).parentId "")) (fun c -> c = defaultArg (fst cat).parentId ""))
this.View.["admin/category/edit", model] upcast this.View.["admin/category/edit", model]
| None -> this.NotFound () | None -> this.NotFound ()
/// Save a category /// Save a category

View File

@ -13,17 +13,18 @@ type NancyModule with
member this.WebLog = this.Context.Items.[Keys.WebLog] :?> WebLog member this.WebLog = this.Context.Items.[Keys.WebLog] :?> WebLog
/// Display a view using the theme specified for the web log /// Display a view using the theme specified for the web log
member this.ThemedView view model = this.View.[(sprintf "themes/%s/%s" this.WebLog.themePath view), model] member this.ThemedView view (model : MyWebLogModel) : obj =
upcast this.View.[(sprintf "themes/%s/%s" this.WebLog.themePath view), model]
/// Return a 404 /// Return a 404
member this.NotFound () = this.Negotiate.WithStatusCode 404 member this.NotFound () : obj = upcast HttpStatusCode.NotFound
/// Redirect a request, storing messages in the session if they exist /// Redirect a request, storing messages in the session if they exist
member this.Redirect url (model : MyWebLogModel) = member this.Redirect url (model : MyWebLogModel) : obj =
match List.length model.messages with match List.length model.messages with
| 0 -> () | 0 -> ()
| _ -> this.Session.[Keys.Messages] <- model.messages | _ -> this.Session.[Keys.Messages] <- model.messages
this.Negotiate.WithHeader("Location", url).WithStatusCode(HttpStatusCode.TemporaryRedirect) upcast this.Response.AsRedirect(url).WithStatusCode HttpStatusCode.TemporaryRedirect
/// Require a specific level of access for the current web log /// Require a specific level of access for the current web log
member this.RequiresAccessLevel level = member this.RequiresAccessLevel level =

View File

@ -14,22 +14,22 @@ type PageModule(conn : IConnection, clock : IClock) as this =
inherit NancyModule() inherit NancyModule()
do do
this.Get .["/pages" ] <- fun _ -> upcast this.PageList () this.Get .["/pages" ] <- fun _ -> this.PageList ()
this.Get .["/page/{id}/edit" ] <- fun parms -> upcast this.EditPage (downcast parms) this.Get .["/page/{id}/edit" ] <- fun parms -> this.EditPage (downcast parms)
this.Post .["/page/{id}/edit" ] <- fun parms -> upcast this.SavePage (downcast parms) this.Post .["/page/{id}/edit" ] <- fun parms -> this.SavePage (downcast parms)
this.Delete.["/page/{id}/delete"] <- fun parms -> upcast this.DeletePage (downcast parms) this.Delete.["/page/{id}/delete"] <- fun parms -> this.DeletePage (downcast parms)
/// List all pages /// List all pages
member this.PageList () = member this.PageList () =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let model = PagesModel(this.Context, this.WebLog, findAllPages conn this.WebLog.id) let model = PagesModel(this.Context, this.WebLog, findAllPages conn this.WebLog.id)
model.pageTitle <- Resources.Pages model.pageTitle <- Resources.Pages
this.View.["admin/page/list", model] upcast this.View.["admin/page/list", model]
/// Edit a page /// Edit a page
member this.EditPage (parameters : DynamicDictionary) = member this.EditPage (parameters : DynamicDictionary) =
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let pageId : string = downcast parameters.["id"] let pageId = parameters.["id"].ToString ()
match (match pageId with match (match pageId with
| "new" -> Some Page.empty | "new" -> Some Page.empty
| _ -> tryFindPage conn this.WebLog.id pageId) with | _ -> tryFindPage conn this.WebLog.id pageId) with
@ -42,16 +42,16 @@ type PageModule(conn : IConnection, clock : IClock) as this =
model.pageTitle <- match pageId with model.pageTitle <- match pageId with
| "new" -> Resources.AddNewPage | "new" -> Resources.AddNewPage
| _ -> Resources.EditPage | _ -> Resources.EditPage
this.View.["admin/page/edit"] upcast this.View.["admin/page/edit"]
| None -> this.NotFound () | None -> this.NotFound ()
/// Save a page /// Save a page
member this.SavePage (parameters : DynamicDictionary) = member this.SavePage (parameters : DynamicDictionary) =
this.ValidateCsrfToken () this.ValidateCsrfToken ()
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let pageId : string = downcast parameters.["id"] let pageId = parameters.["id"].ToString ()
let form = this.Bind<EditPageForm> () let form = this.Bind<EditPageForm> ()
let now = clock.Now.Ticks let now = clock.Now.Ticks
match (match pageId with match (match pageId with
| "new" -> Some Page.empty | "new" -> Some Page.empty
| _ -> tryFindPage conn this.WebLog.id pageId) with | _ -> tryFindPage conn this.WebLog.id pageId) with
@ -84,7 +84,7 @@ type PageModule(conn : IConnection, clock : IClock) as this =
member this.DeletePage (parameters : DynamicDictionary) = member this.DeletePage (parameters : DynamicDictionary) =
this.ValidateCsrfToken () this.ValidateCsrfToken ()
this.RequiresAccessLevel AuthorizationLevel.Administrator this.RequiresAccessLevel AuthorizationLevel.Administrator
let pageId : string = downcast parameters.["id"] let pageId = parameters.["id"].ToString ()
match tryFindPageWithoutRevisions conn this.WebLog.id pageId with match tryFindPageWithoutRevisions conn this.WebLog.id pageId with
| Some page -> deletePage conn page.webLogId page.id | Some page -> deletePage conn page.webLogId page.id
let model = MyWebLogModel(this.Context, this.WebLog) let model = MyWebLogModel(this.Context, this.WebLog)

View File

@ -24,17 +24,17 @@ type PostModule(conn : IConnection, clock : IClock) as this =
let forDisplay posts = posts |> List.map (fun post -> PostForDisplay(this.WebLog, post)) let forDisplay posts = posts |> List.map (fun post -> PostForDisplay(this.WebLog, post))
do do
this.Get .["/" ] <- fun _ -> upcast this.HomePage () this.Get .["/" ] <- fun _ -> this.HomePage ()
this.Get .["/{permalink*}" ] <- fun parms -> upcast this.CatchAll (downcast parms) this.Get .["/{permalink*}" ] <- fun parms -> this.CatchAll (downcast parms)
this.Get .["/posts/page/{page:int}" ] <- fun parms -> upcast this.PublishedPostsPage (getPage <| downcast parms) this.Get .["/posts/page/{page:int}" ] <- fun parms -> this.PublishedPostsPage (getPage <| downcast parms)
this.Get .["/category/{slug}" ] <- fun parms -> upcast this.CategorizedPosts (downcast parms) this.Get .["/category/{slug}" ] <- fun parms -> this.CategorizedPosts (downcast parms)
this.Get .["/category/{slug}/page/{page:int}"] <- fun parms -> upcast this.CategorizedPosts (downcast parms) this.Get .["/category/{slug}/page/{page:int}"] <- fun parms -> this.CategorizedPosts (downcast parms)
this.Get .["/tag/{tag}" ] <- fun parms -> upcast this.TaggedPosts (downcast parms) this.Get .["/tag/{tag}" ] <- fun parms -> this.TaggedPosts (downcast parms)
this.Get .["/tag/{tag}/page/{page:int}" ] <- fun parms -> upcast this.TaggedPosts (downcast parms) this.Get .["/tag/{tag}/page/{page:int}" ] <- fun parms -> this.TaggedPosts (downcast parms)
this.Get .["/posts/list" ] <- fun _ -> upcast this.PostList 1 this.Get .["/posts/list" ] <- fun _ -> this.PostList 1
this.Get .["/posts/list/page/{page:int}" ] <- fun parms -> upcast this.PostList (getPage <| downcast parms) this.Get .["/posts/list/page/{page:int}" ] <- fun parms -> this.PostList (getPage <| downcast parms)
this.Get .["/post/{postId}/edit" ] <- fun parms -> upcast this.EditPost (downcast parms) this.Get .["/post/{postId}/edit" ] <- fun parms -> this.EditPost (downcast parms)
this.Post.["/post/{postId}/edit" ] <- fun parms -> upcast this.SavePost (downcast parms) this.Post.["/post/{postId}/edit" ] <- fun parms -> this.SavePost (downcast parms)
// ---- Display posts to users ---- // ---- Display posts to users ----
@ -86,9 +86,8 @@ type PostModule(conn : IConnection, clock : IClock) as this =
| None -> // Maybe it's an old permalink for a post | None -> // Maybe it's an old permalink for a post
match tryFindPostByPriorPermalink conn this.WebLog.id url with match tryFindPostByPriorPermalink conn this.WebLog.id url with
| Some post -> // Redirect them to the proper permalink | Some post -> // Redirect them to the proper permalink
this.Negotiate upcast this.Response.AsRedirect(sprintf "/%s" post.permalink)
.WithHeader("Location", sprintf "/%s" post.permalink) .WithStatusCode HttpStatusCode.MovedPermanently
.WithStatusCode HttpStatusCode.MovedPermanently
| None -> this.NotFound () | None -> this.NotFound ()
/// Display categorized posts /// Display categorized posts
@ -102,7 +101,7 @@ type PostModule(conn : IConnection, clock : IClock) as this =
model.hasNewer <- match List.isEmpty model.posts with model.hasNewer <- match List.isEmpty model.posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindNewerCategorizedPost conn cat.id | _ -> Option.isSome <| tryFindNewerCategorizedPost conn cat.id
(List.last model.posts).post (List.head model.posts).post
model.hasOlder <- match List.isEmpty model.posts with model.hasOlder <- match List.isEmpty model.posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindOlderCategorizedPost conn cat.id | _ -> Option.isSome <| tryFindOlderCategorizedPost conn cat.id
@ -125,7 +124,7 @@ type PostModule(conn : IConnection, clock : IClock) as this =
model.posts <- findPageOfTaggedPosts conn this.WebLog.id tag pageNbr 10 |> forDisplay model.posts <- findPageOfTaggedPosts conn this.WebLog.id tag pageNbr 10 |> forDisplay
model.hasNewer <- match List.isEmpty model.posts with model.hasNewer <- match List.isEmpty model.posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindNewerTaggedPost conn tag (List.last model.posts).post | _ -> Option.isSome <| tryFindNewerTaggedPost conn tag (List.head model.posts).post
model.hasOlder <- match List.isEmpty model.posts with model.hasOlder <- match List.isEmpty model.posts with
| true -> false | true -> false
| _ -> Option.isSome <| tryFindOlderTaggedPost conn tag (List.last model.posts).post | _ -> Option.isSome <| tryFindOlderTaggedPost conn tag (List.last model.posts).post
@ -147,7 +146,7 @@ type PostModule(conn : IConnection, clock : IClock) as this =
model.hasOlder <- List.length model.posts > 24 model.hasOlder <- List.length model.posts > 24
model.urlPrefix <- "/posts/list" model.urlPrefix <- "/posts/list"
model.pageTitle <- Resources.Posts model.pageTitle <- Resources.Posts
this.View.["admin/post/list", model] upcast this.View.["admin/post/list", model]
/// Edit a post /// Edit a post
member this.EditPost (parameters : DynamicDictionary) = member this.EditPost (parameters : DynamicDictionary) =
@ -170,7 +169,7 @@ type PostModule(conn : IConnection, clock : IClock) as this =
model.pageTitle <- match post.id with model.pageTitle <- match post.id with
| "new" -> Resources.AddNewPost | "new" -> Resources.AddNewPost
| _ -> Resources.EditPost | _ -> Resources.EditPost
this.View.["admin/post/edit"] upcast this.View.["admin/post/edit"]
| None -> this.NotFound () | None -> this.NotFound ()
/// Save a post /// Save a post

View File

@ -21,9 +21,9 @@ type UserModule(conn : IConnection) as this =
|> Seq.fold (fun acc byt -> sprintf "%s%s" acc (byt.ToString "x2")) "" |> Seq.fold (fun acc byt -> sprintf "%s%s" acc (byt.ToString "x2")) ""
do do
this.Get .["/logon" ] <- fun parms -> upcast this.ShowLogOn (downcast parms) this.Get .["/logon" ] <- fun parms -> this.ShowLogOn (downcast parms)
this.Post.["/logon" ] <- fun parms -> upcast this.DoLogOn (downcast parms) this.Post.["/logon" ] <- fun parms -> this.DoLogOn (downcast parms)
this.Get .["/logoff"] <- fun parms -> upcast this.LogOff () this.Get .["/logoff"] <- fun parms -> this.LogOff ()
/// Show the log on page /// Show the log on page
member this.ShowLogOn (parameters : DynamicDictionary) = member this.ShowLogOn (parameters : DynamicDictionary) =
@ -31,7 +31,7 @@ type UserModule(conn : IConnection) as this =
model.form.returnUrl <- match parameters.ContainsKey "returnUrl" with model.form.returnUrl <- match parameters.ContainsKey "returnUrl" with
| true -> parameters.["returnUrl"].ToString () | true -> parameters.["returnUrl"].ToString ()
| _ -> "" | _ -> ""
this.View.["admin/user/logon", model] upcast this.View.["admin/user/logon", model]
/// Process a user log on /// Process a user log on
member this.DoLogOn (parameters : DynamicDictionary) = member this.DoLogOn (parameters : DynamicDictionary) =
@ -46,16 +46,13 @@ type UserModule(conn : IConnection) as this =
|> model.addMessage |> model.addMessage
this.Redirect "" model |> ignore // Save the messages in the session before the Nancy redirect this.Redirect "" model |> ignore // Save the messages in the session before the Nancy redirect
// TODO: investigate if addMessage should update the session when it's called // TODO: investigate if addMessage should update the session when it's called
this.LoginAndRedirect upcast this.LoginAndRedirect (System.Guid.Parse user.id,
(System.Guid.Parse user.id, fallbackRedirectUrl = defaultArg (Option.ofObj(form.returnUrl)) "/") fallbackRedirectUrl = defaultArg (Option.ofObj(form.returnUrl)) "/")
| None -> { level = Level.Error | None -> { level = Level.Error
message = Resources.ErrBadLogOnAttempt message = Resources.ErrBadLogOnAttempt
details = None } details = None }
|> model.addMessage |> model.addMessage
this.Redirect "" model |> ignore // Save the messages in the session before the Nancy redirect this.Redirect (sprintf "/user/logon?returnUrl=%s" form.returnUrl) model
// Can't redirect with a negotiator when the other leg uses a straight response... :/
this.Response.AsRedirect((sprintf "/user/logon?returnUrl=%s" form.returnUrl),
Responses.RedirectResponse.RedirectType.SeeOther)
/// Log a user off /// Log a user off
member this.LogOff () = member this.LogOff () =
@ -67,4 +64,4 @@ type UserModule(conn : IConnection) as this =
details = None } details = None }
|> model.addMessage |> model.addMessage
this.Redirect "" model |> ignore this.Redirect "" model |> ignore
this.LogoutAndRedirect "/" upcast this.LogoutAndRedirect "/"

View File

@ -7,18 +7,16 @@ open Nancy.Session.Persistable
open Newtonsoft.Json open Newtonsoft.Json
open NodaTime open NodaTime
open NodaTime.Text open NodaTime.Text
open System
/// Levels for a user message /// Levels for a user message
module Level = module Level =
/// An informational message /// An informational message
[<Literal>]
let Info = "Info" let Info = "Info"
/// A message regarding a non-fatal but non-optimal condition /// A message regarding a non-fatal but non-optimal condition
[<Literal>]
let Warning = "WARNING" let Warning = "WARNING"
/// A message regarding a failure of the expected result /// A message regarding a failure of the expected result
[<Literal>]
let Error = "ERROR" let Error = "ERROR"
@ -127,7 +125,28 @@ type MyWebLogModel(ctx : NancyContext, webLog : WebLog) =
member this.displayShortDate ticks = FormatDateTime.shortDate this.webLog.timeZone ticks member this.displayShortDate ticks = FormatDateTime.shortDate this.webLog.timeZone ticks
/// Display the time /// Display the time
member this.displayTime ticks = FormatDateTime.time this.webLog.timeZone ticks member this.displayTime ticks = FormatDateTime.time this.webLog.timeZone ticks
/// The page title with the web log name appended
member this.displayPageTitle =
match this.pageTitle with
| "" -> match this.webLog.subtitle with
| Some st -> sprintf "%s | %s" this.webLog.name st
| None -> this.webLog.name
| pt -> sprintf "%s | %s" pt this.webLog.name
/// An image with the version and load time in the tool tip
member this.footerLogo =
seq {
yield "<img src=\"/default/footer-logo.png\" alt=\"myWebLog\" title=\""
yield sprintf "%s %s &bull; " Resources.PoweredBy this.generator
yield Resources.LoadedIn
yield " "
yield TimeSpan(System.DateTime.Now.Ticks - this.requestStart).TotalSeconds.ToString "f3"
yield " "
yield Resources.Seconds.ToLower ()
yield "\" />"
}
|> Seq.reduce (fun acc x -> acc + x)
// ---- Admin models ---- // ---- Admin models ----
@ -266,7 +285,7 @@ type EditPageModel(ctx, webLog, page, revision) =
// ---- Post models ---- // ---- Post models ----
/// Model for post display /// Model for single post display
type PostModel(ctx, webLog, post) = type PostModel(ctx, webLog, post) =
inherit MyWebLogModel(ctx, webLog) inherit MyWebLogModel(ctx, webLog)
/// The post being displayed /// The post being displayed
@ -285,7 +304,10 @@ type PostModel(ctx, webLog, post) =
member this.tags = post.tags member this.tags = post.tags
|> List.sort |> List.sort
|> List.map (fun tag -> tag, tag.Replace(' ', '+')) |> List.map (fun tag -> tag, tag.Replace(' ', '+'))
/// Does this post have a newer post?
member this.hasNewer = this.newerPost.IsSome
/// Does this post have an older post?
member this.hasOlder = this.olderPost.IsSome
/// Wrapper for a post with additional properties /// Wrapper for a post with additional properties
type PostForDisplay(webLog : WebLog, post : Post) = type PostForDisplay(webLog : WebLog, post : Post) =
@ -311,22 +333,16 @@ type PostForDisplay(webLog : WebLog, post : Post) =
/// Model for all page-of-posts pages /// Model for all page-of-posts pages
type PostsModel(ctx, webLog) = type PostsModel(ctx, webLog) =
inherit MyWebLogModel(ctx, webLog) inherit MyWebLogModel(ctx, webLog)
/// The subtitle for the page /// The subtitle for the page
member val subtitle = Option<string>.None with get, set member val subtitle = Option<string>.None with get, set
/// The posts to display /// The posts to display
member val posts = List.empty<PostForDisplay> with get, set member val posts = List.empty<PostForDisplay> with get, set
/// The page number of the post list /// The page number of the post list
member val pageNbr = 0 with get, set member val pageNbr = 0 with get, set
/// Whether there is a newer page of posts for the list /// Whether there is a newer page of posts for the list
member val hasNewer = false with get, set member val hasNewer = false with get, set
/// Whether there is an older page of posts for the list /// Whether there is an older page of posts for the list
member val hasOlder = true with get, set member val hasOlder = true with get, set
/// The prefix for the next/prior links /// The prefix for the next/prior links
member val urlPrefix = "" with get, set member val urlPrefix = "" with get, set

View File

@ -22,7 +22,7 @@
<OutputPath>bin\Debug\</OutputPath> <OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<WarningLevel>3</WarningLevel> <WarningLevel>3</WarningLevel>
<DocumentationFile>bin\Debug\myWebLog.Web.XML</DocumentationFile> <DocumentationFile>bin\Debug\myWebLog.Web.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>

View File

@ -108,6 +108,12 @@
<Content Include="views\themes\default\content\bootstrap-theme.min.css"> <Content Include="views\themes\default\content\bootstrap-theme.min.css">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="views\themes\default\content\footer-logo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="views\themes\default\footer.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="views\themes\default\index-content.html"> <Content Include="views\themes\default\index-content.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>

View File

@ -2,7 +2,7 @@
@Section['Content'] @Section['Content']
<div class="row"> <div class="row">
<p><a class="btn btn-primary" href="/page/new/edit"><i class="fa fa-plus"></i> @Translate.AddNew</a></p> <p><a class="btn btn-primary" href="/page/new/edit"><i class="fa fa-plus"></i> &nbsp; @Translate.AddNew</a></p>
</div> </div>
<div class="row"> <div class="row">
<table class="table table-hover"> <table class="table table-hover">

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,10 @@
<footer>
<hr />
<div class="container-fluid">
<div class="row">
<div class="col-xs-12 text-right">
@Model.footerLogo
</div>
</div>
</div>
</footer>

View File

@ -2,4 +2,8 @@
@Section['Content'] @Section['Content']
@Partial['themes/default/index-content', Model] @Partial['themes/default/index-content', Model]
@EndSection @EndSection
@Section['Footer']
@Partial['themes/default/footer', Model]
@EndSection

View File

@ -4,7 +4,7 @@
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<meta name="generator" content="@Model.generator" /> <meta name="generator" content="@Model.generator" />
<title>@Model.pageTitle | @Model.webLog.name</title> <title>@Model.displayPageTitle</title>
<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" /> <link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="/default/bootstrap-theme.min.css" /> <link rel="stylesheet" type="text/css" href="/default/bootstrap-theme.min.css" />
<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" /> <link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" />

View File

@ -2,4 +2,8 @@
@Section['Content'] @Section['Content']
@Partial['themes/default/page-content', Model] @Partial['themes/default/page-content', Model]
@EndSection @EndSection
@Section['Footer']
@Partial['themes/default/footer', Model]
@EndSection

View File

@ -8,9 +8,11 @@
<i class="fa fa-calendar" title="@Translate.Date"></i> @Model.publishedDate &nbsp; <i class="fa fa-calendar" title="@Translate.Date"></i> @Model.publishedDate &nbsp;
<i class="fa fa-clock-o" title="@Translate.Time"></i> @Model.publishedTime &nbsp; &nbsp; &nbsp; <i class="fa fa-clock-o" title="@Translate.Time"></i> @Model.publishedTime &nbsp; &nbsp; &nbsp;
@Each.post.categories @Each.post.categories
<i class="fa fa-folder-open-o" title="@Translate.Category"></i> &nbsp; <span style="white-space:nowrap;">
<!-- <a href="/category/@Current.slug" title=__("Categorized under %s", category.name)) --> <i class="fa fa-folder-open-o" title="@Translate.Category"></i>
@Current.name &nbsp; &nbsp; <a href="/category/@Current.slug" title="@Translate.CategorizedUnder @Current.name">@Current.name</a>
&nbsp; &nbsp;
</span>
@EndEach @EndEach
</h4> </h4>
</div> </div>
@ -22,9 +24,11 @@
<div class="row"> <div class="row">
<div class="col-xs-12"> <div class="col-xs-12">
@Each.tags @Each.tags
<a href="/tag/@Current.Item2" title="@Translate.PostsTagged &quot;@Current.Item1&quot;"> <span style="white-space:nowrap;">
<i class="fa fa-tag"></i> @Current.Item1 <a href="/tag/@Current.Item2" title="@Translate.PostsTagged &quot;@Current.Item1&quot;">
</a> &nbsp; &nbsp; <i class="fa fa-tag"></i> @Current.Item1
</a> &nbsp; &nbsp;
</span>
@EndEach @EndEach
</div> </div>
</div> </div>
@ -52,14 +56,14 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-6"> <div class="col-xs-6">
@If.newerPost.IsSome @If.hasNewer
<a href="/@Model.newerPost.Value.permalink" title="@Translate.NextPost - &quot;@Model.newerPost.Value.title&quot;"> <a href="/@Model.newerPost.Value.permalink" title="@Translate.NextPost - &quot;@Model.newerPost.Value.title&quot;">
&#xab;&nbsp; @Model.newerPost.Value.title &#xab;&nbsp; @Model.newerPost.Value.title
</a> </a>
@EndIf @EndIf
</div> </div>
<div class="col-xs-6 text-right"> <div class="col-xs-6 text-right">
@If.olderPost.IsSome @If.hasOlder
<a href="/@Model.olderPost.Value.permalink" <a href="/@Model.olderPost.Value.permalink"
title="@Translate.PreviousPost - &quot;@Model.olderPost.Value.title&quot;"> title="@Translate.PreviousPost - &quot;@Model.olderPost.Value.title&quot;">
@Model.olderPost.Value.title &nbsp;&#xbb; @Model.olderPost.Value.title &nbsp;&#xbb;

View File

@ -2,4 +2,8 @@
@Section['Content'] @Section['Content']
@Partial['themes/default/single-content', Model] @Partial['themes/default/single-content', Model]
@EndSection @EndSection
@Section['Footer']
@Partial['themes/default/footer', Model]
@EndSection