PascalCase record members
Contrary to some examples, the official design guidelines for F# state that these should be PascalCase rather than camelCase. Also, while the projects are still "myWebLog", the namespaces and DLLs are now "MyWebLog". (never intended it to be the other way, really...)
This commit is contained in:
parent
2574501ccd
commit
ac8fa084d1
5
src/Settings.FSharpLint
Normal file
5
src/Settings.FSharpLint
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FSharpLintSettings xmlns="https://github.com/fsprojects/FSharpLint/blob/master/ConfigurationSchema.xsd">
|
||||||
|
<IgnoreFiles Update="Overwrite"><![CDATA[assemblyinfo.*]]></IgnoreFiles>
|
||||||
|
<Analysers />
|
||||||
|
</FSharpLintSettings>
|
@ -4,11 +4,11 @@ open System.Reflection
|
|||||||
open System.Runtime.CompilerServices
|
open System.Runtime.CompilerServices
|
||||||
open System.Runtime.InteropServices
|
open System.Runtime.InteropServices
|
||||||
|
|
||||||
[<assembly: AssemblyTitle("myWebLog.Data")>]
|
[<assembly: AssemblyTitle("MyWebLog.Data")>]
|
||||||
[<assembly: AssemblyDescription("Data access for myWebLog")>]
|
[<assembly: AssemblyDescription("Data access for myWebLog")>]
|
||||||
[<assembly: AssemblyConfiguration("")>]
|
[<assembly: AssemblyConfiguration("")>]
|
||||||
[<assembly: AssemblyCompany("DJS Consulting")>]
|
[<assembly: AssemblyCompany("DJS Consulting")>]
|
||||||
[<assembly: AssemblyProduct("myWebLog.Data")>]
|
[<assembly: AssemblyProduct("MyWebLog.Data")>]
|
||||||
[<assembly: AssemblyCopyright("Copyright © 2016")>]
|
[<assembly: AssemblyCopyright("Copyright © 2016")>]
|
||||||
[<assembly: AssemblyTrademark("")>]
|
[<assembly: AssemblyTrademark("")>]
|
||||||
[<assembly: AssemblyCulture("")>]
|
[<assembly: AssemblyCulture("")>]
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
module myWebLog.Data.Category
|
module MyWebLog.Data.Category
|
||||||
|
|
||||||
open FSharp.Interop.Dynamic
|
open FSharp.Interop.Dynamic
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Rethink
|
open Rethink
|
||||||
|
open RethinkDb.Driver.Ast
|
||||||
open System.Dynamic
|
open System.Dynamic
|
||||||
|
|
||||||
let private r = RethinkDb.Driver.RethinkDB.R
|
let private r = RethinkDb.Driver.RethinkDB.R
|
||||||
@ -11,18 +12,18 @@ let private r = RethinkDb.Driver.RethinkDB.R
|
|||||||
let private category (webLogId : string) (catId : string) =
|
let private category (webLogId : string) (catId : string) =
|
||||||
r.Table(Table.Category)
|
r.Table(Table.Category)
|
||||||
.Get(catId)
|
.Get(catId)
|
||||||
.Filter(fun c -> c.["webLogId"].Eq(webLogId))
|
.Filter(fun c -> c.["WebLogId"].Eq(webLogId))
|
||||||
|
|
||||||
/// Sort categories by their name, with their children sorted below them, including an indent level
|
/// Sort categories by their name, with their children sorted below them, including an indent level
|
||||||
let sortCategories categories =
|
let sortCategories categories =
|
||||||
let rec getChildren (cat : Category) indent =
|
let rec getChildren (cat : Category) indent =
|
||||||
seq {
|
seq {
|
||||||
yield cat, indent
|
yield cat, indent
|
||||||
for child in categories |> List.filter (fun c -> c.parentId = Some cat.id) do
|
for child in categories |> List.filter (fun c -> c.ParentId = Some cat.Id) do
|
||||||
yield! getChildren child (indent + 1)
|
yield! getChildren child (indent + 1)
|
||||||
}
|
}
|
||||||
categories
|
categories
|
||||||
|> List.filter (fun c -> c.parentId.IsNone)
|
|> List.filter (fun c -> c.ParentId.IsNone)
|
||||||
|> List.map (fun c -> getChildren c 0)
|
|> List.map (fun c -> getChildren c 0)
|
||||||
|> Seq.collect id
|
|> Seq.collect id
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
@ -30,8 +31,8 @@ let sortCategories categories =
|
|||||||
/// Get all categories for a web log
|
/// Get all categories for a web log
|
||||||
let getAllCategories conn (webLogId : string) =
|
let getAllCategories conn (webLogId : string) =
|
||||||
r.Table(Table.Category)
|
r.Table(Table.Category)
|
||||||
.GetAll(webLogId).OptArg("index", "webLogId")
|
.GetAll(webLogId).OptArg("index", "WebLogId")
|
||||||
.OrderBy("name")
|
.OrderBy("Name")
|
||||||
.RunListAsync<Category>(conn)
|
.RunListAsync<Category>(conn)
|
||||||
|> await
|
|> await
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
@ -46,28 +47,28 @@ let tryFindCategory conn webLogId catId : Category option =
|
|||||||
|
|
||||||
/// Save a category
|
/// Save a category
|
||||||
let saveCategory conn webLogId (cat : Category) =
|
let saveCategory conn webLogId (cat : Category) =
|
||||||
match cat.id with
|
match cat.Id with
|
||||||
| "new" -> let newCat = { cat with id = string <| System.Guid.NewGuid()
|
| "new" -> let newCat = { cat with Id = string <| System.Guid.NewGuid()
|
||||||
webLogId = webLogId }
|
WebLogId = webLogId }
|
||||||
r.Table(Table.Category)
|
r.Table(Table.Category)
|
||||||
.Insert(newCat)
|
.Insert(newCat)
|
||||||
.RunResultAsync(conn) |> await |> ignore
|
.RunResultAsync(conn) |> await |> ignore
|
||||||
newCat.id
|
newCat.Id
|
||||||
| _ -> let upd8 = ExpandoObject()
|
| _ -> let upd8 = ExpandoObject()
|
||||||
upd8?name <- cat.name
|
upd8?Name <- cat.Name
|
||||||
upd8?slug <- cat.slug
|
upd8?Slug <- cat.Slug
|
||||||
upd8?description <- cat.description
|
upd8?Description <- cat.Description
|
||||||
upd8?parentId <- cat.parentId
|
upd8?ParentId <- cat.ParentId
|
||||||
(category webLogId cat.id)
|
(category webLogId cat.Id)
|
||||||
.Update(upd8)
|
.Update(upd8)
|
||||||
.RunResultAsync(conn) |> await |> ignore
|
.RunResultAsync(conn) |> await |> ignore
|
||||||
cat.id
|
cat.Id
|
||||||
|
|
||||||
/// Remove a category from a given parent
|
/// Remove a category from a given parent
|
||||||
let removeCategoryFromParent conn webLogId parentId catId =
|
let removeCategoryFromParent conn webLogId parentId catId =
|
||||||
match tryFindCategory conn webLogId parentId with
|
match tryFindCategory conn webLogId parentId with
|
||||||
| Some parent -> let upd8 = ExpandoObject()
|
| Some parent -> let upd8 = ExpandoObject()
|
||||||
upd8?children <- parent.children
|
upd8?Children <- parent.Children
|
||||||
|> List.filter (fun childId -> childId <> catId)
|
|> List.filter (fun childId -> childId <> catId)
|
||||||
(category webLogId parentId)
|
(category webLogId parentId)
|
||||||
.Update(upd8)
|
.Update(upd8)
|
||||||
@ -78,7 +79,7 @@ let removeCategoryFromParent conn webLogId parentId catId =
|
|||||||
let addCategoryToParent conn webLogId parentId catId =
|
let addCategoryToParent conn webLogId parentId catId =
|
||||||
match tryFindCategory conn webLogId parentId with
|
match tryFindCategory conn webLogId parentId with
|
||||||
| Some parent -> let upd8 = ExpandoObject()
|
| Some parent -> let upd8 = ExpandoObject()
|
||||||
upd8?children <- catId :: parent.children
|
upd8?Children <- catId :: parent.Children
|
||||||
(category webLogId parentId)
|
(category webLogId parentId)
|
||||||
.Update(upd8)
|
.Update(upd8)
|
||||||
.RunResultAsync(conn) |> await |> ignore
|
.RunResultAsync(conn) |> await |> ignore
|
||||||
@ -87,40 +88,40 @@ let addCategoryToParent conn webLogId parentId catId =
|
|||||||
/// Delete a category
|
/// Delete a category
|
||||||
let deleteCategory conn cat =
|
let deleteCategory conn cat =
|
||||||
// Remove the category from its parent
|
// Remove the category from its parent
|
||||||
match cat.parentId with
|
match cat.ParentId with
|
||||||
| Some parentId -> removeCategoryFromParent conn cat.webLogId parentId cat.id
|
| Some parentId -> removeCategoryFromParent conn cat.WebLogId parentId cat.Id
|
||||||
| None -> ()
|
| None -> ()
|
||||||
// Move this category's children to its parent
|
// Move this category's children to its parent
|
||||||
let newParent = ExpandoObject()
|
let newParent = ExpandoObject()
|
||||||
newParent?parentId <- cat.parentId
|
newParent?ParentId <- cat.ParentId
|
||||||
cat.children
|
cat.Children
|
||||||
|> List.iter (fun childId -> (category cat.webLogId childId)
|
|> List.iter (fun childId -> (category cat.WebLogId childId)
|
||||||
.Update(newParent)
|
.Update(newParent)
|
||||||
.RunResultAsync(conn) |> await |> ignore)
|
.RunResultAsync(conn) |> await |> ignore)
|
||||||
// Remove the category from posts where it is assigned
|
// Remove the category from posts where it is assigned
|
||||||
r.Table(Table.Post)
|
r.Table(Table.Post)
|
||||||
.GetAll(cat.webLogId).OptArg("index", "webLogId")
|
.GetAll(cat.WebLogId).OptArg("index", "WebLogId")
|
||||||
.Filter(fun p -> p.["categoryIds"].Contains(cat.id))
|
.Filter(ReqlFunction1(fun p -> upcast p.["CategoryIds"].Contains(cat.Id)))
|
||||||
.RunCursorAsync<Post>(conn)
|
.RunCursorAsync<Post>(conn)
|
||||||
|> await
|
|> await
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
|> List.iter (fun post -> let newCats = ExpandoObject()
|
|> List.iter (fun post -> let newCats = ExpandoObject()
|
||||||
newCats?categoryIds <- post.categoryIds
|
newCats?CategoryIds <- post.CategoryIds
|
||||||
|> List.filter (fun c -> c <> cat.id)
|
|> List.filter (fun c -> c <> cat.Id)
|
||||||
r.Table(Table.Post)
|
r.Table(Table.Post)
|
||||||
.Get(post.id)
|
.Get(post.Id)
|
||||||
.Update(newCats)
|
.Update(newCats)
|
||||||
.RunResultAsync(conn) |> await |> ignore)
|
.RunResultAsync(conn) |> await |> ignore)
|
||||||
// Now, delete the category
|
// Now, delete the category
|
||||||
r.Table(Table.Category)
|
r.Table(Table.Category)
|
||||||
.Get(cat.id)
|
.Get(cat.Id)
|
||||||
.Delete()
|
.Delete()
|
||||||
.RunResultAsync(conn) |> await |> ignore
|
.RunResultAsync(conn) |> await |> ignore
|
||||||
|
|
||||||
/// 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(r.Array(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
|
||||||
|
@ -1,47 +1,59 @@
|
|||||||
namespace myWebLog.Data
|
namespace MyWebLog.Data
|
||||||
|
|
||||||
open RethinkDb.Driver
|
open RethinkDb.Driver
|
||||||
open RethinkDb.Driver.Net
|
open RethinkDb.Driver.Net
|
||||||
open Newtonsoft.Json
|
open Newtonsoft.Json
|
||||||
|
|
||||||
/// Data configuration
|
/// Data configuration
|
||||||
type DataConfig = {
|
type DataConfig =
|
||||||
/// The hostname for the RethinkDB server
|
{ /// The hostname for the RethinkDB server
|
||||||
hostname : string
|
[<JsonProperty("hostname")>]
|
||||||
|
Hostname : string
|
||||||
/// The port for the RethinkDB server
|
/// The port for the RethinkDB server
|
||||||
port : int
|
[<JsonProperty("port")>]
|
||||||
|
Port : int
|
||||||
/// The authorization key to use when connecting to the server
|
/// The authorization key to use when connecting to the server
|
||||||
authKey : string
|
[<JsonProperty("authKey")>]
|
||||||
|
AuthKey : string
|
||||||
/// How long an attempt to connect to the server should wait before giving up
|
/// How long an attempt to connect to the server should wait before giving up
|
||||||
timeout : int
|
[<JsonProperty("timeout")>]
|
||||||
|
Timeout : int
|
||||||
/// The name of the default database to use on the connection
|
/// The name of the default database to use on the connection
|
||||||
database : string
|
[<JsonProperty("database")>]
|
||||||
|
Database : string
|
||||||
/// A connection to the RethinkDB server using the configuration in this object
|
/// A connection to the RethinkDB server using the configuration in this object
|
||||||
conn : IConnection
|
[<JsonIgnore>]
|
||||||
}
|
Conn : IConnection }
|
||||||
with
|
with
|
||||||
/// Create a data configuration from JSON
|
/// Create a data configuration from JSON
|
||||||
static member fromJson json =
|
static member FromJson json =
|
||||||
let mutable cfg = JsonConvert.DeserializeObject<DataConfig> json
|
let ensureHostname cfg = match cfg.Hostname with
|
||||||
cfg <- match cfg.hostname with
|
| null -> { cfg with Hostname = RethinkDBConstants.DefaultHostname }
|
||||||
| null -> { cfg with hostname = RethinkDBConstants.DefaultHostname }
|
|
||||||
| _ -> cfg
|
| _ -> cfg
|
||||||
cfg <- match cfg.port with
|
let ensurePort cfg = match cfg.Port with
|
||||||
| 0 -> { cfg with port = RethinkDBConstants.DefaultPort }
|
| 0 -> { cfg with Port = RethinkDBConstants.DefaultPort }
|
||||||
| _ -> cfg
|
| _ -> cfg
|
||||||
cfg <- match cfg.authKey with
|
let ensureAuthKey cfg = match cfg.AuthKey with
|
||||||
| null -> { cfg with authKey = RethinkDBConstants.DefaultAuthkey }
|
| null -> { cfg with AuthKey = RethinkDBConstants.DefaultAuthkey }
|
||||||
| _ -> cfg
|
| _ -> cfg
|
||||||
cfg <- match cfg.timeout with
|
let ensureTimeout cfg = match cfg.Timeout with
|
||||||
| 0 -> { cfg with timeout = RethinkDBConstants.DefaultTimeout }
|
| 0 -> { cfg with Timeout = RethinkDBConstants.DefaultTimeout }
|
||||||
| _ -> cfg
|
| _ -> cfg
|
||||||
cfg <- match cfg.database with
|
let ensureDatabase cfg = match cfg.Database with
|
||||||
| null -> { cfg with database = RethinkDBConstants.DefaultDbName }
|
| null -> { cfg with Database = RethinkDBConstants.DefaultDbName }
|
||||||
| _ -> cfg
|
| _ -> cfg
|
||||||
{ cfg with conn = RethinkDB.R.Connection()
|
let connect cfg = { cfg with Conn = RethinkDB.R.Connection()
|
||||||
.Hostname(cfg.hostname)
|
.Hostname(cfg.Hostname)
|
||||||
.Port(cfg.port)
|
.Port(cfg.Port)
|
||||||
.AuthKey(cfg.authKey)
|
.AuthKey(cfg.AuthKey)
|
||||||
.Db(cfg.database)
|
.Db(cfg.Database)
|
||||||
.Timeout(cfg.timeout)
|
.Timeout(cfg.Timeout)
|
||||||
.Connect() }
|
.Connect() }
|
||||||
|
JsonConvert.DeserializeObject<DataConfig> json
|
||||||
|
|> ensureHostname
|
||||||
|
|> ensurePort
|
||||||
|
|> ensureAuthKey
|
||||||
|
|> ensureTimeout
|
||||||
|
|> ensureDatabase
|
||||||
|
|> connect
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
namespace myWebLog.Entities
|
namespace MyWebLog.Entities
|
||||||
|
|
||||||
open Newtonsoft.Json
|
open Newtonsoft.Json
|
||||||
|
|
||||||
@ -37,261 +37,244 @@ module CommentStatus =
|
|||||||
// ---- Entities ----
|
// ---- Entities ----
|
||||||
|
|
||||||
/// A revision of a post or page
|
/// A revision of a post or page
|
||||||
type Revision = {
|
type Revision =
|
||||||
/// The instant which this revision was saved
|
{ /// The instant which this revision was saved
|
||||||
asOf : int64
|
AsOf : int64
|
||||||
/// The source language
|
/// The source language
|
||||||
sourceType : string
|
SourceType : string
|
||||||
/// The text
|
/// The text
|
||||||
text : string
|
Text : string }
|
||||||
}
|
|
||||||
with
|
with
|
||||||
/// An empty revision
|
/// An empty revision
|
||||||
static member empty =
|
static member Empty =
|
||||||
{ asOf = int64 0
|
{ AsOf = int64 0
|
||||||
sourceType = RevisionSource.HTML
|
SourceType = RevisionSource.HTML
|
||||||
text = "" }
|
Text = "" }
|
||||||
|
|
||||||
/// A page with static content
|
/// A page with static content
|
||||||
type Page = {
|
type Page =
|
||||||
/// The Id
|
{ /// The Id
|
||||||
id : string
|
Id : string
|
||||||
/// The Id of the web log to which this page belongs
|
/// The Id of the web log to which this page belongs
|
||||||
webLogId : string
|
WebLogId : string
|
||||||
/// The Id of the author of this page
|
/// The Id of the author of this page
|
||||||
authorId : string
|
AuthorId : string
|
||||||
/// The title of the page
|
/// The title of the page
|
||||||
title : string
|
Title : string
|
||||||
/// The link at which this page is displayed
|
/// The link at which this page is displayed
|
||||||
permalink : string
|
Permalink : string
|
||||||
/// The instant this page was published
|
/// The instant this page was published
|
||||||
publishedOn : int64
|
PublishedOn : int64
|
||||||
/// The instant this page was last updated
|
/// The instant this page was last updated
|
||||||
updatedOn : int64
|
UpdatedOn : int64
|
||||||
/// Whether this page shows as part of the web log's navigation
|
/// Whether this page shows as part of the web log's navigation
|
||||||
showInPageList : bool
|
ShowInPageList : bool
|
||||||
/// The current text of the page
|
/// The current text of the page
|
||||||
text : string
|
Text : string
|
||||||
/// Revisions of this page
|
/// Revisions of this page
|
||||||
revisions : Revision list
|
Revisions : Revision list }
|
||||||
}
|
|
||||||
with
|
with
|
||||||
static member empty =
|
static member Empty =
|
||||||
{ id = ""
|
{ Id = ""
|
||||||
webLogId = ""
|
WebLogId = ""
|
||||||
authorId = ""
|
AuthorId = ""
|
||||||
title = ""
|
Title = ""
|
||||||
permalink = ""
|
Permalink = ""
|
||||||
publishedOn = int64 0
|
PublishedOn = int64 0
|
||||||
updatedOn = int64 0
|
UpdatedOn = int64 0
|
||||||
showInPageList = false
|
ShowInPageList = false
|
||||||
text = ""
|
Text = ""
|
||||||
revisions = List.empty
|
Revisions = List.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// An entry in the list of pages displayed as part of the web log (derived via query)
|
/// An entry in the list of pages displayed as part of the web log (derived via query)
|
||||||
type PageListEntry = {
|
type PageListEntry =
|
||||||
permalink : string
|
{ Permalink : string
|
||||||
title : string
|
Title : string }
|
||||||
}
|
|
||||||
|
|
||||||
/// A web log
|
/// A web log
|
||||||
type WebLog = {
|
type WebLog =
|
||||||
/// The Id
|
{ /// The Id
|
||||||
id : string
|
Id : string
|
||||||
/// The name
|
/// The name
|
||||||
name : string
|
Name : string
|
||||||
/// The subtitle
|
/// The subtitle
|
||||||
subtitle : string option
|
Subtitle : string option
|
||||||
/// The default page ("posts" or a page Id)
|
/// The default page ("posts" or a page Id)
|
||||||
defaultPage : string
|
DefaultPage : string
|
||||||
/// The path of the theme (within /views/themes)
|
/// The path of the theme (within /views/themes)
|
||||||
themePath : string
|
ThemePath : string
|
||||||
/// The URL base
|
/// The URL base
|
||||||
urlBase : string
|
UrlBase : string
|
||||||
/// The time zone in which dates/times should be displayed
|
/// The time zone in which dates/times should be displayed
|
||||||
timeZone : string
|
TimeZone : string
|
||||||
/// A list of pages to be rendered as part of the site navigation
|
/// A list of pages to be rendered as part of the site navigation (not stored)
|
||||||
[<JsonIgnore>]
|
PageList : PageListEntry list }
|
||||||
pageList : PageListEntry list
|
|
||||||
}
|
|
||||||
with
|
with
|
||||||
/// An empty web log
|
/// An empty web log
|
||||||
static member empty =
|
static member Empty =
|
||||||
{ id = ""
|
{ Id = ""
|
||||||
name = ""
|
Name = ""
|
||||||
subtitle = None
|
Subtitle = None
|
||||||
defaultPage = ""
|
DefaultPage = ""
|
||||||
themePath = "default"
|
ThemePath = "default"
|
||||||
urlBase = ""
|
UrlBase = ""
|
||||||
timeZone = "America/New_York"
|
TimeZone = "America/New_York"
|
||||||
pageList = List.empty
|
PageList = List.empty }
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// An authorization between a user and a web log
|
/// An authorization between a user and a web log
|
||||||
type Authorization = {
|
type Authorization =
|
||||||
/// The Id of the web log to which this authorization grants access
|
{ /// The Id of the web log to which this authorization grants access
|
||||||
webLogId : string
|
WebLogId : string
|
||||||
/// The level of access granted by this authorization
|
/// The level of access granted by this authorization
|
||||||
level : string
|
Level : string }
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// A user of myWebLog
|
/// A user of myWebLog
|
||||||
type User = {
|
type User =
|
||||||
/// The Id
|
{ /// The Id
|
||||||
id : string
|
Id : string
|
||||||
/// The user name (e-mail address)
|
/// The user name (e-mail address)
|
||||||
userName : string
|
UserName : string
|
||||||
/// The first name
|
/// The first name
|
||||||
firstName : string
|
FirstName : string
|
||||||
/// The last name
|
/// The last name
|
||||||
lastName : string
|
LastName : string
|
||||||
/// The user's preferred name
|
/// The user's preferred name
|
||||||
preferredName : string
|
PreferredName : string
|
||||||
/// The hash of the user's password
|
/// The hash of the user's password
|
||||||
passwordHash : string
|
PasswordHash : string
|
||||||
/// The URL of the user's personal site
|
/// The URL of the user's personal site
|
||||||
url : string option
|
Url : string option
|
||||||
/// The user's authorizations
|
/// The user's authorizations
|
||||||
authorizations : Authorization list
|
Authorizations : Authorization list }
|
||||||
}
|
|
||||||
with
|
with
|
||||||
/// An empty user
|
/// An empty user
|
||||||
static member empty =
|
static member Empty =
|
||||||
{ id = ""
|
{ Id = ""
|
||||||
userName = ""
|
UserName = ""
|
||||||
firstName = ""
|
FirstName = ""
|
||||||
lastName = ""
|
LastName = ""
|
||||||
preferredName = ""
|
PreferredName = ""
|
||||||
passwordHash = ""
|
PasswordHash = ""
|
||||||
url = None
|
Url = None
|
||||||
authorizations = List.empty
|
Authorizations = List.empty }
|
||||||
}
|
|
||||||
|
|
||||||
/// Claims for this user
|
/// Claims for this user
|
||||||
[<JsonIgnore>]
|
[<JsonIgnore>]
|
||||||
member this.claims = this.authorizations
|
member this.Claims = this.Authorizations
|
||||||
|> List.map (fun auth -> sprintf "%s|%s" auth.webLogId auth.level)
|
|> List.map (fun auth -> sprintf "%s|%s" auth.WebLogId auth.Level)
|
||||||
|
|
||||||
|
|
||||||
/// A category to which posts may be assigned
|
/// A category to which posts may be assigned
|
||||||
type Category = {
|
type Category =
|
||||||
/// The Id
|
{ /// The Id
|
||||||
id : string
|
Id : string
|
||||||
/// The Id of the web log to which this category belongs
|
/// The Id of the web log to which this category belongs
|
||||||
webLogId : string
|
WebLogId : string
|
||||||
/// The displayed name
|
/// The displayed name
|
||||||
name : string
|
Name : string
|
||||||
/// The slug (used in category URLs)
|
/// The slug (used in category URLs)
|
||||||
slug : string
|
Slug : string
|
||||||
/// A longer description of the category
|
/// A longer description of the category
|
||||||
description : string option
|
Description : string option
|
||||||
/// The parent Id of this category (if a subcategory)
|
/// The parent Id of this category (if a subcategory)
|
||||||
parentId : string option
|
ParentId : string option
|
||||||
/// The categories for which this category is the parent
|
/// The categories for which this category is the parent
|
||||||
children : string list
|
Children : string list }
|
||||||
}
|
|
||||||
with
|
with
|
||||||
/// An empty category
|
/// An empty category
|
||||||
static member empty =
|
static member empty =
|
||||||
{ id = "new"
|
{ Id = "new"
|
||||||
webLogId = ""
|
WebLogId = ""
|
||||||
name = ""
|
Name = ""
|
||||||
slug = ""
|
Slug = ""
|
||||||
description = None
|
Description = None
|
||||||
parentId = None
|
ParentId = None
|
||||||
children = List.empty
|
Children = List.empty }
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// A comment (applies to a post)
|
/// A comment (applies to a post)
|
||||||
type Comment = {
|
type Comment =
|
||||||
/// The Id
|
{ /// The Id
|
||||||
id : string
|
Id : string
|
||||||
/// The Id of the post to which this comment applies
|
/// The Id of the post to which this comment applies
|
||||||
postId : string
|
PostId : string
|
||||||
/// The Id of the comment to which this comment is a reply
|
/// The Id of the comment to which this comment is a reply
|
||||||
inReplyToId : string option
|
InReplyToId : string option
|
||||||
/// The name of the commentor
|
/// The name of the commentor
|
||||||
name : string
|
Name : string
|
||||||
/// The e-mail address of the commentor
|
/// The e-mail address of the commentor
|
||||||
email : string
|
Email : string
|
||||||
/// The URL of the commentor's personal website
|
/// The URL of the commentor's personal website
|
||||||
url : string option
|
Url : string option
|
||||||
/// The status of the comment
|
/// The status of the comment
|
||||||
status : string
|
Status : string
|
||||||
/// The instant the comment was posted
|
/// The instant the comment was posted
|
||||||
postedOn : int64
|
PostedOn : int64
|
||||||
/// The text of the comment
|
/// The text of the comment
|
||||||
text : string
|
Text : string }
|
||||||
}
|
|
||||||
with
|
with
|
||||||
static member empty =
|
static member Empty =
|
||||||
{ id = ""
|
{ Id = ""
|
||||||
postId = ""
|
PostId = ""
|
||||||
inReplyToId = None
|
InReplyToId = None
|
||||||
name = ""
|
Name = ""
|
||||||
email = ""
|
Email = ""
|
||||||
url = None
|
Url = None
|
||||||
status = CommentStatus.Pending
|
Status = CommentStatus.Pending
|
||||||
postedOn = int64 0
|
PostedOn = int64 0
|
||||||
text = ""
|
Text = "" }
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// A post
|
/// A post
|
||||||
type Post = {
|
type Post =
|
||||||
/// The Id
|
{ /// The Id
|
||||||
id : string
|
Id : string
|
||||||
/// The Id of the web log to which this post belongs
|
/// The Id of the web log to which this post belongs
|
||||||
webLogId : string
|
WebLogId : string
|
||||||
/// The Id of the author of this post
|
/// The Id of the author of this post
|
||||||
authorId : string
|
AuthorId : string
|
||||||
/// The status
|
/// The status
|
||||||
status : string
|
Status : string
|
||||||
/// The title
|
/// The title
|
||||||
title : string
|
Title : string
|
||||||
/// The link at which the post resides
|
/// The link at which the post resides
|
||||||
permalink : string
|
Permalink : string
|
||||||
/// The instant on which the post was originally published
|
/// The instant on which the post was originally published
|
||||||
publishedOn : int64
|
PublishedOn : int64
|
||||||
/// The instant on which the post was last updated
|
/// The instant on which the post was last updated
|
||||||
updatedOn : int64
|
UpdatedOn : int64
|
||||||
/// The text of the post
|
/// The text of the post
|
||||||
text : string
|
Text : string
|
||||||
/// The Ids of the categories to which this is assigned
|
/// The Ids of the categories to which this is assigned
|
||||||
categoryIds : string list
|
CategoryIds : string list
|
||||||
/// The tags for the post
|
/// The tags for the post
|
||||||
tags : string list
|
Tags : string list
|
||||||
/// The permalinks at which this post may have once resided
|
/// The permalinks at which this post may have once resided
|
||||||
priorPermalinks : string list
|
PriorPermalinks : string list
|
||||||
/// Revisions of this post
|
/// Revisions of this post
|
||||||
revisions : Revision list
|
Revisions : Revision list
|
||||||
/// The categories to which this is assigned
|
/// The categories to which this is assigned (not stored in database)
|
||||||
[<JsonIgnore>]
|
Categories : Category list
|
||||||
categories : Category list
|
/// The comments (not stored in database)
|
||||||
/// The comments
|
Comments : Comment list }
|
||||||
[<JsonIgnore>]
|
|
||||||
comments : Comment list
|
|
||||||
}
|
|
||||||
with
|
with
|
||||||
static member empty =
|
static member Empty =
|
||||||
{ id = "new"
|
{ Id = "new"
|
||||||
webLogId = ""
|
WebLogId = ""
|
||||||
authorId = ""
|
AuthorId = ""
|
||||||
status = PostStatus.Draft
|
Status = PostStatus.Draft
|
||||||
title = ""
|
Title = ""
|
||||||
permalink = ""
|
Permalink = ""
|
||||||
publishedOn = int64 0
|
PublishedOn = int64 0
|
||||||
updatedOn = int64 0
|
UpdatedOn = int64 0
|
||||||
text = ""
|
Text = ""
|
||||||
categoryIds = List.empty
|
CategoryIds = List.empty
|
||||||
tags = List.empty
|
Tags = List.empty
|
||||||
priorPermalinks = List.empty
|
PriorPermalinks = List.empty
|
||||||
revisions = List.empty
|
Revisions = List.empty
|
||||||
categories = List.empty
|
Categories = List.empty
|
||||||
comments = List.empty
|
Comments = List.empty }
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
module myWebLog.Data.Page
|
module MyWebLog.Data.Page
|
||||||
|
|
||||||
open FSharp.Interop.Dynamic
|
open FSharp.Interop.Dynamic
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Rethink
|
open Rethink
|
||||||
open RethinkDb.Driver.Ast
|
open RethinkDb.Driver.Ast
|
||||||
open System.Dynamic
|
open System.Dynamic
|
||||||
@ -12,7 +12,7 @@ let private r = RethinkDb.Driver.RethinkDB.R
|
|||||||
let private page (webLogId : string) (pageId : string) =
|
let private page (webLogId : string) (pageId : string) =
|
||||||
r.Table(Table.Page)
|
r.Table(Table.Page)
|
||||||
.Get(pageId)
|
.Get(pageId)
|
||||||
.Filter(ReqlFunction1(fun p -> upcast p.["webLogId"].Eq(webLogId)))
|
.Filter(ReqlFunction1(fun p -> upcast p.["WebLogId"].Eq(webLogId)))
|
||||||
|
|
||||||
/// Get a page by its Id
|
/// Get a page by its Id
|
||||||
let tryFindPage conn webLogId pageId =
|
let tryFindPage conn webLogId pageId =
|
||||||
@ -21,14 +21,14 @@ let tryFindPage conn webLogId pageId =
|
|||||||
.RunAtomAsync<Page>(conn) |> await |> box with
|
.RunAtomAsync<Page>(conn) |> await |> box with
|
||||||
| null -> None
|
| null -> None
|
||||||
| page -> let pg : Page = unbox page
|
| page -> let pg : Page = unbox page
|
||||||
match pg.webLogId = webLogId with
|
match pg.WebLogId = webLogId with
|
||||||
| true -> Some pg
|
| true -> Some pg
|
||||||
| _ -> None
|
| _ -> None
|
||||||
|
|
||||||
/// Get a page by its Id (excluding revisions)
|
/// Get a page by its Id (excluding revisions)
|
||||||
let tryFindPageWithoutRevisions conn webLogId pageId : Page option =
|
let tryFindPageWithoutRevisions conn webLogId pageId : Page option =
|
||||||
match (page webLogId pageId)
|
match (page webLogId pageId)
|
||||||
.Without("revisions")
|
.Without("Revisions")
|
||||||
.RunAtomAsync<Page>(conn) |> await |> box with
|
.RunAtomAsync<Page>(conn) |> await |> box with
|
||||||
| null -> None
|
| null -> None
|
||||||
| page -> Some <| unbox page
|
| page -> Some <| unbox page
|
||||||
@ -36,8 +36,8 @@ 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(r.Array(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
|
||||||
|> Seq.tryHead
|
|> Seq.tryHead
|
||||||
@ -45,32 +45,32 @@ let tryFindPageByPermalink conn (webLogId : string) (permalink : string) =
|
|||||||
/// Get a list of all pages (excludes page text and revisions)
|
/// Get a list of all pages (excludes page text and revisions)
|
||||||
let findAllPages conn (webLogId : string) =
|
let findAllPages conn (webLogId : string) =
|
||||||
r.Table(Table.Page)
|
r.Table(Table.Page)
|
||||||
.GetAll(webLogId).OptArg("index", "webLogId")
|
.GetAll(webLogId).OptArg("index", "WebLogId")
|
||||||
.OrderBy("title")
|
.OrderBy("Title")
|
||||||
.Without("text", "revisions")
|
.Without("Text", "Revisions")
|
||||||
.RunListAsync<Page>(conn)
|
.RunListAsync<Page>(conn)
|
||||||
|> await
|
|> await
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
|
|
||||||
/// Save a page
|
/// Save a page
|
||||||
let savePage conn (pg : Page) =
|
let savePage conn (pg : Page) =
|
||||||
match pg.id with
|
match pg.Id with
|
||||||
| "new" -> let newPage = { pg with id = string <| System.Guid.NewGuid() }
|
| "new" -> let newPage = { pg with Id = string <| System.Guid.NewGuid() }
|
||||||
r.Table(Table.Page)
|
r.Table(Table.Page)
|
||||||
.Insert(page)
|
.Insert(page)
|
||||||
.RunResultAsync(conn) |> await |> ignore
|
.RunResultAsync(conn) |> await |> ignore
|
||||||
newPage.id
|
newPage.Id
|
||||||
| _ -> let upd8 = ExpandoObject()
|
| _ -> let upd8 = ExpandoObject()
|
||||||
upd8?title <- pg.title
|
upd8?Title <- pg.Title
|
||||||
upd8?permalink <- pg.permalink
|
upd8?Permalink <- pg.Permalink
|
||||||
upd8?publishedOn <- pg.publishedOn
|
upd8?PublishedOn <- pg.PublishedOn
|
||||||
upd8?updatedOn <- pg.updatedOn
|
upd8?UpdatedOn <- pg.UpdatedOn
|
||||||
upd8?text <- pg.text
|
upd8?Text <- pg.Text
|
||||||
upd8?revisions <- pg.revisions
|
upd8?Revisions <- pg.Revisions
|
||||||
(page pg.webLogId pg.id)
|
(page pg.WebLogId pg.Id)
|
||||||
.Update(upd8)
|
.Update(upd8)
|
||||||
.RunResultAsync(conn) |> await |> ignore
|
.RunResultAsync(conn) |> await |> ignore
|
||||||
pg.id
|
pg.Id
|
||||||
|
|
||||||
/// Delete a page
|
/// Delete a page
|
||||||
let deletePage conn webLogId pageId =
|
let deletePage conn webLogId pageId =
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
module myWebLog.Data.Post
|
module MyWebLog.Data.Post
|
||||||
|
|
||||||
open FSharp.Interop.Dynamic
|
open FSharp.Interop.Dynamic
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Rethink
|
open Rethink
|
||||||
open RethinkDb.Driver.Ast
|
open RethinkDb.Driver.Ast
|
||||||
open System.Dynamic
|
open System.Dynamic
|
||||||
@ -11,22 +11,20 @@ let private r = RethinkDb.Driver.RethinkDB.R
|
|||||||
/// Shorthand to select all published posts for a web log
|
/// Shorthand to select all published posts for a web log
|
||||||
let private publishedPosts (webLogId : string)=
|
let private publishedPosts (webLogId : string)=
|
||||||
r.Table(Table.Post)
|
r.Table(Table.Post)
|
||||||
.GetAll(r.Array(webLogId, PostStatus.Published)).OptArg("index", "webLogAndStatus")
|
.GetAll(r.Array(webLogId, PostStatus.Published)).OptArg("index", "WebLogAndStatus")
|
||||||
|
|
||||||
/// Shorthand to sort posts by published date, slice for the given page, and return a list
|
/// Shorthand to sort posts by published date, slice for the given page, and return a list
|
||||||
let private toPostList conn pageNbr nbrPerPage (filter : ReqlExpr) =
|
let private toPostList conn pageNbr nbrPerPage (filter : ReqlExpr) =
|
||||||
filter
|
filter
|
||||||
.OrderBy(r.Desc("publishedOn"))
|
.OrderBy(r.Desc("PublishedOn"))
|
||||||
.Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage)
|
.Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage)
|
||||||
.RunListAsync<Post>(conn)
|
.RunListAsync<Post>(conn)
|
||||||
|> await
|
|> await
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
|
|
||||||
/// Shorthand to get a newer or older post
|
/// Shorthand to get a newer or older post
|
||||||
// TODO: older posts need to sort by published on DESC
|
let private adjacentPost conn post (theFilter : ReqlExpr -> obj) (sort : obj) =
|
||||||
//let private adjacentPost conn post (theFilter : ReqlExpr -> ReqlExpr) (sort :ReqlExpr) : Post option =
|
(publishedPosts post.WebLogId)
|
||||||
let private adjacentPost conn post (theFilter : ReqlExpr -> obj) (sort : obj) : Post option =
|
|
||||||
(publishedPosts post.webLogId)
|
|
||||||
.Filter(theFilter)
|
.Filter(theFilter)
|
||||||
.OrderBy(sort)
|
.OrderBy(sort)
|
||||||
.Limit(1)
|
.Limit(1)
|
||||||
@ -48,45 +46,45 @@ 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(ReqlFunction1(fun p -> upcast 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(ReqlFunction1(fun p -> upcast 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
|
||||||
let tryFindNewerPost conn post = newerPost conn post (fun p -> upcast p.["publishedOn"].Gt(post.publishedOn))
|
let tryFindNewerPost conn post = newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn))
|
||||||
|
|
||||||
/// Try to get the next newest post assigned to the given category
|
/// Try to get the next newest post assigned to the given category
|
||||||
let tryFindNewerCategorizedPost conn (categoryId : string) post =
|
let tryFindNewerCategorizedPost conn (categoryId : string) post =
|
||||||
newerPost conn post (fun p -> upcast p.["publishedOn"].Gt(post.publishedOn)
|
newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn)
|
||||||
.And(p.["categoryIds"].Contains(categoryId)))
|
.And(p.["CategoryIds"].Contains(categoryId)))
|
||||||
|
|
||||||
/// Try to get the next newest tagged post from the given tagged post
|
/// Try to get the next newest tagged post from the given tagged post
|
||||||
let tryFindNewerTaggedPost conn (tag : string) post =
|
let tryFindNewerTaggedPost conn (tag : string) post =
|
||||||
newerPost conn post (fun p -> upcast p.["publishedOn"].Gt(post.publishedOn).And(p.["tags"].Contains(tag)))
|
newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn).And(p.["Tags"].Contains(tag)))
|
||||||
|
|
||||||
/// Try to get the next oldest post from the given post
|
/// Try to get the next oldest post from the given post
|
||||||
let tryFindOlderPost conn post = olderPost conn post (fun p -> upcast p.["publishedOn"].Lt(post.publishedOn))
|
let tryFindOlderPost conn post = olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn))
|
||||||
|
|
||||||
/// Try to get the next oldest post assigned to the given category
|
/// Try to get the next oldest post assigned to the given category
|
||||||
let tryFindOlderCategorizedPost conn (categoryId : string) post =
|
let tryFindOlderCategorizedPost conn (categoryId : string) post =
|
||||||
olderPost conn post (fun p -> upcast p.["publishedOn"].Lt(post.publishedOn)
|
olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn)
|
||||||
.And(p.["categoryIds"].Contains(categoryId)))
|
.And(p.["CategoryIds"].Contains(categoryId)))
|
||||||
|
|
||||||
/// Try to get the next oldest tagged post from the given tagged post
|
/// Try to get the next oldest tagged post from the given tagged post
|
||||||
let tryFindOlderTaggedPost conn (tag : string) post =
|
let tryFindOlderTaggedPost conn (tag : string) post =
|
||||||
olderPost conn post (fun p -> upcast p.["publishedOn"].Lt(post.publishedOn).And(p.["tags"].Contains(tag)))
|
olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn).And(p.["Tags"].Contains(tag)))
|
||||||
|
|
||||||
/// Get a page of all posts in all statuses
|
/// Get a page of all posts in all statuses
|
||||||
let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage =
|
let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage =
|
||||||
// FIXME: sort unpublished posts by their last updated date
|
// FIXME: sort unpublished posts by their last updated date
|
||||||
r.Table(Table.Post)
|
r.Table(Table.Post)
|
||||||
.GetAll(webLogId).OptArg("index", "webLogId")
|
.GetAll(webLogId).OptArg("index", "WebLogId")
|
||||||
.OrderBy(r.Desc("publishedOn"))
|
.OrderBy(r.Desc("PublishedOn"))
|
||||||
.Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage)
|
.Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage)
|
||||||
.RunListAsync<Post>(conn)
|
.RunListAsync<Post>(conn)
|
||||||
|> await
|
|> await
|
||||||
@ -96,7 +94,7 @@ let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage =
|
|||||||
let tryFindPost conn webLogId postId : Post option =
|
let tryFindPost conn webLogId postId : Post option =
|
||||||
match r.Table(Table.Post)
|
match r.Table(Table.Post)
|
||||||
.Get(postId)
|
.Get(postId)
|
||||||
.Filter(fun p -> p.["webLogId"].Eq(webLogId))
|
.Filter(ReqlFunction1(fun p -> upcast p.["WebLogId"].Eq(webLogId)))
|
||||||
.RunAtomAsync<Post>(conn)
|
.RunAtomAsync<Post>(conn)
|
||||||
|> box with
|
|> box with
|
||||||
| null -> None
|
| null -> None
|
||||||
@ -106,27 +104,22 @@ let tryFindPost conn webLogId postId : Post option =
|
|||||||
// TODO: see if we can make .Merge work for page list even though the attribute is ignored
|
// 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)
|
// (needs to be ignored for serialization, but included for deserialization)
|
||||||
let tryFindPostByPermalink conn webLogId permalink =
|
let tryFindPostByPermalink conn webLogId permalink =
|
||||||
match r.Table(Table.Post)
|
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 p -> r.HashMap("Categories", r.Table(Table.Category)
|
||||||
|
.GetAll(p.["CategoryIds"])
|
||||||
|
.Without("Children")
|
||||||
|
.OrderBy("Name")
|
||||||
|
.CoerceTo("array")))
|
||||||
|
.Merge(fun p -> r.HashMap("Comments", r.Table(Table.Comment)
|
||||||
|
.GetAll(p.["Id"]).OptArg("index", "PostId")
|
||||||
|
.OrderBy("PostedOn")
|
||||||
|
.CoerceTo("array")))
|
||||||
.RunCursorAsync<Post>(conn)
|
.RunCursorAsync<Post>(conn)
|
||||||
|> await
|
|> await
|
||||||
|> Seq.tryHead with
|
|> Seq.tryHead
|
||||||
| Some p -> Some { p with categories = r.Table(Table.Category)
|
|
||||||
.GetAll(p.categoryIds |> List.toArray)
|
|
||||||
.Without("children")
|
|
||||||
.OrderBy("name")
|
|
||||||
.RunListAsync<Category>(conn)
|
|
||||||
|> await
|
|
||||||
|> Seq.toList
|
|
||||||
comments = r.Table(Table.Comment)
|
|
||||||
.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) =
|
||||||
@ -140,34 +133,34 @@ let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) =
|
|||||||
|
|
||||||
/// Get a set of posts for RSS
|
/// Get a set of posts for RSS
|
||||||
let findFeedPosts conn webLogId nbr : (Post * User option) list =
|
let findFeedPosts conn webLogId nbr : (Post * User option) list =
|
||||||
findPageOfPublishedPosts conn webLogId 1 nbr
|
(publishedPosts webLogId)
|
||||||
|> List.map (fun post -> { post with categories = r.Table(Table.Category)
|
.Merge(fun post -> r.HashMap("Categories", r.Table(Table.Category)
|
||||||
.GetAll(post.categoryIds |> List.toArray)
|
.GetAll(post.["CategoryIds"])
|
||||||
.OrderBy("name")
|
.OrderBy("Name")
|
||||||
.Pluck("id", "name")
|
.Pluck("Id", "Name")
|
||||||
.RunListAsync<Category>(conn)
|
.CoerceTo("array")))
|
||||||
|> await
|
|> toPostList conn 1 nbr
|
||||||
|> Seq.toList },
|
|> List.map (fun post -> post, match r.Table(Table.User)
|
||||||
(match r.Table(Table.User)
|
.Get(post.AuthorId)
|
||||||
.Get(post.authorId)
|
|
||||||
.RunAtomAsync<User>(conn)
|
.RunAtomAsync<User>(conn)
|
||||||
|> await
|
|> await
|
||||||
|> box with
|
|> box with
|
||||||
| null -> None
|
| null -> None
|
||||||
| user -> Some <| unbox user))
|
| user -> Some <| unbox user)
|
||||||
|
|
||||||
/// Save a post
|
/// Save a post
|
||||||
let savePost conn post =
|
let savePost conn post =
|
||||||
match post.id with
|
match post.Id with
|
||||||
| "new" -> let newPost = { post with id = string <| System.Guid.NewGuid() }
|
| "new" -> let newPost = { post with Id = string <| System.Guid.NewGuid() }
|
||||||
r.Table(Table.Post)
|
r.Table(Table.Post)
|
||||||
.Insert(newPost)
|
.Insert(newPost)
|
||||||
.RunResultAsync(conn)
|
.RunResultAsync(conn)
|
||||||
|> ignore
|
|> ignore
|
||||||
newPost.id
|
newPost.Id
|
||||||
| _ -> r.Table(Table.Post)
|
| _ -> r.Table(Table.Post)
|
||||||
.Get(post.id)
|
.Get(post.Id)
|
||||||
.Replace(post)
|
.Replace( { post with Categories = List.empty
|
||||||
|
Comments = List.empty } )
|
||||||
.RunResultAsync(conn)
|
.RunResultAsync(conn)
|
||||||
|> ignore
|
|> ignore
|
||||||
post.id
|
post.Id
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module myWebLog.Data.Rethink
|
module MyWebLog.Data.Rethink
|
||||||
|
|
||||||
open RethinkDb.Driver.Ast
|
open RethinkDb.Driver.Ast
|
||||||
open RethinkDb.Driver.Net
|
open RethinkDb.Driver.Net
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module myWebLog.Data.SetUp
|
module MyWebLog.Data.SetUp
|
||||||
|
|
||||||
open Rethink
|
open Rethink
|
||||||
open RethinkDb.Driver.Ast
|
open RethinkDb.Driver.Ast
|
||||||
@ -12,17 +12,17 @@ let private logStepDone () = Console.Out.WriteLine (" done.")
|
|||||||
/// Ensure the myWebLog database exists
|
/// Ensure the myWebLog database exists
|
||||||
let checkDatabase (cfg : DataConfig) =
|
let checkDatabase (cfg : DataConfig) =
|
||||||
logStep "|> Checking database"
|
logStep "|> Checking database"
|
||||||
let dbs = r.DbList().RunListAsync<string>(cfg.conn) |> await
|
let dbs = r.DbList().RunListAsync<string>(cfg.Conn) |> await
|
||||||
match dbs.Contains cfg.database with
|
match dbs.Contains cfg.Database with
|
||||||
| true -> ()
|
| true -> ()
|
||||||
| _ -> logStepStart (sprintf " %s database not found - creating" cfg.database)
|
| _ -> logStepStart (sprintf " %s database not found - creating" cfg.Database)
|
||||||
r.DbCreate(cfg.database).RunResultAsync(cfg.conn) |> await |> ignore
|
r.DbCreate(cfg.Database).RunResultAsync(cfg.Conn) |> await |> ignore
|
||||||
logStepDone ()
|
logStepDone ()
|
||||||
|
|
||||||
/// Ensure all required tables exist
|
/// Ensure all required tables exist
|
||||||
let checkTables cfg =
|
let checkTables cfg =
|
||||||
logStep "|> Checking tables"
|
logStep "|> Checking tables"
|
||||||
let tables = r.Db(cfg.database).TableList().RunListAsync<string>(cfg.conn) |> await
|
let tables = r.Db(cfg.Database).TableList().RunListAsync<string>(cfg.Conn) |> await
|
||||||
[ Table.Category; Table.Comment; Table.Page; Table.Post; Table.User; Table.WebLog ]
|
[ Table.Category; Table.Comment; Table.Page; Table.Post; Table.User; Table.WebLog ]
|
||||||
|> List.map (fun tbl -> match tables.Contains tbl with
|
|> List.map (fun tbl -> match tables.Contains tbl with
|
||||||
| true -> None
|
| true -> None
|
||||||
@ -30,27 +30,27 @@ let checkTables cfg =
|
|||||||
|> List.filter (fun create -> create.IsSome)
|
|> List.filter (fun create -> create.IsSome)
|
||||||
|> List.map (fun create -> create.Value)
|
|> List.map (fun create -> create.Value)
|
||||||
|> List.iter (fun (tbl, create) -> logStepStart (sprintf " Creating table %s" tbl)
|
|> List.iter (fun (tbl, create) -> logStepStart (sprintf " Creating table %s" tbl)
|
||||||
create.RunResultAsync(cfg.conn) |> await |> ignore
|
create.RunResultAsync(cfg.Conn) |> await |> ignore
|
||||||
logStepDone ())
|
logStepDone ())
|
||||||
|
|
||||||
/// Shorthand to get the table
|
/// Shorthand to get the table
|
||||||
let tbl cfg table = r.Db(cfg.database).Table(table)
|
let tbl cfg table = r.Db(cfg.Database).Table(table)
|
||||||
|
|
||||||
/// Create the given index
|
/// Create the given index
|
||||||
let createIndex cfg table (index : string * (ReqlExpr -> obj) option) =
|
let createIndex cfg table (index : string * (ReqlExpr -> obj) option) =
|
||||||
let idxName, idxFunc = index
|
let idxName, idxFunc = index
|
||||||
logStepStart (sprintf """ Creating index "%s" on table %s""" idxName table)
|
logStepStart (sprintf """ Creating index "%s" on table %s""" idxName table)
|
||||||
match idxFunc with
|
match idxFunc with
|
||||||
| Some f -> (tbl cfg table).IndexCreate(idxName, f).RunResultAsync(cfg.conn)
|
| Some f -> (tbl cfg table).IndexCreate(idxName, f).RunResultAsync(cfg.Conn)
|
||||||
| None -> (tbl cfg table).IndexCreate(idxName ).RunResultAsync(cfg.conn)
|
| None -> (tbl cfg table).IndexCreate(idxName ).RunResultAsync(cfg.Conn)
|
||||||
|> await |> ignore
|
|> await |> ignore
|
||||||
(tbl cfg table).IndexWait(idxName).RunAtomAsync(cfg.conn) |> await |> ignore
|
(tbl cfg table).IndexWait(idxName).RunAtomAsync(cfg.Conn) |> await |> ignore
|
||||||
logStepDone ()
|
logStepDone ()
|
||||||
|
|
||||||
/// Ensure that the given indexes exist, and create them if required
|
/// Ensure that the given indexes exist, and create them if required
|
||||||
let ensureIndexes cfg (indexes : (string * (string * (ReqlExpr -> obj) option) list) list) =
|
let ensureIndexes cfg (indexes : (string * (string * (ReqlExpr -> obj) option) list) list) =
|
||||||
let ensureForTable tabl =
|
let ensureForTable tabl =
|
||||||
let idx = (tbl cfg (fst tabl)).IndexList().RunListAsync<string>(cfg.conn) |> await
|
let idx = (tbl cfg (fst tabl)).IndexList().RunListAsync<string>(cfg.Conn) |> await
|
||||||
snd tabl
|
snd tabl
|
||||||
|> List.iter (fun index -> match idx.Contains (fst index) with
|
|> List.iter (fun index -> match idx.Contains (fst index) with
|
||||||
| true -> ()
|
| true -> ()
|
||||||
@ -68,21 +68,21 @@ let webLogField (name : string) : (ReqlExpr -> obj) option =
|
|||||||
/// Ensure all the required indexes exist
|
/// Ensure all the required indexes exist
|
||||||
let checkIndexes cfg =
|
let checkIndexes cfg =
|
||||||
logStep "|> Checking indexes"
|
logStep "|> Checking indexes"
|
||||||
[ Table.Category, [ "webLogId", None
|
[ Table.Category, [ "WebLogId", None
|
||||||
"slug", webLogField "slug"
|
"Slug", webLogField "Slug"
|
||||||
]
|
]
|
||||||
Table.Comment, [ "postId", None
|
Table.Comment, [ "PostId", None
|
||||||
]
|
]
|
||||||
Table.Page, [ "webLogId", None
|
Table.Page, [ "WebLogId", None
|
||||||
"permalink", webLogField "permalink"
|
"Permalink", webLogField "Permalink"
|
||||||
]
|
]
|
||||||
Table.Post, [ "webLogId", None
|
Table.Post, [ "WebLogId", None
|
||||||
"webLogAndStatus", webLogField "status"
|
"WebLogAndStatus", webLogField "Status"
|
||||||
"permalink", webLogField "permalink"
|
"Permalink", webLogField "Permalink"
|
||||||
]
|
]
|
||||||
Table.User, [ "userName", None
|
Table.User, [ "UserName", None
|
||||||
]
|
]
|
||||||
Table.WebLog, [ "urlBase", None
|
Table.WebLog, [ "UrlBase", None
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|> ensureIndexes cfg
|
|> ensureIndexes cfg
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module myWebLog.Data.Table
|
module MyWebLog.Data.Table
|
||||||
|
|
||||||
/// The Category table
|
/// The Category table
|
||||||
let Category = "Category"
|
let Category = "Category"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
module myWebLog.Data.User
|
module MyWebLog.Data.User
|
||||||
|
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Rethink
|
open Rethink
|
||||||
|
|
||||||
let private r = RethinkDb.Driver.RethinkDB.R
|
let private r = RethinkDb.Driver.RethinkDB.R
|
||||||
@ -11,8 +11,8 @@ let private r = RethinkDb.Driver.RethinkDB.R
|
|||||||
// http://rethinkdb.com/docs/secondary-indexes/java/ for more information.
|
// http://rethinkdb.com/docs/secondary-indexes/java/ for more information.
|
||||||
let tryUserLogOn conn (email : string) (passwordHash : string) =
|
let tryUserLogOn conn (email : string) (passwordHash : string) =
|
||||||
r.Table(Table.User)
|
r.Table(Table.User)
|
||||||
.GetAll(email).OptArg("index", "userName")
|
.GetAll(email).OptArg("index", "UserName")
|
||||||
.Filter(fun u -> u.["passwordHash"].Eq(passwordHash))
|
.Filter(fun u -> u.["PasswordHash"].Eq(passwordHash))
|
||||||
.RunCursorAsync<User>(conn)
|
.RunCursorAsync<User>(conn)
|
||||||
|> await
|
|> await
|
||||||
|> Seq.tryHead
|
|> Seq.tryHead
|
||||||
|
@ -1,43 +1,39 @@
|
|||||||
module myWebLog.Data.WebLog
|
module MyWebLog.Data.WebLog
|
||||||
|
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Rethink
|
open Rethink
|
||||||
|
open RethinkDb.Driver.Ast
|
||||||
|
|
||||||
let private r = RethinkDb.Driver.RethinkDB.R
|
let private r = RethinkDb.Driver.RethinkDB.R
|
||||||
|
|
||||||
/// Counts of items displayed on the admin dashboard
|
/// Counts of items displayed on the admin dashboard
|
||||||
type DashboardCounts = {
|
type DashboardCounts =
|
||||||
|
{ /// The number of pages for the web log
|
||||||
|
Pages : int
|
||||||
/// The number of pages for the web log
|
/// The number of pages for the web log
|
||||||
pages : int
|
Posts : int
|
||||||
/// The number of pages for the web log
|
|
||||||
posts : int
|
|
||||||
/// The number of categories for the web log
|
/// The number of categories for the web log
|
||||||
categories : int
|
Categories : int }
|
||||||
}
|
|
||||||
|
|
||||||
/// Detemine the web log by the URL base
|
/// Detemine the web log by the URL base
|
||||||
// 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 tryFindWebLogByUrlBase conn (urlBase : string) =
|
let tryFindWebLogByUrlBase conn (urlBase : string) =
|
||||||
let webLog = r.Table(Table.WebLog)
|
r.Table(Table.WebLog)
|
||||||
.GetAll(urlBase).OptArg("index", "urlBase")
|
.GetAll(urlBase).OptArg("index", "urlBase")
|
||||||
|
.Merge(fun w -> r.HashMap("PageList", r.Table(Table.Page)
|
||||||
|
.GetAll(w.["Id"]).OptArg("index", "WebLogId")
|
||||||
|
.Filter(ReqlFunction1(fun pg -> upcast pg.["ShowInPageList"].Eq(true)))
|
||||||
|
.OrderBy("Title")
|
||||||
|
.Pluck("Title", "Permalink")
|
||||||
|
.CoerceTo("array")))
|
||||||
.RunCursorAsync<WebLog>(conn)
|
.RunCursorAsync<WebLog>(conn)
|
||||||
|> await
|
|> await
|
||||||
|> Seq.tryHead
|
|> Seq.tryHead
|
||||||
match webLog with
|
|
||||||
| Some w -> Some { w with pageList = r.Table(Table.Page)
|
|
||||||
.GetAll(w.id).OptArg("index", "webLogId")
|
|
||||||
.Filter(fun pg -> pg.["showInPageList"].Eq(true))
|
|
||||||
.OrderBy("title")
|
|
||||||
.Pluck("title", "permalink")
|
|
||||||
.RunListAsync<PageListEntry>(conn) |> await |> Seq.toList }
|
|
||||||
| None -> None
|
|
||||||
|
|
||||||
/// Get counts for the admin dashboard
|
/// Get counts for the admin dashboard
|
||||||
let findDashboardCounts conn (webLogId : string) =
|
let findDashboardCounts conn (webLogId : string) =
|
||||||
r.Expr( r.HashMap("pages", r.Table(Table.Page ).GetAll(webLogId).OptArg("index", "webLogId").Count()))
|
r.Expr( r.HashMap("Pages", r.Table(Table.Page ).GetAll(webLogId).OptArg("index", "WebLogId").Count()))
|
||||||
.Merge(r.HashMap("posts", r.Table(Table.Post ).GetAll(webLogId).OptArg("index", "webLogId").Count()))
|
.Merge(r.HashMap("Posts", r.Table(Table.Post ).GetAll(webLogId).OptArg("index", "WebLogId").Count()))
|
||||||
.Merge(r.HashMap("categories", r.Table(Table.Category).GetAll(webLogId).OptArg("index", "webLogId").Count()))
|
.Merge(r.HashMap("Categories", r.Table(Table.Category).GetAll(webLogId).OptArg("index", "WebLogId").Count()))
|
||||||
.RunAtomAsync<DashboardCounts>(conn)
|
.RunAtomAsync<DashboardCounts>(conn)
|
||||||
|> await
|
|> await
|
||||||
|
|
@ -8,7 +8,7 @@
|
|||||||
<ProjectGuid>1fba0b84-b09e-4b16-b9b6-5730dea27192</ProjectGuid>
|
<ProjectGuid>1fba0b84-b09e-4b16-b9b6-5730dea27192</ProjectGuid>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RootNamespace>myWebLog.Data</RootNamespace>
|
<RootNamespace>myWebLog.Data</RootNamespace>
|
||||||
<AssemblyName>myWebLog.Data</AssemblyName>
|
<AssemblyName>MyWebLog.Data</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
|
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
@ -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>
|
||||||
|
4
src/myWebLog.Resources/Resources.Designer.cs
generated
4
src/myWebLog.Resources/Resources.Designer.cs
generated
@ -8,7 +8,7 @@
|
|||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace myWebLog {
|
namespace MyWebLog {
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ namespace myWebLog {
|
|||||||
public static global::System.Resources.ResourceManager ResourceManager {
|
public static global::System.Resources.ResourceManager ResourceManager {
|
||||||
get {
|
get {
|
||||||
if (object.ReferenceEquals(resourceMan, null)) {
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("myWebLog.Resources", typeof(Resources).Assembly);
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MyWebLog.Resources", typeof(Resources).Assembly);
|
||||||
resourceMan = temp;
|
resourceMan = temp;
|
||||||
}
|
}
|
||||||
return resourceMan;
|
return resourceMan;
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
<ProjectGuid>{A12EA8DA-88BC-4447-90CB-A0E2DCC37523}</ProjectGuid>
|
<ProjectGuid>{A12EA8DA-88BC-4447-90CB-A0E2DCC37523}</ProjectGuid>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
<RootNamespace>myWebLog</RootNamespace>
|
<RootNamespace>MyWebLog</RootNamespace>
|
||||||
<AssemblyName>myWebLog.Resources</AssemblyName>
|
<AssemblyName>MyWebLog.Resources</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
namespace myWebLog
|
namespace MyWebLog
|
||||||
|
|
||||||
open myWebLog.Data.WebLog
|
open MyWebLog.Data.WebLog
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open RethinkDb.Driver.Net
|
open RethinkDb.Driver.Net
|
||||||
|
|
||||||
@ -15,6 +15,6 @@ type AdminModule(conn : IConnection) as this =
|
|||||||
/// 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
|
||||||
upcast this.View.["admin/dashboard", model]
|
upcast this.View.["admin/dashboard", model]
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
module myWebLog.App
|
module MyWebLog.App
|
||||||
|
|
||||||
open myWebLog
|
open MyWebLog
|
||||||
open myWebLog.Data
|
open MyWebLog.Data
|
||||||
open myWebLog.Data.SetUp
|
open MyWebLog.Data.SetUp
|
||||||
open myWebLog.Data.WebLog
|
open MyWebLog.Data.WebLog
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.Authentication.Forms
|
open Nancy.Authentication.Forms
|
||||||
open Nancy.Bootstrapper
|
open Nancy.Bootstrapper
|
||||||
@ -25,7 +25,7 @@ open System.IO
|
|||||||
open System.Text.RegularExpressions
|
open System.Text.RegularExpressions
|
||||||
|
|
||||||
/// Set up a database connection
|
/// Set up a database connection
|
||||||
let cfg = try DataConfig.fromJson (System.IO.File.ReadAllText "data-config.json")
|
let cfg = try DataConfig.FromJson (System.IO.File.ReadAllText "data-config.json")
|
||||||
with ex -> raise <| ApplicationException(Resources.ErrDataConfig, ex)
|
with ex -> raise <| ApplicationException(Resources.ErrDataConfig, ex)
|
||||||
|
|
||||||
do
|
do
|
||||||
@ -37,7 +37,7 @@ type TranslateTokenViewEngineMatcher() =
|
|||||||
interface ISuperSimpleViewEngineMatcher with
|
interface ISuperSimpleViewEngineMatcher with
|
||||||
member this.Invoke (content, model, host) =
|
member this.Invoke (content, model, host) =
|
||||||
regex.Replace(content, fun m -> let key = m.Groups.["TranslationKey"].Value
|
regex.Replace(content, fun m -> let key = m.Groups.["TranslationKey"].Value
|
||||||
match myWebLog.Resources.ResourceManager.GetString key with
|
match MyWebLog.Resources.ResourceManager.GetString key with
|
||||||
| null -> key
|
| null -> key
|
||||||
| xlat -> xlat)
|
| xlat -> xlat)
|
||||||
|
|
||||||
@ -54,8 +54,8 @@ type MyWebLogUserMapper(container : TinyIoCContainer) =
|
|||||||
|
|
||||||
interface IUserMapper with
|
interface IUserMapper with
|
||||||
member this.GetUserFromIdentifier (identifier, context) =
|
member this.GetUserFromIdentifier (identifier, context) =
|
||||||
match context.Request.PersistableSession.GetOrDefault(Keys.User, User.empty) with
|
match context.Request.PersistableSession.GetOrDefault(Keys.User, User.Empty) with
|
||||||
| user when user.id = string identifier -> upcast MyWebLogUser(user.preferredName, user.claims)
|
| user when user.Id = string identifier -> upcast MyWebLogUser(user.PreferredName, user.Claims)
|
||||||
| _ -> null
|
| _ -> null
|
||||||
|
|
||||||
|
|
||||||
@ -87,7 +87,7 @@ type MyWebLogBootstrapper() =
|
|||||||
// Data configuration (both config and the connection; Nancy modules just need the connection)
|
// Data configuration (both config and the connection; Nancy modules just need the connection)
|
||||||
container.Register<DataConfig>(cfg)
|
container.Register<DataConfig>(cfg)
|
||||||
|> ignore
|
|> ignore
|
||||||
container.Register<IConnection>(cfg.conn)
|
container.Register<IConnection>(cfg.Conn)
|
||||||
|> ignore
|
|> ignore
|
||||||
// NodaTime
|
// NodaTime
|
||||||
container.Register<IClock>(SystemClock.Instance)
|
container.Register<IClock>(SystemClock.Instance)
|
||||||
@ -109,8 +109,8 @@ type MyWebLogBootstrapper() =
|
|||||||
// CSRF
|
// CSRF
|
||||||
Csrf.Enable pipelines
|
Csrf.Enable pipelines
|
||||||
// Sessions
|
// Sessions
|
||||||
let sessions = RethinkDbSessionConfiguration(cfg.conn)
|
let sessions = RethinkDbSessionConfiguration(cfg.Conn)
|
||||||
sessions.Database <- cfg.database
|
sessions.Database <- cfg.Database
|
||||||
PersistableSessions.Enable (pipelines, sessions)
|
PersistableSessions.Enable (pipelines, sessions)
|
||||||
()
|
()
|
||||||
|
|
||||||
@ -128,17 +128,18 @@ let version =
|
|||||||
type RequestEnvironment() =
|
type RequestEnvironment() =
|
||||||
interface IRequestStartup with
|
interface IRequestStartup with
|
||||||
member this.Initialize (pipelines, context) =
|
member this.Initialize (pipelines, context) =
|
||||||
pipelines.BeforeRequest.AddItemToStartOfPipeline
|
let establishEnv (ctx : NancyContext) =
|
||||||
(fun ctx -> ctx.Items.[Keys.RequestStart] <- DateTime.Now.Ticks
|
ctx.Items.[Keys.RequestStart] <- DateTime.Now.Ticks
|
||||||
match tryFindWebLogByUrlBase cfg.conn ctx.Request.Url.HostName with
|
match tryFindWebLogByUrlBase cfg.Conn ctx.Request.Url.HostName with
|
||||||
| Some webLog -> ctx.Items.[Keys.WebLog] <- webLog
|
| Some webLog -> ctx.Items.[Keys.WebLog] <- webLog
|
||||||
| None -> ApplicationException
|
| None -> // TODO: redirect to domain set up page
|
||||||
(sprintf "%s %s" ctx.Request.Url.HostName Resources.ErrNotConfigured)
|
ApplicationException (sprintf "%s %s" ctx.Request.Url.HostName Resources.ErrNotConfigured)
|
||||||
|> raise
|
|> raise
|
||||||
ctx.Items.[Keys.Version] <- version
|
ctx.Items.[Keys.Version] <- version
|
||||||
null)
|
null
|
||||||
|
pipelines.BeforeRequest.AddItemToStartOfPipeline establishEnv
|
||||||
|
|
||||||
|
|
||||||
let app = OwinApp.ofMidFunc "/" (NancyMiddleware.UseNancy (NancyOptions()))
|
let app = OwinApp.ofMidFunc "/" (NancyMiddleware.UseNancy (NancyOptions()))
|
||||||
|
|
||||||
let run () = startWebServer defaultConfig app // webPart
|
let Run () = startWebServer defaultConfig app // webPart
|
||||||
|
@ -4,11 +4,11 @@ open System.Reflection
|
|||||||
open System.Runtime.CompilerServices
|
open System.Runtime.CompilerServices
|
||||||
open System.Runtime.InteropServices
|
open System.Runtime.InteropServices
|
||||||
|
|
||||||
[<assembly: AssemblyTitle("myWebLog.Web")>]
|
[<assembly: AssemblyTitle("MyWebLog.Web")>]
|
||||||
[<assembly: AssemblyDescription("Main Nancy assembly for myWebLog")>]
|
[<assembly: AssemblyDescription("Main Nancy assembly for myWebLog")>]
|
||||||
[<assembly: AssemblyConfiguration("")>]
|
[<assembly: AssemblyConfiguration("")>]
|
||||||
[<assembly: AssemblyCompany("DJS Consulting")>]
|
[<assembly: AssemblyCompany("DJS Consulting")>]
|
||||||
[<assembly: AssemblyProduct("myWebLog.Web")>]
|
[<assembly: AssemblyProduct("MyWebLog.Web")>]
|
||||||
[<assembly: AssemblyCopyright("Copyright © 2016")>]
|
[<assembly: AssemblyCopyright("Copyright © 2016")>]
|
||||||
[<assembly: AssemblyTrademark("")>]
|
[<assembly: AssemblyTrademark("")>]
|
||||||
[<assembly: AssemblyCulture("")>]
|
[<assembly: AssemblyCulture("")>]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
namespace myWebLog
|
namespace MyWebLog
|
||||||
|
|
||||||
open myWebLog.Data.Category
|
open MyWebLog.Data.Category
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.ModelBinding
|
open Nancy.ModelBinding
|
||||||
open Nancy.Security
|
open Nancy.Security
|
||||||
@ -21,24 +21,21 @@ type CategoryModule(conn : IConnection) as this =
|
|||||||
member this.CategoryList () =
|
member this.CategoryList () =
|
||||||
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
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))))
|
||||||
upcast 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) =
|
||||||
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
let catId : string = downcast parameters.["id"]
|
let catId = parameters.["id"].ToString ()
|
||||||
match (match catId with
|
match (match catId with
|
||||||
| "new" -> Some Category.empty
|
| "new" -> Some Category.empty
|
||||||
| _ -> tryFindCategory conn this.WebLog.id catId) with
|
| _ -> tryFindCategory conn this.WebLog.Id catId) with
|
||||||
| Some cat -> let model = CategoryEditModel(this.Context, this.WebLog, cat)
|
| Some cat -> let model = CategoryEditModel(this.Context, this.WebLog, cat)
|
||||||
let cats = 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 ""))
|
||||||
model.categories <- getAllCategories conn this.WebLog.id
|
|
||||||
|> List.map (fun cat -> IndentedCategory.create cat
|
|
||||||
(fun c -> c = defaultArg (fst cat).parentId ""))
|
|
||||||
upcast this.View.["admin/category/edit", model]
|
upcast this.View.["admin/category/edit", model]
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
@ -46,32 +43,32 @@ type CategoryModule(conn : IConnection) as this =
|
|||||||
member this.SaveCategory (parameters : DynamicDictionary) =
|
member this.SaveCategory (parameters : DynamicDictionary) =
|
||||||
this.ValidateCsrfToken ()
|
this.ValidateCsrfToken ()
|
||||||
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
let catId : string = downcast parameters.["id"]
|
let catId = parameters.["id"].ToString ()
|
||||||
let form = this.Bind<CategoryForm> ()
|
let form = this.Bind<CategoryForm> ()
|
||||||
let oldCat = match catId with
|
let oldCat = match catId with
|
||||||
| "new" -> Some Category.empty
|
| "new" -> Some Category.empty
|
||||||
| _ -> tryFindCategory conn this.WebLog.id catId
|
| _ -> tryFindCategory conn this.WebLog.Id catId
|
||||||
match oldCat with
|
match oldCat with
|
||||||
| Some old -> let cat = { old with name = form.name
|
| Some old -> let cat = { old with Name = form.Name
|
||||||
slug = form.slug
|
Slug = form.Slug
|
||||||
description = match form.description with | "" -> None | d -> Some d
|
Description = match form.Description with "" -> None | d -> Some d
|
||||||
parentId = match form.parentId with | "" -> None | p -> Some p }
|
ParentId = match form.ParentId with "" -> None | p -> Some p }
|
||||||
let newCatId = saveCategory conn this.WebLog.id cat
|
let newCatId = saveCategory conn this.WebLog.Id cat
|
||||||
match old.parentId = cat.parentId with
|
match old.ParentId = cat.ParentId with
|
||||||
| true -> ()
|
| true -> ()
|
||||||
| _ -> match old.parentId with
|
| _ -> match old.ParentId with
|
||||||
| Some parentId -> removeCategoryFromParent conn this.WebLog.id parentId newCatId
|
| Some parentId -> removeCategoryFromParent conn this.WebLog.Id parentId newCatId
|
||||||
| None -> ()
|
| None -> ()
|
||||||
match cat.parentId with
|
match cat.ParentId with
|
||||||
| Some parentId -> addCategoryToParent conn this.WebLog.id parentId newCatId
|
| Some parentId -> addCategoryToParent conn this.WebLog.Id parentId newCatId
|
||||||
| None -> ()
|
| None -> ()
|
||||||
let model = MyWebLogModel(this.Context, this.WebLog)
|
let model = MyWebLogModel(this.Context, this.WebLog)
|
||||||
{ level = Level.Info
|
{ UserMessage.Empty with
|
||||||
message = System.String.Format
|
Level = Level.Info
|
||||||
|
Message = System.String.Format
|
||||||
(Resources.MsgCategoryEditSuccess,
|
(Resources.MsgCategoryEditSuccess,
|
||||||
(match catId with | "new" -> Resources.Added | _ -> Resources.Updated))
|
(match catId with | "new" -> Resources.Added | _ -> Resources.Updated)) }
|
||||||
details = None }
|
|> model.AddMessage
|
||||||
|> model.addMessage
|
|
||||||
this.Redirect (sprintf "/category/%s/edit" newCatId) model
|
this.Redirect (sprintf "/category/%s/edit" newCatId) model
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
@ -79,13 +76,12 @@ type CategoryModule(conn : IConnection) as this =
|
|||||||
member this.DeleteCategory (parameters : DynamicDictionary) =
|
member this.DeleteCategory (parameters : DynamicDictionary) =
|
||||||
this.ValidateCsrfToken ()
|
this.ValidateCsrfToken ()
|
||||||
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
let catId : string = downcast parameters.["id"]
|
let catId = parameters.["id"].ToString ()
|
||||||
match tryFindCategory conn this.WebLog.id catId with
|
match tryFindCategory conn this.WebLog.Id catId with
|
||||||
| Some cat -> deleteCategory conn cat
|
| Some cat -> deleteCategory conn cat
|
||||||
let model = MyWebLogModel(this.Context, this.WebLog)
|
let model = MyWebLogModel(this.Context, this.WebLog)
|
||||||
{ level = Level.Info
|
{ UserMessage.Empty with Level = Level.Info
|
||||||
message = System.String.Format(Resources.MsgCategoryDeleted, cat.name)
|
Message = System.String.Format(Resources.MsgCategoryDeleted, cat.Name) }
|
||||||
details = None }
|
|> model.AddMessage
|
||||||
|> model.addMessage
|
|
||||||
this.Redirect "/categories" model
|
this.Redirect "/categories" model
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
module myWebLog.Keys
|
module MyWebLog.Keys
|
||||||
|
|
||||||
let Messages = "messages"
|
let Messages = "messages"
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
[<AutoOpen>]
|
[<AutoOpen>]
|
||||||
module myWebLog.ModuleExtensions
|
module MyWebLog.ModuleExtensions
|
||||||
|
|
||||||
open myWebLog
|
open MyWebLog.Entities
|
||||||
open myWebLog.Entities
|
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.Security
|
open Nancy.Security
|
||||||
|
|
||||||
@ -14,19 +13,19 @@ type NancyModule with
|
|||||||
|
|
||||||
/// 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 : MyWebLogModel) : obj =
|
member this.ThemedView view (model : MyWebLogModel) : obj =
|
||||||
upcast this.View.[(sprintf "themes/%s/%s" this.WebLog.themePath view), model]
|
upcast this.View.[(sprintf "themes/%s/%s" this.WebLog.ThemePath view), model]
|
||||||
|
|
||||||
/// Return a 404
|
/// Return a 404
|
||||||
member this.NotFound () : obj = upcast HttpStatusCode.NotFound
|
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) : obj =
|
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
|
||||||
upcast this.Response.AsRedirect(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 =
|
||||||
this.RequiresAuthentication()
|
this.RequiresAuthentication()
|
||||||
this.RequiresClaims [| sprintf "%s|%s" this.WebLog.id level |]
|
this.RequiresClaims [| sprintf "%s|%s" this.WebLog.Id level |]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
namespace myWebLog
|
namespace MyWebLog
|
||||||
|
|
||||||
open FSharp.Markdown
|
open FSharp.Markdown
|
||||||
open myWebLog.Data.Page
|
open MyWebLog.Data.Page
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.ModelBinding
|
open Nancy.ModelBinding
|
||||||
open Nancy.Security
|
open Nancy.Security
|
||||||
@ -22,9 +22,9 @@ type PageModule(conn : IConnection, clock : IClock) as this =
|
|||||||
/// 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
|
||||||
|> List.map (fun p -> PageForDisplay(this.WebLog, p))))
|
|> List.map (fun p -> PageForDisplay(this.WebLog, p))))
|
||||||
model.pageTitle <- Resources.Pages
|
model.PageTitle <- Resources.Pages
|
||||||
upcast this.View.["admin/page/list", model]
|
upcast this.View.["admin/page/list", model]
|
||||||
|
|
||||||
/// Edit a page
|
/// Edit a page
|
||||||
@ -32,17 +32,15 @@ type PageModule(conn : IConnection, clock : IClock) as this =
|
|||||||
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
let pageId = parameters.["id"].ToString ()
|
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
|
||||||
| Some page -> let rev = match page.revisions
|
| Some page -> let rev = match page.Revisions
|
||||||
|> List.sortByDescending (fun r -> r.asOf)
|
|> List.sortByDescending (fun r -> r.AsOf)
|
||||||
|> List.tryHead with
|
|> List.tryHead with
|
||||||
| Some r -> r
|
| Some r -> r
|
||||||
| None -> Revision.empty
|
| None -> Revision.Empty
|
||||||
let model = EditPageModel(this.Context, this.WebLog, page, rev)
|
let model = EditPageModel(this.Context, this.WebLog, page, rev)
|
||||||
model.pageTitle <- match pageId with
|
model.PageTitle <- match pageId with "new" -> Resources.AddNewPage | _ -> Resources.EditPage
|
||||||
| "new" -> Resources.AddNewPage
|
|
||||||
| _ -> Resources.EditPage
|
|
||||||
upcast this.View.["admin/page/edit", model]
|
upcast this.View.["admin/page/edit", model]
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
@ -54,30 +52,28 @@ type PageModule(conn : IConnection, clock : IClock) as this =
|
|||||||
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
|
||||||
| Some p -> let page = match pageId with
|
| Some p -> let page = match pageId with "new" -> { p with WebLogId = this.WebLog.Id } | _ -> p
|
||||||
| "new" -> { p with webLogId = this.WebLog.id }
|
|
||||||
| _ -> p
|
|
||||||
let pId = { p with
|
let pId = { p with
|
||||||
title = form.title
|
Title = form.Title
|
||||||
permalink = form.permalink
|
Permalink = form.Permalink
|
||||||
publishedOn = match pageId with | "new" -> now | _ -> page.publishedOn
|
PublishedOn = match pageId with "new" -> now | _ -> page.PublishedOn
|
||||||
updatedOn = now
|
UpdatedOn = now
|
||||||
text = match form.source with
|
Text = match form.Source with
|
||||||
| RevisionSource.Markdown -> Markdown.TransformHtml form.text
|
| RevisionSource.Markdown -> Markdown.TransformHtml form.Text
|
||||||
| _ -> form.text
|
| _ -> form.Text
|
||||||
revisions = { asOf = now
|
Revisions = { AsOf = now
|
||||||
sourceType = form.source
|
SourceType = form.Source
|
||||||
text = form.text } :: page.revisions }
|
Text = form.Text } :: page.Revisions }
|
||||||
|> savePage conn
|
|> savePage conn
|
||||||
let model = MyWebLogModel(this.Context, this.WebLog)
|
let model = MyWebLogModel(this.Context, this.WebLog)
|
||||||
{ level = Level.Info
|
{ UserMessage.Empty with
|
||||||
message = System.String.Format
|
Level = Level.Info
|
||||||
|
Message = System.String.Format
|
||||||
(Resources.MsgPageEditSuccess,
|
(Resources.MsgPageEditSuccess,
|
||||||
(match pageId with | "new" -> Resources.Added | _ -> Resources.Updated))
|
(match pageId with | "new" -> Resources.Added | _ -> Resources.Updated)) }
|
||||||
details = None }
|
|> model.AddMessage
|
||||||
|> model.addMessage
|
|
||||||
this.Redirect (sprintf "/page/%s/edit" pId) model
|
this.Redirect (sprintf "/page/%s/edit" pId) model
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
@ -86,12 +82,11 @@ type PageModule(conn : IConnection, clock : IClock) as this =
|
|||||||
this.ValidateCsrfToken ()
|
this.ValidateCsrfToken ()
|
||||||
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
let pageId = parameters.["id"].ToString ()
|
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)
|
||||||
{ level = Level.Info
|
{ UserMessage.Empty with Level = Level.Info
|
||||||
message = Resources.MsgPageDeleted
|
Message = Resources.MsgPageDeleted }
|
||||||
details = None }
|
|> model.AddMessage
|
||||||
|> model.addMessage
|
|
||||||
this.Redirect "/pages" model
|
this.Redirect "/pages" model
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
namespace myWebLog
|
namespace MyWebLog
|
||||||
|
|
||||||
open FSharp.Markdown
|
open FSharp.Markdown
|
||||||
open myWebLog.Data.Category
|
open MyWebLog.Data.Category
|
||||||
open myWebLog.Data.Page
|
open MyWebLog.Data.Page
|
||||||
open myWebLog.Data.Post
|
open MyWebLog.Data.Post
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.ModelBinding
|
open Nancy.ModelBinding
|
||||||
open Nancy.Security
|
open Nancy.Security
|
||||||
@ -20,39 +20,39 @@ type PostModule(conn : IConnection, clock : IClock) as this =
|
|||||||
|
|
||||||
/// Get the page number from the dictionary
|
/// Get the page number from the dictionary
|
||||||
let getPage (parameters : DynamicDictionary) =
|
let getPage (parameters : DynamicDictionary) =
|
||||||
match parameters.ContainsKey "page" with | true -> System.Int32.Parse (parameters.["page"].ToString ()) | _ -> 1
|
match parameters.ContainsKey "page" with true -> System.Int32.Parse (parameters.["page"].ToString ()) | _ -> 1
|
||||||
|
|
||||||
/// Convert a list of posts to a list of posts for display
|
/// Convert a list of posts to a list of posts for display
|
||||||
let forDisplay posts = posts |> List.map (fun post -> PostForDisplay(this.WebLog, post))
|
let forDisplay posts = posts |> List.map (fun post -> PostForDisplay(this.WebLog, post))
|
||||||
|
|
||||||
/// Generate an RSS/Atom feed of the latest posts
|
/// Generate an RSS/Atom feed of the latest posts
|
||||||
let generateFeed format : obj =
|
let generateFeed format : obj =
|
||||||
let posts = findFeedPosts conn this.WebLog.id 10
|
let posts = findFeedPosts conn this.WebLog.Id 10
|
||||||
let feed =
|
let feed =
|
||||||
SyndicationFeed(
|
SyndicationFeed(
|
||||||
this.WebLog.name, defaultArg this.WebLog.subtitle null,
|
this.WebLog.Name, defaultArg this.WebLog.Subtitle null,
|
||||||
Uri(sprintf "%s://%s" this.Request.Url.Scheme this.WebLog.urlBase), null,
|
Uri(sprintf "%s://%s" this.Request.Url.Scheme this.WebLog.UrlBase), null,
|
||||||
(match posts |> List.tryHead with
|
(match posts |> List.tryHead with
|
||||||
| Some (post, _) -> Instant(post.updatedOn).ToDateTimeOffset ()
|
| Some (post, _) -> Instant(post.UpdatedOn).ToDateTimeOffset ()
|
||||||
| _ -> System.DateTimeOffset(System.DateTime.MinValue)),
|
| _ -> System.DateTimeOffset(System.DateTime.MinValue)),
|
||||||
posts
|
posts
|
||||||
|> List.map (fun (post, user) ->
|
|> List.map (fun (post, user) ->
|
||||||
let item =
|
let item =
|
||||||
SyndicationItem(
|
SyndicationItem(
|
||||||
BaseUri = Uri(sprintf "%s://%s/%s" this.Request.Url.Scheme this.WebLog.urlBase post.permalink),
|
BaseUri = Uri(sprintf "%s://%s/%s" this.Request.Url.Scheme this.WebLog.UrlBase post.Permalink),
|
||||||
PublishDate = Instant(post.publishedOn).ToDateTimeOffset (),
|
PublishDate = Instant(post.PublishedOn).ToDateTimeOffset (),
|
||||||
LastUpdatedTime = Instant(post.updatedOn).ToDateTimeOffset (),
|
LastUpdatedTime = Instant(post.UpdatedOn).ToDateTimeOffset (),
|
||||||
Title = TextSyndicationContent(post.title),
|
Title = TextSyndicationContent(post.Title),
|
||||||
Content = TextSyndicationContent(post.text, TextSyndicationContentKind.Html))
|
Content = TextSyndicationContent(post.Text, TextSyndicationContentKind.Html))
|
||||||
user
|
user
|
||||||
|> Option.iter (fun u -> item.Authors.Add
|
|> Option.iter (fun u -> item.Authors.Add
|
||||||
(SyndicationPerson(u.userName, u.preferredName, defaultArg u.url null)))
|
(SyndicationPerson(u.UserName, u.PreferredName, defaultArg u.Url null)))
|
||||||
post.categories
|
post.Categories
|
||||||
|> List.iter (fun c -> item.Categories.Add(SyndicationCategory(c.name)))
|
|> List.iter (fun c -> item.Categories.Add(SyndicationCategory(c.Name)))
|
||||||
item))
|
item))
|
||||||
let stream = new IO.MemoryStream()
|
let stream = new IO.MemoryStream()
|
||||||
Xml.XmlWriter.Create(stream)
|
Xml.XmlWriter.Create(stream)
|
||||||
|> match format with | "atom" -> feed.SaveAsAtom10 | _ -> feed.SaveAsRss20
|
|> match format with "atom" -> feed.SaveAsAtom10 | _ -> feed.SaveAsRss20
|
||||||
stream.Position <- int64 0
|
stream.Position <- int64 0
|
||||||
upcast this.Response.FromStream(stream, sprintf "application/%s+xml" format)
|
upcast this.Response.FromStream(stream, sprintf "application/%s+xml" format)
|
||||||
|
|
||||||
@ -75,77 +75,77 @@ type PostModule(conn : IConnection, clock : IClock) as this =
|
|||||||
/// Display a page of published posts
|
/// Display a page of published posts
|
||||||
member this.PublishedPostsPage pageNbr =
|
member this.PublishedPostsPage pageNbr =
|
||||||
let model = PostsModel(this.Context, this.WebLog)
|
let model = PostsModel(this.Context, this.WebLog)
|
||||||
model.pageNbr <- pageNbr
|
model.PageNbr <- pageNbr
|
||||||
model.posts <- findPageOfPublishedPosts conn this.WebLog.id pageNbr 10 |> forDisplay
|
model.Posts <- findPageOfPublishedPosts conn this.WebLog.Id pageNbr 10 |> forDisplay
|
||||||
model.hasNewer <- match pageNbr with
|
model.HasNewer <- match pageNbr with
|
||||||
| 1 -> false
|
| 1 -> false
|
||||||
| _ -> match List.isEmpty model.posts with
|
| _ -> match List.isEmpty model.Posts with
|
||||||
| true -> false
|
| true -> false
|
||||||
| _ -> Option.isSome <| tryFindNewerPost conn (List.last model.posts).post
|
| _ -> Option.isSome <| tryFindNewerPost conn (List.last 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 <| tryFindOlderPost conn (List.head model.posts).post
|
| _ -> Option.isSome <| tryFindOlderPost conn (List.head model.Posts).Post
|
||||||
model.urlPrefix <- "/posts"
|
model.UrlPrefix <- "/posts"
|
||||||
model.pageTitle <- match pageNbr with
|
model.PageTitle <- match pageNbr with
|
||||||
| 1 -> ""
|
| 1 -> ""
|
||||||
| _ -> sprintf "%s%i" Resources.PageHash pageNbr
|
| _ -> sprintf "%s%i" Resources.PageHash pageNbr
|
||||||
this.ThemedView "index" model
|
this.ThemedView "index" model
|
||||||
|
|
||||||
/// Display either the newest posts or the configured home page
|
/// Display either the newest posts or the configured home page
|
||||||
member this.HomePage () =
|
member this.HomePage () =
|
||||||
match this.WebLog.defaultPage with
|
match this.WebLog.DefaultPage with
|
||||||
| "posts" -> this.PublishedPostsPage 1
|
| "posts" -> this.PublishedPostsPage 1
|
||||||
| pageId -> match tryFindPageWithoutRevisions conn this.WebLog.id pageId with
|
| pageId -> match tryFindPageWithoutRevisions conn this.WebLog.Id pageId with
|
||||||
| Some page -> let model = PageModel(this.Context, this.WebLog, page)
|
| Some page -> let model = PageModel(this.Context, this.WebLog, page)
|
||||||
model.pageTitle <- page.title
|
model.PageTitle <- page.Title
|
||||||
this.ThemedView "page" model
|
this.ThemedView "page" model
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
/// Derive a post or page from the URL, or redirect from a prior URL to the current one
|
/// Derive a post or page from the URL, or redirect from a prior URL to the current one
|
||||||
member this.CatchAll (parameters : DynamicDictionary) =
|
member this.CatchAll (parameters : DynamicDictionary) =
|
||||||
let url = parameters.["permalink"].ToString ()
|
let url = parameters.["permalink"].ToString ()
|
||||||
match tryFindPostByPermalink conn this.WebLog.id url with
|
match tryFindPostByPermalink conn this.WebLog.Id url with
|
||||||
| Some post -> // Hopefully the most common result; the permalink is a permalink!
|
| Some post -> // Hopefully the most common result; the permalink is a permalink!
|
||||||
let model = PostModel(this.Context, this.WebLog, post)
|
let model = PostModel(this.Context, this.WebLog, post)
|
||||||
model.newerPost <- tryFindNewerPost conn post
|
model.NewerPost <- tryFindNewerPost conn post
|
||||||
model.olderPost <- tryFindOlderPost conn post
|
model.OlderPost <- tryFindOlderPost conn post
|
||||||
model.pageTitle <- post.title
|
model.PageTitle <- post.Title
|
||||||
this.ThemedView "single" model
|
this.ThemedView "single" model
|
||||||
| None -> // Maybe it's a page permalink instead...
|
| None -> // Maybe it's a page permalink instead...
|
||||||
match tryFindPageByPermalink conn this.WebLog.id url with
|
match tryFindPageByPermalink conn this.WebLog.Id url with
|
||||||
| Some page -> // ...and it is!
|
| Some page -> // ...and it is!
|
||||||
let model = PageModel(this.Context, this.WebLog, page)
|
let model = PageModel(this.Context, this.WebLog, page)
|
||||||
model.pageTitle <- page.title
|
model.PageTitle <- page.Title
|
||||||
this.ThemedView "page" model
|
this.ThemedView "page" model
|
||||||
| 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
|
||||||
upcast this.Response.AsRedirect(sprintf "/%s" post.permalink)
|
upcast this.Response.AsRedirect(sprintf "/%s" post.Permalink)
|
||||||
.WithStatusCode HttpStatusCode.MovedPermanently
|
.WithStatusCode HttpStatusCode.MovedPermanently
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
/// Display categorized posts
|
/// Display categorized posts
|
||||||
member this.CategorizedPosts (parameters : DynamicDictionary) =
|
member this.CategorizedPosts (parameters : DynamicDictionary) =
|
||||||
let slug = parameters.["slug"].ToString ()
|
let slug = parameters.["slug"].ToString ()
|
||||||
match tryFindCategoryBySlug conn this.WebLog.id slug with
|
match tryFindCategoryBySlug conn this.WebLog.Id slug with
|
||||||
| Some cat -> let pageNbr = getPage parameters
|
| Some cat -> let pageNbr = getPage parameters
|
||||||
let model = PostsModel(this.Context, this.WebLog)
|
let model = PostsModel(this.Context, this.WebLog)
|
||||||
model.pageNbr <- pageNbr
|
model.PageNbr <- pageNbr
|
||||||
model.posts <- findPageOfCategorizedPosts conn this.WebLog.id cat.id pageNbr 10 |> forDisplay
|
model.Posts <- findPageOfCategorizedPosts conn this.WebLog.Id cat.Id 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 <| tryFindNewerCategorizedPost conn cat.id
|
| _ -> Option.isSome <| tryFindNewerCategorizedPost conn cat.Id
|
||||||
(List.head 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
|
||||||
(List.last model.posts).post
|
(List.last model.Posts).Post
|
||||||
model.urlPrefix <- sprintf "/category/%s" slug
|
model.UrlPrefix <- sprintf "/category/%s" slug
|
||||||
model.pageTitle <- sprintf "\"%s\" Category%s" cat.name
|
model.PageTitle <- sprintf "\"%s\" Category%s" cat.Name
|
||||||
(match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n)
|
(match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n)
|
||||||
model.subtitle <- Some <| match cat.description with
|
model.Subtitle <- Some <| match cat.Description with
|
||||||
| Some desc -> desc
|
| Some desc -> desc
|
||||||
| None -> sprintf "Posts in the \"%s\" category" cat.name
|
| None -> sprintf "Posts in the \"%s\" category" cat.Name
|
||||||
this.ThemedView "index" model
|
this.ThemedView "index" model
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
@ -154,17 +154,17 @@ type PostModule(conn : IConnection, clock : IClock) as this =
|
|||||||
let tag = parameters.["tag"].ToString ()
|
let tag = parameters.["tag"].ToString ()
|
||||||
let pageNbr = getPage parameters
|
let pageNbr = getPage parameters
|
||||||
let model = PostsModel(this.Context, this.WebLog)
|
let model = PostsModel(this.Context, this.WebLog)
|
||||||
model.pageNbr <- pageNbr
|
model.PageNbr <- pageNbr
|
||||||
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.head 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
|
||||||
model.urlPrefix <- sprintf "/tag/%s" tag
|
model.UrlPrefix <- sprintf "/tag/%s" tag
|
||||||
model.pageTitle <- sprintf "\"%s\" Tag%s" tag (match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n)
|
model.PageTitle <- sprintf "\"%s\" Tag%s" tag (match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n)
|
||||||
model.subtitle <- Some <| sprintf "Posts tagged \"%s\"" tag
|
model.Subtitle <- Some <| sprintf "Posts tagged \"%s\"" tag
|
||||||
this.ThemedView "index" model
|
this.ThemedView "index" model
|
||||||
|
|
||||||
/// Generate an RSS feed
|
/// Generate an RSS feed
|
||||||
@ -183,35 +183,33 @@ type PostModule(conn : IConnection, clock : IClock) as this =
|
|||||||
member this.PostList pageNbr =
|
member this.PostList pageNbr =
|
||||||
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
let model = PostsModel(this.Context, this.WebLog)
|
let model = PostsModel(this.Context, this.WebLog)
|
||||||
model.pageNbr <- pageNbr
|
model.PageNbr <- pageNbr
|
||||||
model.posts <- findPageOfAllPosts conn this.WebLog.id pageNbr 25 |> forDisplay
|
model.Posts <- findPageOfAllPosts conn this.WebLog.Id pageNbr 25 |> forDisplay
|
||||||
model.hasNewer <- pageNbr > 1
|
model.HasNewer <- pageNbr > 1
|
||||||
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
|
||||||
upcast 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) =
|
||||||
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
let postId : string = downcast parameters.["postId"]
|
let postId = parameters.["postId"].ToString ()
|
||||||
match (match postId with
|
match (match postId with
|
||||||
| "new" -> Some Post.empty
|
| "new" -> Some Post.Empty
|
||||||
| _ -> tryFindPost conn this.WebLog.id postId) with
|
| _ -> tryFindPost conn this.WebLog.Id postId) with
|
||||||
| Some post -> let rev = match post.revisions
|
| Some post -> let rev = match post.Revisions
|
||||||
|> List.sortByDescending (fun r -> r.asOf)
|
|> List.sortByDescending (fun r -> r.AsOf)
|
||||||
|> List.tryHead with
|
|> List.tryHead with
|
||||||
| Some r -> r
|
| Some r -> r
|
||||||
| None -> Revision.empty
|
| None -> Revision.Empty
|
||||||
let model = EditPostModel(this.Context, this.WebLog, post, rev)
|
let model = EditPostModel(this.Context, this.WebLog, post, rev)
|
||||||
model.categories <- getAllCategories conn this.WebLog.id
|
model.Categories <- getAllCategories conn this.WebLog.Id
|
||||||
|> List.map (fun cat -> string (fst cat).id,
|
|> List.map (fun cat -> string (fst cat).Id,
|
||||||
sprintf "%s%s"
|
sprintf "%s%s"
|
||||||
(String.replicate (snd cat) " ")
|
(String.replicate (snd cat) " ")
|
||||||
(fst cat).name)
|
(fst cat).Name)
|
||||||
model.pageTitle <- match post.id with
|
model.PageTitle <- match post.Id with "new" -> Resources.AddNewPost | _ -> Resources.EditPost
|
||||||
| "new" -> Resources.AddNewPost
|
|
||||||
| _ -> Resources.EditPost
|
|
||||||
upcast this.View.["admin/post/edit"]
|
upcast this.View.["admin/post/edit"]
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
@ -219,45 +217,45 @@ type PostModule(conn : IConnection, clock : IClock) as this =
|
|||||||
member this.SavePost (parameters : DynamicDictionary) =
|
member this.SavePost (parameters : DynamicDictionary) =
|
||||||
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
this.ValidateCsrfToken ()
|
this.ValidateCsrfToken ()
|
||||||
let postId : string = downcast parameters.["postId"]
|
let postId = parameters.["postId"].ToString ()
|
||||||
let form = this.Bind<EditPostForm>()
|
let form = this.Bind<EditPostForm>()
|
||||||
let now = clock.Now.Ticks
|
let now = clock.Now.Ticks
|
||||||
match (match postId with
|
match (match postId with
|
||||||
| "new" -> Some Post.empty
|
| "new" -> Some Post.Empty
|
||||||
| _ -> tryFindPost conn this.WebLog.id postId) with
|
| _ -> tryFindPost conn this.WebLog.Id postId) with
|
||||||
| Some p -> let justPublished = p.publishedOn = int64 0 && form.publishNow
|
| Some p -> let justPublished = p.PublishedOn = int64 0 && form.PublishNow
|
||||||
let post = match postId with
|
let post = match postId with
|
||||||
| "new" -> { p with
|
| "new" -> { p with
|
||||||
webLogId = this.WebLog.id
|
WebLogId = this.WebLog.Id
|
||||||
authorId = (this.Request.PersistableSession.GetOrDefault<User>
|
AuthorId = (this.Request.PersistableSession.GetOrDefault<User>
|
||||||
(Keys.User, User.empty)).id }
|
(Keys.User, User.Empty)).Id }
|
||||||
| _ -> p
|
| _ -> p
|
||||||
let pId = { post with
|
let pId = { post with
|
||||||
status = match form.publishNow with
|
Status = match form.PublishNow with
|
||||||
| true -> PostStatus.Published
|
| true -> PostStatus.Published
|
||||||
| _ -> PostStatus.Draft
|
| _ -> PostStatus.Draft
|
||||||
title = form.title
|
Title = form.Title
|
||||||
permalink = form.permalink
|
Permalink = form.Permalink
|
||||||
publishedOn = match justPublished with | true -> now | _ -> int64 0
|
PublishedOn = match justPublished with true -> now | _ -> int64 0
|
||||||
updatedOn = now
|
UpdatedOn = now
|
||||||
text = match form.source with
|
Text = match form.Source with
|
||||||
| RevisionSource.Markdown -> Markdown.TransformHtml form.text
|
| RevisionSource.Markdown -> Markdown.TransformHtml form.Text
|
||||||
| _ -> form.text
|
| _ -> form.Text
|
||||||
categoryIds = Array.toList form.categories
|
CategoryIds = Array.toList form.Categories
|
||||||
tags = form.tags.Split ','
|
Tags = form.Tags.Split ','
|
||||||
|> Seq.map (fun t -> t.Trim().ToLowerInvariant())
|
|> Seq.map (fun t -> t.Trim().ToLowerInvariant())
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
revisions = { asOf = now
|
Revisions = { AsOf = now
|
||||||
sourceType = form.source
|
SourceType = form.Source
|
||||||
text = form.text } :: post.revisions }
|
Text = form.Text } :: post.Revisions }
|
||||||
|> savePost conn
|
|> savePost conn
|
||||||
let model = MyWebLogModel(this.Context, this.WebLog)
|
let model = MyWebLogModel(this.Context, this.WebLog)
|
||||||
{ level = Level.Info
|
{ UserMessage.Empty with
|
||||||
message = System.String.Format
|
Level = Level.Info
|
||||||
|
Message = System.String.Format
|
||||||
(Resources.MsgPostEditSuccess,
|
(Resources.MsgPostEditSuccess,
|
||||||
(match postId with | "new" -> Resources.Added | _ -> Resources.Updated),
|
(match postId with | "new" -> Resources.Added | _ -> Resources.Updated),
|
||||||
(match justPublished with | true -> Resources.AndPublished | _ -> ""))
|
(match justPublished with | true -> Resources.AndPublished | _ -> "")) }
|
||||||
details = None }
|
|> model.AddMessage
|
||||||
|> model.addMessage
|
|
||||||
this.Redirect (sprintf "/post/%s/edit" pId) model
|
this.Redirect (sprintf "/post/%s/edit" pId) model
|
||||||
| None -> this.NotFound ()
|
| None -> this.NotFound ()
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
namespace myWebLog
|
namespace MyWebLog
|
||||||
|
|
||||||
open myWebLog.Data.User
|
open MyWebLog.Data.User
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.Authentication.Forms
|
open Nancy.Authentication.Forms
|
||||||
open Nancy.Cryptography
|
open Nancy.Cryptography
|
||||||
@ -21,15 +21,16 @@ 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 -> this.ShowLogOn (downcast parms)
|
this.Get .["/logon" ] <- fun _ -> this.ShowLogOn ()
|
||||||
this.Post.["/logon" ] <- fun parms -> this.DoLogOn (downcast parms)
|
this.Post.["/logon" ] <- fun parms -> this.DoLogOn (downcast parms)
|
||||||
this.Get .["/logoff"] <- fun parms -> this.LogOff ()
|
this.Get .["/logoff"] <- fun _ -> this.LogOff ()
|
||||||
|
|
||||||
/// Show the log on page
|
/// Show the log on page
|
||||||
member this.ShowLogOn (parameters : DynamicDictionary) =
|
member this.ShowLogOn () =
|
||||||
let model = LogOnModel(this.Context, this.WebLog)
|
let model = LogOnModel(this.Context, this.WebLog)
|
||||||
model.form.returnUrl <- match parameters.ContainsKey "returnUrl" with
|
let query = this.Request.Query :?> DynamicDictionary
|
||||||
| true -> parameters.["returnUrl"].ToString ()
|
model.Form.ReturnUrl <- match query.ContainsKey "returnUrl" with
|
||||||
|
| true -> query.["returnUrl"].ToString ()
|
||||||
| _ -> ""
|
| _ -> ""
|
||||||
upcast this.View.["admin/user/logon", model]
|
upcast this.View.["admin/user/logon", model]
|
||||||
|
|
||||||
@ -38,30 +39,28 @@ type UserModule(conn : IConnection) as this =
|
|||||||
this.ValidateCsrfToken ()
|
this.ValidateCsrfToken ()
|
||||||
let form = this.Bind<LogOnForm> ()
|
let form = this.Bind<LogOnForm> ()
|
||||||
let model = MyWebLogModel(this.Context, this.WebLog)
|
let model = MyWebLogModel(this.Context, this.WebLog)
|
||||||
match tryUserLogOn conn form.email (pbkdf2 form.password) with
|
match tryUserLogOn conn form.Email (pbkdf2 form.Password) with
|
||||||
| Some user -> this.Session.[Keys.User] <- user
|
| Some user -> this.Session.[Keys.User] <- user
|
||||||
{ level = Level.Info
|
{ UserMessage.Empty with Level = Level.Info
|
||||||
message = Resources.MsgLogOnSuccess
|
Message = Resources.MsgLogOnSuccess }
|
||||||
details = None }
|
|> 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
|
||||||
upcast this.LoginAndRedirect (System.Guid.Parse user.id,
|
upcast this.LoginAndRedirect (System.Guid.Parse user.Id,
|
||||||
fallbackRedirectUrl = defaultArg (Option.ofObj(form.returnUrl)) "/")
|
fallbackRedirectUrl = defaultArg (Option.ofObj form.ReturnUrl) "/")
|
||||||
| None -> { level = Level.Error
|
| None -> { UserMessage.Empty with Level = Level.Error
|
||||||
message = Resources.ErrBadLogOnAttempt
|
Message = Resources.ErrBadLogOnAttempt }
|
||||||
details = None }
|
|> model.AddMessage
|
||||||
|> model.addMessage
|
this.Redirect (sprintf "/user/logon?returnUrl=%s" form.ReturnUrl) model
|
||||||
this.Redirect (sprintf "/user/logon?returnUrl=%s" form.returnUrl) model
|
|
||||||
|
|
||||||
/// Log a user off
|
/// Log a user off
|
||||||
member this.LogOff () =
|
member this.LogOff () =
|
||||||
let user = this.Request.PersistableSession.GetOrDefault<User> (Keys.User, User.empty)
|
// FIXME: why are we getting the user here if we don't do anything with it?
|
||||||
|
let user = this.Request.PersistableSession.GetOrDefault<User> (Keys.User, User.Empty)
|
||||||
this.Session.DeleteAll ()
|
this.Session.DeleteAll ()
|
||||||
let model = MyWebLogModel(this.Context, this.WebLog)
|
let model = MyWebLogModel(this.Context, this.WebLog)
|
||||||
{ level = Level.Info
|
{ UserMessage.Empty with Level = Level.Info
|
||||||
message = Resources.MsgLogOffSuccess
|
Message = Resources.MsgLogOffSuccess }
|
||||||
details = None }
|
|> model.AddMessage
|
||||||
|> model.addMessage
|
|
||||||
this.Redirect "" model |> ignore
|
this.Redirect "" model |> ignore
|
||||||
upcast this.LogoutAndRedirect "/"
|
upcast this.LogoutAndRedirect "/"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
namespace myWebLog
|
namespace MyWebLog
|
||||||
|
|
||||||
open myWebLog.Data.WebLog
|
open MyWebLog.Data.WebLog
|
||||||
open myWebLog.Entities
|
open MyWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.Session.Persistable
|
open Nancy.Session.Persistable
|
||||||
open Newtonsoft.Json
|
open Newtonsoft.Json
|
||||||
@ -21,25 +21,23 @@ module Level =
|
|||||||
|
|
||||||
|
|
||||||
/// A message for the user
|
/// A message for the user
|
||||||
type UserMessage = {
|
type UserMessage =
|
||||||
/// The level of the message (use Level module constants)
|
{ /// The level of the message (use Level module constants)
|
||||||
level : string
|
Level : string
|
||||||
/// The text of the message
|
/// The text of the message
|
||||||
message : string
|
Message : string
|
||||||
/// Further details regarding the message
|
/// Further details regarding the message
|
||||||
details : string option
|
Details : string option }
|
||||||
}
|
|
||||||
with
|
with
|
||||||
/// An empty message
|
/// An empty message
|
||||||
static member empty = {
|
static member Empty =
|
||||||
level = Level.Info
|
{ Level = Level.Info
|
||||||
message = ""
|
Message = ""
|
||||||
details = None
|
Details = None }
|
||||||
}
|
|
||||||
|
|
||||||
/// Display version
|
/// Display version
|
||||||
[<JsonIgnore>]
|
[<JsonIgnore>]
|
||||||
member this.toDisplay =
|
member this.ToDisplay =
|
||||||
let classAndLabel =
|
let classAndLabel =
|
||||||
dict [
|
dict [
|
||||||
Level.Error, ("danger", Resources.Error)
|
Level.Error, ("danger", Resources.Error)
|
||||||
@ -48,23 +46,23 @@ with
|
|||||||
]
|
]
|
||||||
seq {
|
seq {
|
||||||
yield "<div class=\"alert alert-dismissable alert-"
|
yield "<div class=\"alert alert-dismissable alert-"
|
||||||
yield fst classAndLabel.[this.level]
|
yield fst classAndLabel.[this.Level]
|
||||||
yield "\" role=\"alert\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\""
|
yield "\" role=\"alert\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\""
|
||||||
yield Resources.Close
|
yield Resources.Close
|
||||||
yield "\">×</button><strong>"
|
yield "\">×</button><strong>"
|
||||||
match snd classAndLabel.[this.level] with
|
match snd classAndLabel.[this.Level] with
|
||||||
| "" -> ()
|
| "" -> ()
|
||||||
| lbl -> yield lbl.ToUpper ()
|
| lbl -> yield lbl.ToUpper ()
|
||||||
yield " » "
|
yield " » "
|
||||||
yield this.message
|
yield this.Message
|
||||||
yield "</strong>"
|
yield "</strong>"
|
||||||
match this.details with
|
match this.Details with
|
||||||
| Some d -> yield "<br />"
|
| Some d -> yield "<br />"
|
||||||
yield d
|
yield d
|
||||||
| None -> ()
|
| None -> ()
|
||||||
yield "</div>"
|
yield "</div>"
|
||||||
}
|
}
|
||||||
|> Seq.reduce (fun acc x -> acc + x)
|
|> Seq.reduce (+)
|
||||||
|
|
||||||
|
|
||||||
/// Helpers to format local date/time using NodaTime
|
/// Helpers to format local date/time using NodaTime
|
||||||
@ -94,58 +92,58 @@ type MyWebLogModel(ctx : NancyContext, webLog : WebLog) =
|
|||||||
|
|
||||||
/// Get the messages from the session
|
/// Get the messages from the session
|
||||||
let getMessages () =
|
let getMessages () =
|
||||||
let msg = ctx.Request.PersistableSession.GetOrDefault<UserMessage list>(Keys.Messages, List.empty)
|
let msg = ctx.Request.PersistableSession.GetOrDefault<UserMessage list>(Keys.Messages, [])
|
||||||
match List.length msg with
|
match List.length msg with
|
||||||
| 0 -> ()
|
| 0 -> ()
|
||||||
| _ -> ctx.Request.Session.Delete Keys.Messages
|
| _ -> ctx.Request.Session.Delete Keys.Messages
|
||||||
msg
|
msg
|
||||||
|
|
||||||
/// The web log for this request
|
/// The web log for this request
|
||||||
member this.webLog = webLog
|
member this.WebLog = webLog
|
||||||
/// The subtitle for the webLog (SSVE can't do IsSome that deep)
|
/// The subtitle for the webLog (SSVE can't do IsSome that deep)
|
||||||
member this.webLogSubtitle = defaultArg this.webLog.subtitle ""
|
member this.WebLogSubtitle = defaultArg this.WebLog.Subtitle ""
|
||||||
/// User messages
|
/// User messages
|
||||||
member val messages = getMessages () with get, set
|
member val Messages = getMessages () with get, set
|
||||||
/// The currently logged in user
|
/// The currently logged in user
|
||||||
member this.user = ctx.Request.PersistableSession.GetOrDefault<User>(Keys.User, User.empty)
|
member this.User = ctx.Request.PersistableSession.GetOrDefault<User>(Keys.User, User.Empty)
|
||||||
/// The title of the page
|
/// The title of the page
|
||||||
member val pageTitle = "" with get, set
|
member val PageTitle = "" with get, set
|
||||||
/// The name and version of the application
|
/// The name and version of the application
|
||||||
member this.generator = sprintf "myWebLog %s" (ctx.Items.[Keys.Version].ToString ())
|
member this.Generator = sprintf "myWebLog %s" (ctx.Items.[Keys.Version].ToString ())
|
||||||
/// The request start time
|
/// The request start time
|
||||||
member this.requestStart = ctx.Items.[Keys.RequestStart] :?> int64
|
member this.RequestStart = ctx.Items.[Keys.RequestStart] :?> int64
|
||||||
/// Is a user authenticated for this request?
|
/// Is a user authenticated for this request?
|
||||||
member this.isAuthenticated = "" <> this.user.id
|
member this.IsAuthenticated = "" <> this.User.Id
|
||||||
/// Add a message to the output
|
/// Add a message to the output
|
||||||
member this.addMessage message = this.messages <- message :: this.messages
|
member this.AddMessage message = this.Messages <- message :: this.Messages
|
||||||
|
|
||||||
/// Display a long date
|
/// Display a long date
|
||||||
member this.displayLongDate ticks = FormatDateTime.longDate this.webLog.timeZone ticks
|
member this.DisplayLongDate ticks = FormatDateTime.longDate this.WebLog.TimeZone ticks
|
||||||
/// Display a short date
|
/// Display a short date
|
||||||
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
|
/// The page title with the web log name appended
|
||||||
member this.displayPageTitle =
|
member this.DisplayPageTitle =
|
||||||
match this.pageTitle with
|
match this.PageTitle with
|
||||||
| "" -> match this.webLog.subtitle with
|
| "" -> match this.WebLog.Subtitle with
|
||||||
| Some st -> sprintf "%s | %s" this.webLog.name st
|
| Some st -> sprintf "%s | %s" this.WebLog.Name st
|
||||||
| None -> this.webLog.name
|
| None -> this.WebLog.Name
|
||||||
| pt -> sprintf "%s | %s" pt this.webLog.name
|
| pt -> sprintf "%s | %s" pt this.WebLog.Name
|
||||||
|
|
||||||
/// An image with the version and load time in the tool tip
|
/// An image with the version and load time in the tool tip
|
||||||
member this.footerLogo =
|
member this.FooterLogo =
|
||||||
seq {
|
seq {
|
||||||
yield "<img src=\"/default/footer-logo.png\" alt=\"myWebLog\" title=\""
|
yield "<img src=\"/default/footer-logo.png\" alt=\"myWebLog\" title=\""
|
||||||
yield sprintf "%s %s • " Resources.PoweredBy this.generator
|
yield sprintf "%s %s • " Resources.PoweredBy this.Generator
|
||||||
yield Resources.LoadedIn
|
yield Resources.LoadedIn
|
||||||
yield " "
|
yield " "
|
||||||
yield TimeSpan(System.DateTime.Now.Ticks - this.requestStart).TotalSeconds.ToString "f3"
|
yield TimeSpan(System.DateTime.Now.Ticks - this.RequestStart).TotalSeconds.ToString "f3"
|
||||||
yield " "
|
yield " "
|
||||||
yield Resources.Seconds.ToLower ()
|
yield Resources.Seconds.ToLower ()
|
||||||
yield "\" />"
|
yield "\" />"
|
||||||
}
|
}
|
||||||
|> Seq.reduce (fun acc x -> acc + x)
|
|> Seq.reduce (+)
|
||||||
|
|
||||||
|
|
||||||
// ---- Admin models ----
|
// ---- Admin models ----
|
||||||
@ -154,68 +152,67 @@ type MyWebLogModel(ctx : NancyContext, webLog : WebLog) =
|
|||||||
type DashboardModel(ctx, webLog, counts : DashboardCounts) =
|
type DashboardModel(ctx, webLog, counts : DashboardCounts) =
|
||||||
inherit MyWebLogModel(ctx, webLog)
|
inherit MyWebLogModel(ctx, webLog)
|
||||||
/// The number of posts for the current web log
|
/// The number of posts for the current web log
|
||||||
member val posts = counts.posts with get, set
|
member val Posts = counts.Posts with get, set
|
||||||
/// The number of pages for the current web log
|
/// The number of pages for the current web log
|
||||||
member val pages = counts.pages with get, set
|
member val Pages = counts.Pages with get, set
|
||||||
/// The number of categories for the current web log
|
/// The number of categories for the current web log
|
||||||
member val categories = counts.categories with get, set
|
member val Categories = counts.Categories with get, set
|
||||||
|
|
||||||
|
|
||||||
// ---- Category models ----
|
// ---- Category models ----
|
||||||
|
|
||||||
type IndentedCategory = {
|
type IndentedCategory =
|
||||||
category : Category
|
{ Category : Category
|
||||||
indent : int
|
Indent : int
|
||||||
selected : bool
|
Selected : bool }
|
||||||
}
|
|
||||||
with
|
with
|
||||||
/// Create an indented category
|
/// Create an indented category
|
||||||
static member create (cat : Category * int) (isSelected : string -> bool) =
|
static member Create (cat : Category * int) (isSelected : string -> bool) =
|
||||||
{ category = fst cat
|
{ Category = fst cat
|
||||||
indent = snd cat
|
Indent = snd cat
|
||||||
selected = isSelected (fst cat).id }
|
Selected = isSelected (fst cat).Id }
|
||||||
/// Display name for a category on the list page, complete with indents
|
/// Display name for a category on the list page, complete with indents
|
||||||
member this.listName = sprintf "%s%s" (String.replicate this.indent " » ") this.category.name
|
member this.ListName = sprintf "%s%s" (String.replicate this.Indent " » ") this.Category.Name
|
||||||
/// Display for this category as an option within a select box
|
/// Display for this category as an option within a select box
|
||||||
member this.option =
|
member this.Option =
|
||||||
seq {
|
seq {
|
||||||
yield sprintf "<option value=\"%s\"" this.category.id
|
yield sprintf "<option value=\"%s\"" this.Category.Id
|
||||||
yield (match this.selected with | true -> """ selected="selected">""" | _ -> ">")
|
yield (match this.Selected with | true -> """ selected="selected">""" | _ -> ">")
|
||||||
yield String.replicate this.indent " "
|
yield String.replicate this.Indent " "
|
||||||
yield this.category.name
|
yield this.Category.Name
|
||||||
yield "</option>"
|
yield "</option>"
|
||||||
}
|
}
|
||||||
|> String.concat ""
|
|> String.concat ""
|
||||||
/// Does the category have a description?
|
/// Does the category have a description?
|
||||||
member this.hasDescription = this.category.description.IsSome
|
member this.HasDescription = this.Category.Description.IsSome
|
||||||
|
|
||||||
|
|
||||||
/// Model for the list of categories
|
/// Model for the list of categories
|
||||||
type CategoryListModel(ctx, webLog, categories) =
|
type CategoryListModel(ctx, webLog, categories) =
|
||||||
inherit MyWebLogModel(ctx, webLog)
|
inherit MyWebLogModel(ctx, webLog)
|
||||||
/// The categories
|
/// The categories
|
||||||
member this.categories : IndentedCategory list = categories
|
member this.Categories : IndentedCategory list = categories
|
||||||
|
|
||||||
|
|
||||||
/// Form for editing a category
|
/// Form for editing a category
|
||||||
type CategoryForm(category : Category) =
|
type CategoryForm(category : Category) =
|
||||||
new() = CategoryForm(Category.empty)
|
new() = CategoryForm(Category.empty)
|
||||||
/// The name of the category
|
/// The name of the category
|
||||||
member val name = category.name with get, set
|
member val Name = category.Name with get, set
|
||||||
/// The slug of the category (used in category URLs)
|
/// The slug of the category (used in category URLs)
|
||||||
member val slug = category.slug with get, set
|
member val Slug = category.Slug with get, set
|
||||||
/// The description of the category
|
/// The description of the category
|
||||||
member val description = defaultArg category.description "" with get, set
|
member val Description = defaultArg category.Description "" with get, set
|
||||||
/// The parent category for this one
|
/// The parent category for this one
|
||||||
member val parentId = defaultArg category.parentId "" with get, set
|
member val ParentId = defaultArg category.ParentId "" with get, set
|
||||||
|
|
||||||
/// Model for editing a category
|
/// Model for editing a category
|
||||||
type CategoryEditModel(ctx, webLog, category) =
|
type CategoryEditModel(ctx, webLog, category) =
|
||||||
inherit MyWebLogModel(ctx, webLog)
|
inherit MyWebLogModel(ctx, webLog)
|
||||||
/// The form with the category information
|
/// The form with the category information
|
||||||
member val form = CategoryForm(category) with get, set
|
member val Form = CategoryForm(category) with get, set
|
||||||
/// The categories
|
/// The categories
|
||||||
member val categories : IndentedCategory list = List.empty with get, set
|
member val Categories : IndentedCategory list = [] with get, set
|
||||||
|
|
||||||
|
|
||||||
// ---- Page models ----
|
// ---- Page models ----
|
||||||
@ -223,54 +220,53 @@ type CategoryEditModel(ctx, webLog, category) =
|
|||||||
/// Model for page display
|
/// Model for page display
|
||||||
type PageModel(ctx, webLog, page) =
|
type PageModel(ctx, webLog, page) =
|
||||||
inherit MyWebLogModel(ctx, webLog)
|
inherit MyWebLogModel(ctx, webLog)
|
||||||
|
|
||||||
/// The page to be displayed
|
/// The page to be displayed
|
||||||
member this.page : Page = page
|
member this.Page : Page = page
|
||||||
|
|
||||||
|
|
||||||
/// Wrapper for a page with additional properties
|
/// Wrapper for a page with additional properties
|
||||||
type PageForDisplay(webLog, page) =
|
type PageForDisplay(webLog, page) =
|
||||||
/// The page
|
/// The page
|
||||||
member this.page : Page = page
|
member this.Page : Page = page
|
||||||
/// The time zone of the web log
|
/// The time zone of the web log
|
||||||
member this.timeZone = webLog.timeZone
|
member this.TimeZone = webLog.TimeZone
|
||||||
/// The date the page was last updated
|
/// The date the page was last updated
|
||||||
member this.updatedDate = FormatDateTime.longDate this.timeZone page.updatedOn
|
member this.UpdatedDate = FormatDateTime.longDate this.TimeZone page.UpdatedOn
|
||||||
/// The time the page was last updated
|
/// The time the page was last updated
|
||||||
member this.updatedTime = FormatDateTime.time this.timeZone page.updatedOn
|
member this.UpdatedTime = FormatDateTime.time this.TimeZone page.UpdatedOn
|
||||||
|
|
||||||
|
|
||||||
/// Model for page list display
|
/// Model for page list display
|
||||||
type PagesModel(ctx, webLog, pages) =
|
type PagesModel(ctx, webLog, pages) =
|
||||||
inherit MyWebLogModel(ctx, webLog)
|
inherit MyWebLogModel(ctx, webLog)
|
||||||
/// The pages
|
/// The pages
|
||||||
member this.pages : PageForDisplay list = pages
|
member this.Pages : PageForDisplay list = pages
|
||||||
|
|
||||||
|
|
||||||
/// Form used to edit a page
|
/// Form used to edit a page
|
||||||
type EditPageForm() =
|
type EditPageForm() =
|
||||||
/// The title of the page
|
/// The title of the page
|
||||||
member val title = "" with get, set
|
member val Title = "" with get, set
|
||||||
/// The link for the page
|
/// The link for the page
|
||||||
member val permalink = "" with get, set
|
member val Permalink = "" with get, set
|
||||||
/// The source type of the revision
|
/// The source type of the revision
|
||||||
member val source = "" with get, set
|
member val Source = "" with get, set
|
||||||
/// The text of the revision
|
/// The text of the revision
|
||||||
member val text = "" with get, set
|
member val Text = "" with get, set
|
||||||
/// Whether to show the page in the web log's page list
|
/// Whether to show the page in the web log's page list
|
||||||
member val showInPageList = false with get, set
|
member val ShowInPageList = false with get, set
|
||||||
|
|
||||||
/// Fill the form with applicable values from a page
|
/// Fill the form with applicable values from a page
|
||||||
member this.forPage (page : Page) =
|
member this.ForPage (page : Page) =
|
||||||
this.title <- page.title
|
this.Title <- page.Title
|
||||||
this.permalink <- page.permalink
|
this.Permalink <- page.Permalink
|
||||||
this.showInPageList <- page.showInPageList
|
this.ShowInPageList <- page.ShowInPageList
|
||||||
this
|
this
|
||||||
|
|
||||||
/// Fill the form with applicable values from a revision
|
/// Fill the form with applicable values from a revision
|
||||||
member this.forRevision rev =
|
member this.ForRevision rev =
|
||||||
this.source <- rev.sourceType
|
this.Source <- rev.SourceType
|
||||||
this.text <- rev.text
|
this.Text <- rev.Text
|
||||||
this
|
this
|
||||||
|
|
||||||
|
|
||||||
@ -278,21 +274,21 @@ type EditPageForm() =
|
|||||||
type EditPageModel(ctx, webLog, page, revision) =
|
type EditPageModel(ctx, webLog, page, revision) =
|
||||||
inherit MyWebLogModel(ctx, webLog)
|
inherit MyWebLogModel(ctx, webLog)
|
||||||
/// The page edit form
|
/// The page edit form
|
||||||
member val form = EditPageForm().forPage(page).forRevision(revision)
|
member val Form = EditPageForm().ForPage(page).ForRevision(revision)
|
||||||
/// The page itself
|
/// The page itself
|
||||||
member this.page = page
|
member this.Page = page
|
||||||
/// The page's published date
|
/// The page's published date
|
||||||
member this.publishedDate = this.displayLongDate page.publishedOn
|
member this.PublishedDate = this.DisplayLongDate page.PublishedOn
|
||||||
/// The page's published time
|
/// The page's published time
|
||||||
member this.publishedTime = this.displayTime page.publishedOn
|
member this.PublishedTime = this.DisplayTime page.PublishedOn
|
||||||
/// The page's last updated date
|
/// The page's last updated date
|
||||||
member this.lastUpdatedDate = this.displayLongDate page.updatedOn
|
member this.LastUpdatedDate = this.DisplayLongDate page.UpdatedOn
|
||||||
/// The page's last updated time
|
/// The page's last updated time
|
||||||
member this.lastUpdatedTime = this.displayTime page.updatedOn
|
member this.LastUpdatedTime = this.DisplayTime page.UpdatedOn
|
||||||
/// Is this a new page?
|
/// Is this a new page?
|
||||||
member this.isNew = "new" = page.id
|
member this.IsNew = "new" = page.Id
|
||||||
/// Generate a checked attribute if this page shows in the page list
|
/// Generate a checked attribute if this page shows in the page list
|
||||||
member this.pageListChecked = match page.showInPageList with | true -> "checked=\"checked\"" | _ -> ""
|
member this.PageListChecked = match page.ShowInPageList with true -> "checked=\"checked\"" | _ -> ""
|
||||||
|
|
||||||
|
|
||||||
// ---- Post models ----
|
// ---- Post models ----
|
||||||
@ -301,44 +297,45 @@ type EditPageModel(ctx, webLog, page, revision) =
|
|||||||
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
|
||||||
member this.post : Post = post
|
member this.Post : Post = post
|
||||||
/// The next newer post
|
/// The next newer post
|
||||||
member val newerPost = Option<Post>.None with get, set
|
member val NewerPost = Option<Post>.None with get, set
|
||||||
/// The next older post
|
/// The next older post
|
||||||
member val olderPost = Option<Post>.None with get, set
|
member val OlderPost = Option<Post>.None with get, set
|
||||||
/// The date the post was published
|
/// The date the post was published
|
||||||
member this.publishedDate = this.displayLongDate this.post.publishedOn
|
member this.PublishedDate = this.DisplayLongDate this.Post.PublishedOn
|
||||||
/// The time the post was published
|
/// The time the post was published
|
||||||
member this.publishedTime = this.displayTime this.post.publishedOn
|
member this.PublishedTime = this.DisplayTime this.Post.PublishedOn
|
||||||
/// Does the post have tags?
|
/// Does the post have tags?
|
||||||
member this.hasTags = List.length post.tags > 0
|
member this.HasTags = not (List.isEmpty post.Tags)
|
||||||
/// Get the tags sorted
|
/// Get the tags sorted
|
||||||
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?
|
/// Does this post have a newer post?
|
||||||
member this.hasNewer = this.newerPost.IsSome
|
member this.HasNewer = this.NewerPost.IsSome
|
||||||
/// Does this post have an older post?
|
/// Does this post have an older post?
|
||||||
member this.hasOlder = this.olderPost.IsSome
|
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) =
|
||||||
/// Turn tags into a pipe-delimited string of tags
|
/// Turn tags into a pipe-delimited string of tags
|
||||||
let pipedTags tags = tags |> List.reduce (fun acc x -> sprintf "%s | %s" acc x)
|
let pipedTags tags = tags |> List.reduce (fun acc x -> sprintf "%s | %s" acc x)
|
||||||
/// The actual post
|
/// The actual post
|
||||||
member this.post = post
|
member this.Post = post
|
||||||
/// The time zone for the web log to which this post belongs
|
/// The time zone for the web log to which this post belongs
|
||||||
member this.timeZone = webLog.timeZone
|
member this.TimeZone = webLog.TimeZone
|
||||||
/// The date the post was published
|
/// The date the post was published
|
||||||
member this.publishedDate = FormatDateTime.longDate this.timeZone this.post.publishedOn
|
member this.PublishedDate = FormatDateTime.longDate this.TimeZone this.Post.PublishedOn
|
||||||
/// The time the post was published
|
/// The time the post was published
|
||||||
member this.publishedTime = FormatDateTime.time this.timeZone this.post.publishedOn
|
member this.PublishedTime = FormatDateTime.time this.TimeZone this.Post.PublishedOn
|
||||||
/// Tags
|
/// Tags
|
||||||
member this.tags =
|
member this.Tags =
|
||||||
match List.length this.post.tags with
|
match List.length this.Post.Tags with
|
||||||
| 0 -> ""
|
| 0 -> ""
|
||||||
| 1 | 2 | 3 | 4 | 5 -> this.post.tags |> pipedTags
|
| 1 | 2 | 3 | 4 | 5 -> this.Post.Tags |> pipedTags
|
||||||
| count -> sprintf "%s %s" (this.post.tags |> List.take 3 |> pipedTags)
|
| count -> sprintf "%s %s" (this.Post.Tags |> List.take 3 |> pipedTags)
|
||||||
(System.String.Format(Resources.andXMore, count - 3))
|
(System.String.Format(Resources.andXMore, count - 3))
|
||||||
|
|
||||||
|
|
||||||
@ -346,57 +343,57 @@ type PostForDisplay(webLog : WebLog, post : Post) =
|
|||||||
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 : PostForDisplay list = [] 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
|
||||||
|
|
||||||
/// The link for the next newer page of posts
|
/// The link for the next newer page of posts
|
||||||
member this.newerLink =
|
member this.NewerLink =
|
||||||
match this.urlPrefix = "/posts" && this.pageNbr = 2 && this.webLog.defaultPage = "posts" with
|
match this.UrlPrefix = "/posts" && this.PageNbr = 2 && this.WebLog.DefaultPage = "posts" with
|
||||||
| true -> "/"
|
| true -> "/"
|
||||||
| _ -> sprintf "%s/page/%i" this.urlPrefix (this.pageNbr - 1)
|
| _ -> sprintf "%s/page/%i" this.UrlPrefix (this.PageNbr - 1)
|
||||||
|
|
||||||
/// The link for the prior (older) page of posts
|
/// The link for the prior (older) page of posts
|
||||||
member this.olderLink = sprintf "%s/page/%i" this.urlPrefix (this.pageNbr + 1)
|
member this.OlderLink = sprintf "%s/page/%i" this.UrlPrefix (this.PageNbr + 1)
|
||||||
|
|
||||||
|
|
||||||
/// Form for editing a post
|
/// Form for editing a post
|
||||||
type EditPostForm() =
|
type EditPostForm() =
|
||||||
/// The title of the post
|
/// The title of the post
|
||||||
member val title = "" with get, set
|
member val Title = "" with get, set
|
||||||
/// The permalink for the post
|
/// The permalink for the post
|
||||||
member val permalink = "" with get, set
|
member val Permalink = "" with get, set
|
||||||
/// The source type for this revision
|
/// The source type for this revision
|
||||||
member val source = "" with get, set
|
member val Source = "" with get, set
|
||||||
/// The text
|
/// The text
|
||||||
member val text = "" with get, set
|
member val Text = "" with get, set
|
||||||
/// Tags for the post
|
/// Tags for the post
|
||||||
member val tags = "" with get, set
|
member val Tags = "" with get, set
|
||||||
/// The selected category Ids for the post
|
/// The selected category Ids for the post
|
||||||
member val categories = Array.empty<string> with get, set
|
member val Categories : string[] = [||] with get, set
|
||||||
/// Whether the post should be published
|
/// Whether the post should be published
|
||||||
member val publishNow = true with get, set
|
member val PublishNow = true with get, set
|
||||||
|
|
||||||
/// Fill the form with applicable values from a post
|
/// Fill the form with applicable values from a post
|
||||||
member this.forPost post =
|
member this.ForPost post =
|
||||||
this.title <- post.title
|
this.Title <- post.Title
|
||||||
this.permalink <- post.permalink
|
this.Permalink <- post.Permalink
|
||||||
this.tags <- List.reduce (fun acc x -> sprintf "%s, %s" acc x) post.tags
|
this.Tags <- List.reduce (fun acc x -> sprintf "%s, %s" acc x) post.Tags
|
||||||
this.categories <- List.toArray post.categoryIds
|
this.Categories <- List.toArray post.CategoryIds
|
||||||
this
|
this
|
||||||
|
|
||||||
/// Fill the form with applicable values from a revision
|
/// Fill the form with applicable values from a revision
|
||||||
member this.forRevision rev =
|
member this.ForRevision rev =
|
||||||
this.source <- rev.sourceType
|
this.Source <- rev.SourceType
|
||||||
this.text <- rev.text
|
this.Text <- rev.Text
|
||||||
this
|
this
|
||||||
|
|
||||||
/// View model for the edit post page
|
/// View model for the edit post page
|
||||||
@ -404,17 +401,17 @@ type EditPostModel(ctx, webLog, post, revision) =
|
|||||||
inherit MyWebLogModel(ctx, webLog)
|
inherit MyWebLogModel(ctx, webLog)
|
||||||
|
|
||||||
/// The form
|
/// The form
|
||||||
member val form = EditPostForm().forPost(post).forRevision(revision) with get, set
|
member val Form = EditPostForm().ForPost(post).ForRevision(revision) with get, set
|
||||||
/// The post being edited
|
/// The post being edited
|
||||||
member val post = post with get, set
|
member val Post = post with get, set
|
||||||
/// The categories to which the post may be assigned
|
/// The categories to which the post may be assigned
|
||||||
member val categories = List.empty<string * string> with get, set
|
member val Categories : (string * string) list = [] with get, set
|
||||||
/// Whether the post is currently published
|
/// Whether the post is currently published
|
||||||
member this.isPublished = PostStatus.Published = this.post.status
|
member this.IsPublished = PostStatus.Published = this.Post.Status
|
||||||
/// The published date
|
/// The published date
|
||||||
member this.publishedDate = this.displayLongDate this.post.publishedOn
|
member this.PublishedDate = this.DisplayLongDate this.Post.PublishedOn
|
||||||
/// The published time
|
/// The published time
|
||||||
member this.publishedTime = this.displayTime this.post.publishedOn
|
member this.PublishedTime = this.DisplayTime this.Post.PublishedOn
|
||||||
|
|
||||||
|
|
||||||
// ---- User models ----
|
// ---- User models ----
|
||||||
@ -422,15 +419,15 @@ type EditPostModel(ctx, webLog, post, revision) =
|
|||||||
/// Form for the log on page
|
/// Form for the log on page
|
||||||
type LogOnForm() =
|
type LogOnForm() =
|
||||||
/// The URL to which the user will be directed upon successful log on
|
/// The URL to which the user will be directed upon successful log on
|
||||||
member val returnUrl = "" with get, set
|
member val ReturnUrl = "" with get, set
|
||||||
/// The e-mail address
|
/// The e-mail address
|
||||||
member val email = "" with get, set
|
member val Email = "" with get, set
|
||||||
/// The user's passwor
|
/// The user's passwor
|
||||||
member val password = "" with get, set
|
member val Password = "" with get, set
|
||||||
|
|
||||||
|
|
||||||
/// Model to support the user log on page
|
/// Model to support the user log on page
|
||||||
type LogOnModel(ctx, webLog) =
|
type LogOnModel(ctx, webLog) =
|
||||||
inherit MyWebLogModel(ctx, webLog)
|
inherit MyWebLogModel(ctx, webLog)
|
||||||
/// The log on form
|
/// The log on form
|
||||||
member val form = LogOnForm() with get, set
|
member val Form = LogOnForm() with get, set
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<ProjectGuid>e6ee110a-27a6-4a19-b0cb-d24f48f71b53</ProjectGuid>
|
<ProjectGuid>e6ee110a-27a6-4a19-b0cb-d24f48f71b53</ProjectGuid>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<RootNamespace>myWebLog.Web</RootNamespace>
|
<RootNamespace>myWebLog.Web</RootNamespace>
|
||||||
<AssemblyName>myWebLog.Web</AssemblyName>
|
<AssemblyName>MyWebLog.Web</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
|
<TargetFSharpCoreVersion>4.4.0.0</TargetFSharpCoreVersion>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
namespace myWebLog
|
namespace MyWebLog
|
||||||
{
|
{
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
static void Main(string[] args)
|
static void Main(string[] args)
|
||||||
{
|
{
|
||||||
App.run();
|
App.Run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
[assembly: AssemblyTitle("myWebLog")]
|
[assembly: AssemblyTitle("MyWebLog")]
|
||||||
[assembly: AssemblyDescription("A lightweight blogging platform built on Suave, Nancy, and RethinkDB")]
|
[assembly: AssemblyDescription("A lightweight blogging platform built on Suave, Nancy, and RethinkDB")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("")]
|
[assembly: AssemblyCompany("")]
|
||||||
[assembly: AssemblyProduct("myWebLog")]
|
[assembly: AssemblyProduct("MyWebLog")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
<ProjectGuid>{B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}</ProjectGuid>
|
<ProjectGuid>{B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB}</ProjectGuid>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
<RootNamespace>myWebLog</RootNamespace>
|
<RootNamespace>MyWebLog</RootNamespace>
|
||||||
<AssemblyName>myWebLog</AssemblyName>
|
<AssemblyName>MyWebLog</AssemblyName>
|
||||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||||
<FileAlignment>512</FileAlignment>
|
<FileAlignment>512</FileAlignment>
|
||||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||||
@ -51,7 +51,6 @@
|
|||||||
<Content Include="data-config.json">
|
<Content Include="data-config.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="views\admin\message.html" />
|
|
||||||
<Content Include="views\themes\default\content\bootstrap-theme.css.map">
|
<Content Include="views\themes\default\content\bootstrap-theme.css.map">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<title>@Model.pageTitle | @Translate.Admin | @Model.webLog.name</title>
|
<title>@Model.PageTitle | @Translate.Admin | @Model.WebLog.Name</title>
|
||||||
<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.css" />
|
<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.css" />
|
||||||
<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.4/cosmo/bootstrap.min.css" />
|
<link rel="stylesheet" type="text/css" href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.4/cosmo/bootstrap.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" />
|
||||||
@ -14,17 +14,17 @@
|
|||||||
<nav class="navbar navbar-inverse">
|
<nav class="navbar navbar-inverse">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<a class="navbar-brand" href="/">@Model.webLog.name</a>
|
<a class="navbar-brand" href="/">@Model.WebLog.Name</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-left">
|
<div class="navbar-left">
|
||||||
<p class="navbar-text">@Model.pageTitle</p>
|
<p class="navbar-text">@Model.PageTitle</p>
|
||||||
</div>
|
</div>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
@If.isAuthenticated
|
@If.IsAuthenticated
|
||||||
<li><a href="/admin">@Translate.Dashboard</a></li>
|
<li><a href="/admin">@Translate.Dashboard</a></li>
|
||||||
<li><a href="/user/logoff">@Translate.LogOff</a></li>
|
<li><a href="/user/logoff">@Translate.LogOff</a></li>
|
||||||
@EndIf
|
@EndIf
|
||||||
@IfNot.isAuthenticated
|
@IfNot.IsAuthenticated
|
||||||
<li><a href="/user/logon">@Translate.LogOn</a></li>
|
<li><a href="/user/logon">@Translate.LogOn</a></li>
|
||||||
@EndIf
|
@EndIf
|
||||||
</ul>
|
</ul>
|
||||||
@ -32,15 +32,15 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@Each.messages
|
@Each.Messages
|
||||||
@Current.toDisplay
|
@Current.ToDisplay
|
||||||
@EndEach
|
@EndEach
|
||||||
@Section['Content'];
|
@Section['Content'];
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 text-right">@Model.generator</div>
|
<div class="col-xs-12 text-right">@Model.Generator</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
@Master['admin/admin-layout']
|
@Master['admin/admin-layout']
|
||||||
|
|
||||||
@Section['Content']
|
@Section['Content']
|
||||||
<form action="/category/@Model.category.id/edit" method="post">
|
<form action="/category/@Model.Category.Id/edit" method="post">
|
||||||
@AntiForgeryToken
|
@AntiForgeryToken
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="name">@Translate.Name</label>
|
<label class="control-label" for="Name">@Translate.Name</label>
|
||||||
<input type="text" class="form-control" id="name" name="name" value="@Model.form.name" />
|
<input type="text" class="form-control" id="Name" name="Name" value="@Model.Form.Name" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-8">
|
<div class="col-xs-8">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="slug">@Translate.Slug</label>
|
<label class="control-label" for="Slug">@Translate.Slug</label>
|
||||||
<input type="text" class="form-control" id="slug" name="slug" value="@Model.form.slug}" />
|
<input type="text" class="form-control" id="Slug" name="Slug" value="@Model.Form.Slug" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="description">@Translate.Description</label>
|
<label class="control-label" for="Description">@Translate.Description</label>
|
||||||
<textarea class="form-control" rows="4" id="description" name="description">@Model.form.description</textarea>
|
<textarea class="form-control" rows="4" id="Description" name="Description">@Model.Form.Description</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-4">
|
<div class="col-xs-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="parentId">@Translate.ParentCategory</label>
|
<label class="control-label" for="ParentId">@Translate.ParentCategory</label>
|
||||||
<select class="form-control" id="parentId" name="parentId">
|
<select class="form-control" id="ParentId" name="ParentId">
|
||||||
<option value="">— @Translate.NoParent —</option>
|
<option value="">— @Translate.NoParent —</option>
|
||||||
@Each.categories
|
@Each.Categories
|
||||||
@Current.option
|
@Current.Option
|
||||||
@EndEach
|
@EndEach
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@ -46,7 +46,7 @@
|
|||||||
@Section['Scripts']
|
@Section['Scripts']
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
/* <![CDATA[ */
|
/* <![CDATA[ */
|
||||||
$(document).ready(function () { $("#name").focus() })
|
$(document).ready(function () { $("#Name").focus() })
|
||||||
/* ]] */
|
/* ]] */
|
||||||
</script>
|
</script>
|
||||||
@EndSection
|
@EndSection
|
||||||
|
@ -11,20 +11,20 @@
|
|||||||
<th>@Translate.Category</th>
|
<th>@Translate.Category</th>
|
||||||
<th>@Translate.Description</th>
|
<th>@Translate.Description</th>
|
||||||
</tr>
|
</tr>
|
||||||
@Each.categories
|
@Each.Categories
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="/category/@Current.category.id/edit">@Translate.Edit</a>
|
<a href="/category/@Current.Category.Id/edit">@Translate.Edit</a>
|
||||||
<a href="javascript:void(0)" onclick="deleteCategory('@Current.category.id', '@Current.category.name')">
|
<a href="javascript:void(0)" onclick="deleteCategory('@Current.Category.Id', '@Current.Category.Name')">
|
||||||
@Translate.Delete
|
@Translate.Delete
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td>@Current.listName</td>
|
<td>@Current.ListName</td>
|
||||||
<td>
|
<td>
|
||||||
@If.hasDescription
|
@If.HasDescription
|
||||||
@Current.category.description.Value
|
@Current.Category.Description.Value
|
||||||
@EndIf
|
@EndIf
|
||||||
@IfNot.hasDescription
|
@IfNot.HasDescription
|
||||||
|
|
||||||
@EndIf
|
@EndIf
|
||||||
</td>
|
</td>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
@Section['Content']
|
@Section['Content']
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<h3>@Translate.Posts <span class="badge">@Model.posts</span></h3>
|
<h3>@Translate.Posts <span class="badge">@Model.Posts</span></h3>
|
||||||
<p>
|
<p>
|
||||||
<a href="/posts/list"><i class="fa fa-list-ul"></i> @Translate.ListAll</a>
|
<a href="/posts/list"><i class="fa fa-list-ul"></i> @Translate.ListAll</a>
|
||||||
|
|
||||||
@ -11,7 +11,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<h3>@Translate.Pages <span class="badge">@Model.pages</span></h3>
|
<h3>@Translate.Pages <span class="badge">@Model.Pages</span></h3>
|
||||||
<p>
|
<p>
|
||||||
<a href="/pages"><i class="fa fa-list-ul"></i> @Translate.ListAll</a>
|
<a href="/pages"><i class="fa fa-list-ul"></i> @Translate.ListAll</a>
|
||||||
|
|
||||||
@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
<h3>@Translate.Categories <span class="badge">@Model.categories</span></h3>
|
<h3>@Translate.Categories <span class="badge">@Model.Categories</span></h3>
|
||||||
<p>
|
<p>
|
||||||
<a href="/categories"><i class="fa fa-list-ul"></i> @Translate.ListAll</a>
|
<a href="/categories"><i class="fa fa-list-ul"></i> @Translate.ListAll</a>
|
||||||
|
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
if session && 0 < (session.messages || []).length
|
|
||||||
while 0 < session.messages.length
|
|
||||||
- var message = session.messages.shift()
|
|
||||||
<div class="alert alert-dismissable alert-@Model.level" role="alert">
|
|
||||||
<button type="button" class="close" data-dismiss="alert" aria-label="@Translate.Close">×</button>
|
|
||||||
<strong>
|
|
||||||
if 'danger' == message.type
|
|
||||||
=__("Error").toUpperCase()
|
|
||||||
| »
|
|
||||||
else if 'warning' == message.type
|
|
||||||
=__("Warning").toUpperCase()
|
|
||||||
| »
|
|
||||||
!= message.text
|
|
||||||
</strong>
|
|
||||||
if message.detail
|
|
||||||
br
|
|
||||||
!= message.detail
|
|
||||||
</div>
|
|
@ -1,22 +1,22 @@
|
|||||||
@Master['admin/admin-layout']
|
@Master['admin/admin-layout']
|
||||||
|
|
||||||
@Section['Content']
|
@Section['Content']
|
||||||
<form action="/page/@Model.page.id/edit" method="post">
|
<form action="/page/@Model.Page.Id/edit" method="post">
|
||||||
@AntiForgeryToken
|
@AntiForgeryToken
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="title">@Translate.Title</label>
|
<label class="control-label" for="Title">@Translate.Title</label>
|
||||||
<input type="text" name="title" id="title" class="form-control" value="@Model.form.title" />
|
<input type="text" name="Title" id="Title" class="form-control" value="@Model.Form.Title" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="permalink">@Translate.Permalink</label>
|
<label class="control-label" for="Permalink">@Translate.Permalink</label>
|
||||||
<input type="text" name="permalink" id="permalink" class="form-control" value="@Model.form.permalink" />
|
<input type="text" name="Permalink" id="Permalink" class="form-control" value="@Model.Form.Permalink" />
|
||||||
<p class="form-hint"><em>@Translate.startingWith</em> http://@Model.webLog.urlBase/ </p>
|
<p class="form-hint"><em>@Translate.startingWith</em> http://@Model.webLog.urlBase/ </p>
|
||||||
</div>
|
</div>
|
||||||
<!-- // TODO: Markdown / HTML choice -->
|
<!-- // TODO: Markdown / HTML choice -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<textarea name="text" id="text" rows="15" class="form-control">@Model.form.text</textarea>
|
<textarea name="Text" id="Text" rows="15" class="form-control">@Model.Form.Text</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
@ -26,16 +26,16 @@
|
|||||||
@IfNot.isNew
|
@IfNot.isNew
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label">@Translate.PublishedDate</label>
|
<label class="control-label">@Translate.PublishedDate</label>
|
||||||
<p class="static-control">@Model.publishedDate<br />@Model.publishedTime</p>
|
<p class="static-control">@Model.PublishedDate<br />@Model.PublishedTime</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label">@Translate.LastUpdatedDate</label>
|
<label class="control-label">@Translate.LastUpdatedDate</label>
|
||||||
<p class="static-control">@Model.lastUpdatedDate<br />@Model.lastUpdatedTime</p>
|
<p class="static-control">@Model.LastUpdatedDate<br />@Model.LastUpdatedTime</p>
|
||||||
</div>
|
</div>
|
||||||
@EndIf
|
@EndIf
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="checkbox" name="showInPageList" id="showInPageList" @Model.pageListChecked />
|
<input type="checkbox" name="ShowInPageList" id="ShowInPageList" @Model.PageListChecked />
|
||||||
<label for="showInPageList">@Translate.ShowInPageList</label>
|
<label for="ShowInPageList">@Translate.ShowInPageList</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -51,7 +51,7 @@
|
|||||||
<script type="text/javascript" src="/content/scripts/tinymce-init.js"></script>
|
<script type="text/javascript" src="/content/scripts/tinymce-init.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
/* <![CDATA[ */
|
/* <![CDATA[ */
|
||||||
$(document).ready(function () { $("#title").focus() })
|
$(document).ready(function () { $("#Title").focus() })
|
||||||
/* ]]> */
|
/* ]]> */
|
||||||
</script>
|
</script>
|
||||||
@EndSection
|
@EndSection
|
||||||
|
@ -10,15 +10,15 @@
|
|||||||
<th>@Translate.Title</th>
|
<th>@Translate.Title</th>
|
||||||
<th>@Translate.LastUpdated</th>
|
<th>@Translate.LastUpdated</th>
|
||||||
</tr>
|
</tr>
|
||||||
@Each.pages
|
@Each.Pages
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@Current.page.title<br />
|
@Current.Page.Title<br />
|
||||||
<a href="/@Current.page.permalink">@Translate.View</a>
|
<a href="/@Current.Page.Permalink">@Translate.View</a>
|
||||||
<a href="/page/@Current.page.id/edit">@Translate.Edit</a>
|
<a href="/page/@Current.Page.Id/edit">@Translate.Edit</a>
|
||||||
<a href="javascript:void(0)" onclick="deletePage('@Current.page.id', '@Current.title')">@Translate.Delete</a>
|
<a href="javascript:void(0)" onclick="deletePage('@Current.Page.Id', '@Current.Page.Title')">@Translate.Delete</a>
|
||||||
</td>
|
</td>
|
||||||
<td>@Current.updatedDate<br />@Translate.at @Current.updatedTime</td>
|
<td>@Current.UpdatedDate<br />@Translate.at @Current.UpdatedTime</td>
|
||||||
</tr>
|
</tr>
|
||||||
@EndEach
|
@EndEach
|
||||||
</table>
|
</table>
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
@Master['admin/admin-layout']
|
@Master['admin/admin-layout']
|
||||||
|
|
||||||
@Section['Content']
|
@Section['Content']
|
||||||
<form action='/post/@Model.post.id/edit' method="post">
|
<form action='/post/@Model.Post.Id/edit' method="post">
|
||||||
@AntiForgeryToken
|
@AntiForgeryToken
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="title">@Translate.Title</label>
|
<label class="control-label" for="Title">@Translate.Title</label>
|
||||||
<input type="text" name="title" id="title" class="form-control" value="@Model.form.title" />
|
<input type="text" name="Title" id="Title" class="form-control" value="@Model.Form.Title" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="permalink">@Translate.Permalink</label>
|
<label class="control-label" for="Permalink">@Translate.Permalink</label>
|
||||||
<input type="text" name="permalink" id="permalink" class="form-control" value="@Model.form.permalink" />
|
<input type="text" name="Permalink" id="Permalink" class="form-control" value="@Model.Form.Permalink" />
|
||||||
<!-- // FIXME: hard-coded "http" -->
|
<!-- // FIXME: hard-coded "http" -->
|
||||||
<p class="form-hint"><em>@Translate.startingWith</em> http://@Model.webLog.urlBase/ </p>
|
<p class="form-hint"><em>@Translate.startingWith</em> http://@Model.webLog.urlBase/ </p>
|
||||||
</div>
|
</div>
|
||||||
<!-- // TODO: Markdown / HTML choice -->
|
<!-- // TODO: Markdown / HTML choice -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<textarea name="text" id="text" rows="15">@Model.form.text</textarea>
|
<textarea name="Text" id="Text" rows="15">@Model.Form.Text</textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="tags">@Translate.Tags</label>
|
<label class="control-label" for="Tags">@Translate.Tags</label>
|
||||||
<input type="text" name="tags" id="tags" class="form-control" value="@Model.form.tags" />
|
<input type="text" name="Tags" id="Tags" class="form-control" value="@Model.Form.Tags" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
@ -32,12 +32,12 @@
|
|||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label">@Translate.PostStatus</label>
|
<label class="control-label">@Translate.PostStatus</label>
|
||||||
<p class="static-control">@Model.post.status</p>
|
<p class="static-control">@Model.Post.Status</p>
|
||||||
</div>
|
</div>
|
||||||
@If.isPublished
|
@If.IsPublished
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label">@Translate.PublishedDate</label>
|
<label class="control-label">@Translate.PublishedDate</label>
|
||||||
<p class="static-control">@Model.publishedDate<br />@Model.publishedTime</p>
|
<p class="static-control">@Model.PublishedDate<br />@Model.PublishedTime</p>
|
||||||
</div>
|
</div>
|
||||||
@EndIf
|
@EndIf
|
||||||
</div>
|
</div>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body" style="max-height:350px;overflow:scroll;">
|
<div class="panel-body" style="max-height:350px;overflow:scroll;">
|
||||||
<!-- // TODO: how to check the ones that are already selected? -->
|
<!-- // TODO: how to check the ones that are already selected? -->
|
||||||
@Each.categories
|
@Each.Categories
|
||||||
<!-- - var tab = 0
|
<!-- - var tab = 0
|
||||||
while tab < item.indent
|
while tab < item.indent
|
||||||
|
|
|
|
||||||
@ -56,22 +56,22 @@
|
|||||||
- var attributes = {}
|
- var attributes = {}
|
||||||
if -1 < currentCategories.indexOf(item.category.id)
|
if -1 < currentCategories.indexOf(item.category.id)
|
||||||
- attributes.checked = 'checked' -->
|
- attributes.checked = 'checked' -->
|
||||||
<input type="checkbox" id="category-@Current.Item1" name="category", value="@Current.Item1" />
|
<input type="checkbox" id="Category-@Current.Item1" name="Category", value="@Current.Item1" />
|
||||||
|
|
||||||
<!-- // FIXME: the title should be the category description -->
|
<!-- // FIXME: the title should be the category description -->
|
||||||
<label for="category-@Current.Item1" title="@Current.Item2">@Current.Item2</label>
|
<label for="Category-@Current.Item1" title="@Current.Item2">@Current.Item2</label>
|
||||||
<br/>
|
<br/>
|
||||||
@EndEach
|
@EndEach
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
@If.isPublished
|
@If.IsPublished
|
||||||
<input type="hidden" name="publishNow" value="true" />
|
<input type="hidden" name="PublishNow" value="true" />
|
||||||
@EndIf
|
@EndIf
|
||||||
@IfNot.isPublished
|
@IfNot.IsPublished
|
||||||
<div>
|
<div>
|
||||||
<input type="checkbox" name="publishNow" id="publishNow" value="true" checked="checked" />
|
<input type="checkbox" name="PublishNow" id="PublishNow" value="true" checked="checked" />
|
||||||
<label for="publishNow">@Translate.PublishThisPost</label>
|
<label for="PublishNow">@Translate.PublishThisPost</label>
|
||||||
</div>
|
</div>
|
||||||
@EndIf
|
@EndIf
|
||||||
<p>
|
<p>
|
||||||
@ -89,7 +89,7 @@
|
|||||||
<script type="text/javascript" src="/content/scripts/tinymce-init.js"></script>
|
<script type="text/javascript" src="/content/scripts/tinymce-init.js"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
/** <![CDATA[ */
|
/** <![CDATA[ */
|
||||||
$(document).ready(function () { $("#title").focus() })
|
$(document).ready(function () { $("#Title").focus() })
|
||||||
/** ]]> */
|
/** ]]> */
|
||||||
</script>
|
</script>
|
||||||
@EndSection
|
@EndSection
|
||||||
|
@ -16,33 +16,33 @@
|
|||||||
<th>@Translate.Status</th>
|
<th>@Translate.Status</th>
|
||||||
<th>@Translate.Tags</th>
|
<th>@Translate.Tags</th>
|
||||||
</tr>
|
</tr>
|
||||||
@Each.posts
|
@Each.Posts
|
||||||
<tr>
|
<tr>
|
||||||
<td style="white-space:nowrap;">
|
<td style="white-space:nowrap;">
|
||||||
@Current.publishedDate<br />
|
@Current.PublishedDate<br />
|
||||||
@Translate.at @Current.publishedTime
|
@Translate.at @Current.PublishedTime
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@Current.post.title<br />
|
@Current.Post.Title<br />
|
||||||
<a href="/@Current.post.permalink">@Translate.View</a> |
|
<a href="/@Current.Post.Permalink">@Translate.View</a> |
|
||||||
<a href="/post/@Current.post.id/edit">@Translate.Edit</a> |
|
<a href="/post/@Current.Post.Id/edit">@Translate.Edit</a> |
|
||||||
<a href="/post/@Current.post.id/delete">@Translate.Delete</a>
|
<a href="/post/@Current.Post.Id/delete">@Translate.Delete</a>
|
||||||
</td>
|
</td>
|
||||||
<td>@Current.post.status</td>
|
<td>@Current.Post.Status</td>
|
||||||
<td>@Current.tags</td>
|
<td>@Current.Tags</td>
|
||||||
</tr>
|
</tr>
|
||||||
@EndEach
|
@EndEach
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-3 col-xs-offset-2">
|
<div class="col-xs-3 col-xs-offset-2">
|
||||||
@If.hasNewer
|
@If.HasNewer
|
||||||
<p><a class="btn btn-default" href="@Model.newerLink">« @Translate.NewerPosts</a></p>
|
<p><a class="btn btn-default" href="@Model.NewerLink">« @Translate.NewerPosts</a></p>
|
||||||
@EndIf
|
@EndIf
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3 col-xs-offset-1 text-right">
|
<div class="col-xs-3 col-xs-offset-1 text-right">
|
||||||
@If.hasOlder
|
@If.HasOlder
|
||||||
<p><a class="btn btn-default" href="@Model.olderLink">@Translate.OlderPosts »</a></p>
|
<p><a class="btn btn-default" href="@Model.OlderLink">@Translate.OlderPosts »</a></p>
|
||||||
@EndIf
|
@EndIf
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,12 +3,12 @@
|
|||||||
@Section['Content']
|
@Section['Content']
|
||||||
<form action="/user/logon" method="post">
|
<form action="/user/logon" method="post">
|
||||||
@AntiForgeryToken
|
@AntiForgeryToken
|
||||||
<input type="hidden" name="returnUrl" value="@Model.form.returnUrl" />
|
<input type="hidden" name="ReturnUrl" value="@Model.Form.ReturnUrl" />
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-offset-1 col-sm-8 col-md-offset-3 col-md-6">
|
<div class="col-sm-offset-1 col-sm-8 col-md-offset-3 col-md-6">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-addon" title="@Translate.EmailAddress"><i class="fa fa-envelope"></i></span>
|
<span class="input-group-addon" title="@Translate.EmailAddress"><i class="fa fa-envelope"></i></span>
|
||||||
<input type="text" name="email" id="email" class="form-control" placeholder="@Translate.EmailAddress" />
|
<input type="text" name="Email" id="Email" class="form-control" placeholder="@Translate.EmailAddress" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -17,7 +17,7 @@
|
|||||||
<br />
|
<br />
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<span class="input-group-addon" title="@Translate.Password"><i class="fa fa-key"></i></span>
|
<span class="input-group-addon" title="@Translate.Password"><i class="fa fa-key"></i></span>
|
||||||
<input type="password" name="password" class="form-control" placeholder="@Translate.Password" />
|
<input type="password" name="Password" class="form-control" placeholder="@Translate.Password" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -35,7 +35,7 @@
|
|||||||
@Section['Scripts']
|
@Section['Scripts']
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
/* <![CDATA[ */
|
/* <![CDATA[ */
|
||||||
$(document).ready(function () { $("#email").focus() })
|
$(document).ready(function () { $("#Email").focus() })
|
||||||
/* ]]> */
|
/* ]]> */
|
||||||
</script>
|
</script>
|
||||||
@EndSection
|
@EndSection
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 text-right">
|
<div class="col-xs-12 text-right">
|
||||||
@Model.footerLogo
|
@Model.FooterLogo
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
@Each.messages
|
@Each.Messages
|
||||||
@Current.toDisplay
|
@Current.ToDisplay
|
||||||
@EndEach
|
@EndEach
|
||||||
@If.subTitle.IsSome
|
@If.SubTitle.IsSome
|
||||||
<h2>
|
<h2>
|
||||||
<span class="label label-info">@Model.subTitle</span>
|
<span class="label label-info">@Model.SubTitle</span>
|
||||||
</h2>
|
</h2>
|
||||||
@EndIf
|
@EndIf
|
||||||
@Each.posts
|
@Each.Posts
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<article>
|
<article>
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/@Current.post.permalink"
|
<a href="/@Current.Post.Permalink"
|
||||||
title="@Translate.PermanentLinkTo "@Current.post.title@quot;">@Current.post.title</a>
|
title="@Translate.PermanentLinkTo "@Current.Post.Title@quot;">@Current.Post.Title</a>
|
||||||
</h1>
|
</h1>
|
||||||
<p>
|
<p>
|
||||||
<i class="fa fa-calendar" title="@Translate.Date"></i> @Current.publishedDate
|
<i class="fa fa-calendar" title="@Translate.Date"></i> @Current.PublishedDate
|
||||||
<i class="fa fa-clock-o" title="@Translate.Time"></i> @Current.publishedTime
|
<i class="fa fa-clock-o" title="@Translate.Time"></i> @Current.PublishedTime
|
||||||
</p>
|
</p>
|
||||||
@Current.post.text
|
@Current.Post.Text
|
||||||
</article>
|
</article>
|
||||||
<hr />
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
@ -26,16 +26,16 @@
|
|||||||
@EndEach
|
@EndEach
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-3 col-xs-offset-3">
|
<div class="col-xs-3 col-xs-offset-3">
|
||||||
@If.hasNewer
|
@If.HasNewer
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-primary" href="@Model.newerLink">@Translate.NewerPosts</a>
|
<a class="btn btn-primary" href="@Model.NewerLink">@Translate.NewerPosts</a>
|
||||||
</p>
|
</p>
|
||||||
@EndIf
|
@EndIf
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3 text-right">
|
<div class="col-xs-3 text-right">
|
||||||
@If.hasOlder
|
@If.HasOlder
|
||||||
<p>
|
<p>
|
||||||
<a class="btn btn-primary" href="@Model.olderLink">@Translate.OlderPosts</a>
|
<a class="btn btn-primary" href="@Model.OlderLink">@Translate.OlderPosts</a>
|
||||||
</p>
|
</p>
|
||||||
@EndIf
|
@EndIf
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,13 +3,13 @@
|
|||||||
<head>
|
<head>
|
||||||
<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.displayPageTitle</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" />
|
||||||
<link rel="alternate" type="application/atom+xml" href="//@Model.webLog.urlBase/feed?format=atom" />
|
<link rel="alternate" type="application/atom+xml" href="//@Model.WebLog.UrlBase/feed?format=atom" />
|
||||||
<link rel="alternate" type="application/rss+xml" href="//@Model.webLog.urlBase/feed" />
|
<link rel="alternate" type="application/rss+xml" href="//@Model.WebLog.UrlBase/feed" />
|
||||||
@Section['Head'];
|
@Section['Head'];
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -17,20 +17,20 @@
|
|||||||
<nav class="navbar navbar-default">
|
<nav class="navbar navbar-default">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="navbar-header">
|
<div class="navbar-header">
|
||||||
<a class="navbar-brand" href="/">@Model.webLog.name</a>
|
<a class="navbar-brand" href="/">@Model.WebLog.Name</a>
|
||||||
</div>
|
</div>
|
||||||
<p class="navbar-text">@Model.webLogSubtitle</p>
|
<p class="navbar-text">@Model.WebLogSubtitle</p>
|
||||||
<ul class="nav navbar-nav navbar-left">
|
<ul class="nav navbar-nav navbar-left">
|
||||||
@Each.webLog.pageList
|
@Each.WebLog.PageList
|
||||||
<li><a href="/@Current.permalink">@Current.title</a></li>
|
<li><a href="/@Current.Permalink">@Current.Title</a></li>
|
||||||
@EndEach
|
@EndEach
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
@If.isAuthenticated
|
@If.IsAuthenticated
|
||||||
<li><a href="/admin">@Translate.Dashboard</a></li>
|
<li><a href="/admin">@Translate.Dashboard</a></li>
|
||||||
<li><a href="/user/logoff">@Translate.LogOff</a></li>
|
<li><a href="/user/logoff">@Translate.LogOff</a></li>
|
||||||
@EndIf
|
@EndIf
|
||||||
@IfNot.isAuthenticated
|
@IfNot.IsAuthenticated
|
||||||
<li><a href="/user/logon">@Translate.LogOn</a></li>
|
<li><a href="/user/logon">@Translate.LogOn</a></li>
|
||||||
@EndIf
|
@EndIf
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<article>
|
<article>
|
||||||
<h1>@Model.page.title</h1>
|
<h1>@Model.Page.Title</h1>
|
||||||
@Model.page.text
|
@Model.Page.Text
|
||||||
</article>
|
</article>
|
@ -1,16 +1,16 @@
|
|||||||
<article>
|
<article>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12"><h1>@Model.post.title</h1></div>
|
<div class="col-xs-12"><h1>@Model.Post.Title</h1></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<h4>
|
<h4>
|
||||||
<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
|
||||||
<span style="white-space:nowrap;">
|
<span style="white-space:nowrap;">
|
||||||
<i class="fa fa-folder-open-o" title="@Translate.Category"></i>
|
<i class="fa fa-folder-open-o" title="@Translate.Category"></i>
|
||||||
<a href="/category/@Current.slug" title="@Translate.CategorizedUnder @Current.name">@Current.name</a>
|
<a href="/category/@Current.Slug" title="@Translate.CategorizedUnder @Current.Name">@Current.Name</a>
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
@EndEach
|
@EndEach
|
||||||
@ -18,12 +18,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">@Model.post.text</div>
|
<div class="col-xs-12">@Model.Post.Text</div>
|
||||||
</div>
|
</div>
|
||||||
@If.hasTags
|
@If.HasTags
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
@Each.tags
|
@Each.Tags
|
||||||
<span style="white-space:nowrap;">
|
<span style="white-space:nowrap;">
|
||||||
<a href="/tag/@Current.Item2" title="@Translate.PostsTagged "@Current.Item1"">
|
<a href="/tag/@Current.Item2" title="@Translate.PostsTagged "@Current.Item1"">
|
||||||
<i class="fa fa-tag"></i> @Current.Item1
|
<i class="fa fa-tag"></i> @Current.Item1
|
||||||
@ -56,17 +56,17 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-6">
|
<div class="col-xs-6">
|
||||||
@If.hasNewer
|
@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.hasOlder
|
@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 »
|
||||||
</a>
|
</a>
|
||||||
@EndIf
|
@EndIf
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user