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:
parent
7c99da8cb5
commit
d3712dc562
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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) =
|
||||||
|
@ -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>
|
||||||
|
27
src/myWebLog.Resources/Resources.Designer.cs
generated
27
src/myWebLog.Resources/Resources.Designer.cs
generated
@ -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>
|
||||||
|
@ -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>
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
@ -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 =
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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 "/"
|
||||||
|
@ -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 • " 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
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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> @Translate.AddNew</a></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
|
BIN
src/myWebLog/views/themes/default/content/footer-logo.png
Normal file
BIN
src/myWebLog/views/themes/default/content/footer-logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
10
src/myWebLog/views/themes/default/footer.html
Normal file
10
src/myWebLog/views/themes/default/footer.html
Normal 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>
|
@ -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
|
||||||
|
@ -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" />
|
||||||
|
@ -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
|
||||||
|
@ -8,9 +8,11 @@
|
|||||||
<i class="fa fa-calendar" title="@Translate.Date"></i> @Model.publishedDate
|
<i class="fa fa-calendar" title="@Translate.Date"></i> @Model.publishedDate
|
||||||
<i class="fa fa-clock-o" title="@Translate.Time"></i> @Model.publishedTime
|
<i class="fa fa-clock-o" title="@Translate.Time"></i> @Model.publishedTime
|
||||||
@Each.post.categories
|
@Each.post.categories
|
||||||
<i class="fa fa-folder-open-o" title="@Translate.Category"></i>
|
<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
|
<a href="/category/@Current.slug" title="@Translate.CategorizedUnder @Current.name">@Current.name</a>
|
||||||
|
|
||||||
|
</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 "@Current.Item1"">
|
<span style="white-space:nowrap;">
|
||||||
<i class="fa fa-tag"></i> @Current.Item1
|
<a href="/tag/@Current.Item2" title="@Translate.PostsTagged "@Current.Item1"">
|
||||||
</a>
|
<i class="fa fa-tag"></i> @Current.Item1
|
||||||
|
</a>
|
||||||
|
</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 - "@Model.newerPost.Value.title"">
|
<a href="/@Model.newerPost.Value.permalink" title="@Translate.NextPost - "@Model.newerPost.Value.title"">
|
||||||
« @Model.newerPost.Value.title
|
« @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 - "@Model.olderPost.Value.title"">
|
title="@Translate.PreviousPost - "@Model.olderPost.Value.title"">
|
||||||
@Model.olderPost.Value.title »
|
@Model.olderPost.Value.title »
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user