post edit page
The majority of the changes in this commit have to do with the post edit page; also caught up resource strings that hadn't been actually put in Resources.resx yet
This commit is contained in:
parent
3656bb384c
commit
6b24d416fc
28
src/myWebLog.Data/Category.fs
Normal file
28
src/myWebLog.Data/Category.fs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
module myWebLog.Data.Category
|
||||||
|
|
||||||
|
open myWebLog.Entities
|
||||||
|
open Rethink
|
||||||
|
|
||||||
|
/// Sort categories by their name, with their children sorted below them, including an indent level
|
||||||
|
let sortCategories categories =
|
||||||
|
let rec getChildren (cat : Category) indent =
|
||||||
|
seq {
|
||||||
|
yield cat, indent
|
||||||
|
for child in categories |> List.filter (fun c -> c.parentId = Some cat.id) do
|
||||||
|
yield! getChildren child (indent + 1)
|
||||||
|
}
|
||||||
|
categories
|
||||||
|
|> List.filter (fun c -> c.parentId.IsNone)
|
||||||
|
|> List.map (fun c -> getChildren c 0)
|
||||||
|
|> Seq.collect id
|
||||||
|
|> Seq.toList
|
||||||
|
|
||||||
|
/// Get all categories for a web log
|
||||||
|
let getAllCategories conn webLogId =
|
||||||
|
table Table.Category
|
||||||
|
|> getAll [| webLogId |]
|
||||||
|
|> optArg "index" "webLogId"
|
||||||
|
|> orderBy (fun c -> upcast c.["name"])
|
||||||
|
|> runCursorAsync<Category> conn
|
||||||
|
|> Seq.toList
|
||||||
|
|> sortCategories
|
@ -6,17 +6,23 @@ open Newtonsoft.Json
|
|||||||
|
|
||||||
/// Constants to use for revision source language
|
/// Constants to use for revision source language
|
||||||
module RevisionSource =
|
module RevisionSource =
|
||||||
|
[<Literal>]
|
||||||
let Markdown = "markdown"
|
let Markdown = "markdown"
|
||||||
|
[<Literal>]
|
||||||
let HTML = "html"
|
let HTML = "html"
|
||||||
|
|
||||||
/// Constants to use for authorization levels
|
/// Constants to use for authorization levels
|
||||||
module AuthorizationLevel =
|
module AuthorizationLevel =
|
||||||
|
[<Literal>]
|
||||||
let Administrator = "Administrator"
|
let Administrator = "Administrator"
|
||||||
|
[<Literal>]
|
||||||
let User = "User"
|
let User = "User"
|
||||||
|
|
||||||
/// Constants to use for post statuses
|
/// Constants to use for post statuses
|
||||||
module PostStatus =
|
module PostStatus =
|
||||||
|
[<Literal>]
|
||||||
let Draft = "Draft"
|
let Draft = "Draft"
|
||||||
|
[<Literal>]
|
||||||
let Published = "Published"
|
let Published = "Published"
|
||||||
|
|
||||||
// ---- Entities ----
|
// ---- Entities ----
|
||||||
@ -30,7 +36,12 @@ type Revision = {
|
|||||||
/// The text
|
/// The text
|
||||||
text : string
|
text : string
|
||||||
}
|
}
|
||||||
|
with
|
||||||
|
/// An empty revision
|
||||||
|
static member empty =
|
||||||
|
{ asOf = int64 0
|
||||||
|
sourceType = RevisionSource.HTML
|
||||||
|
text = "" }
|
||||||
|
|
||||||
/// A page with static content
|
/// A page with static content
|
||||||
type Page = {
|
type Page = {
|
||||||
@ -247,7 +258,7 @@ type Post = {
|
|||||||
}
|
}
|
||||||
with
|
with
|
||||||
static member empty =
|
static member empty =
|
||||||
{ id = ""
|
{ id = "new"
|
||||||
webLogId = ""
|
webLogId = ""
|
||||||
authorId = ""
|
authorId = ""
|
||||||
status = PostStatus.Draft
|
status = PostStatus.Draft
|
||||||
|
@ -12,3 +12,14 @@ let tryFindPage conn webLogId pageId : Page option =
|
|||||||
|> box with
|
|> box with
|
||||||
| null -> None
|
| null -> None
|
||||||
| page -> Some <| unbox page
|
| page -> Some <| unbox page
|
||||||
|
|
||||||
|
/// Get a page by its Id (excluding revisions)
|
||||||
|
let tryFindPageWithoutRevisions conn webLogId pageId : Page option =
|
||||||
|
match table Table.Page
|
||||||
|
|> get pageId
|
||||||
|
|> filter (fun p -> upcast p.["webLogId"].Eq(webLogId))
|
||||||
|
|> without [| "revisions" |]
|
||||||
|
|> runAtomAsync<Page> conn
|
||||||
|
|> box with
|
||||||
|
| null -> None
|
||||||
|
| page -> Some <| unbox page
|
@ -47,3 +47,29 @@ let findPageOfAllPosts conn webLogId pageNbr nbrPerPage =
|
|||||||
|> slice ((pageNbr - 1) * nbrPerPage) (pageNbr * nbrPerPage)
|
|> slice ((pageNbr - 1) * nbrPerPage) (pageNbr * nbrPerPage)
|
||||||
|> runCursorAsync<Post> conn
|
|> runCursorAsync<Post> conn
|
||||||
|> Seq.toList
|
|> Seq.toList
|
||||||
|
|
||||||
|
/// Try to find a post by its Id and web log Id
|
||||||
|
let tryFindPost conn webLogId postId : Post option =
|
||||||
|
match table Table.Post
|
||||||
|
|> get postId
|
||||||
|
|> filter (fun p -> upcast p.["webLogId"].Eq(webLogId))
|
||||||
|
|> runAtomAsync<Post> conn
|
||||||
|
|> box with
|
||||||
|
| null -> None
|
||||||
|
| post -> Some <| unbox post
|
||||||
|
|
||||||
|
/// Save a post
|
||||||
|
let savePost conn post =
|
||||||
|
match post.id with
|
||||||
|
| "new" -> let newPost = { post with id = string <| System.Guid.NewGuid() }
|
||||||
|
table Table.Post
|
||||||
|
|> insert newPost
|
||||||
|
|> runResultAsync conn
|
||||||
|
|> ignore
|
||||||
|
newPost.id
|
||||||
|
| _ -> table Table.Post
|
||||||
|
|> get post.id
|
||||||
|
|> replace post
|
||||||
|
|> runResultAsync conn
|
||||||
|
|> ignore
|
||||||
|
post.id
|
||||||
|
@ -14,7 +14,7 @@ let insert (expr : obj) (table : Table) = table.Insert expr
|
|||||||
let limit (expr : obj) (table : ReqlExpr) = table.Limit expr
|
let limit (expr : obj) (table : ReqlExpr) = table.Limit expr
|
||||||
let optArg key (value : obj) (expr : GetAll) = expr.OptArg (key, value)
|
let optArg key (value : obj) (expr : GetAll) = expr.OptArg (key, value)
|
||||||
let orderBy (exprA : ReqlExpr -> ReqlExpr) (expr : ReqlExpr) = expr.OrderBy exprA
|
let orderBy (exprA : ReqlExpr -> ReqlExpr) (expr : ReqlExpr) = expr.OrderBy exprA
|
||||||
let replace (exprA : obj) (expr : ReqlExpr) = expr.Replace exprA
|
let replace (exprA : obj) (expr : Get) = expr.Replace exprA
|
||||||
let runAtomAsync<'T> (conn : IConnection) (ast : ReqlAst) = ast.RunAtomAsync<'T> conn |> await
|
let runAtomAsync<'T> (conn : IConnection) (ast : ReqlAst) = ast.RunAtomAsync<'T> conn |> await
|
||||||
let runCursorAsync<'T> (conn : IConnection) (ast : ReqlAst) = ast.RunCursorAsync<'T> conn |> await
|
let runCursorAsync<'T> (conn : IConnection) (ast : ReqlAst) = ast.RunCursorAsync<'T> conn |> await
|
||||||
let runListAsync<'T> (conn : IConnection) (ast : ReqlAst) = ast.RunAtomAsync<System.Collections.Generic.List<'T>> conn
|
let runListAsync<'T> (conn : IConnection) (ast : ReqlAst) = ast.RunAtomAsync<System.Collections.Generic.List<'T>> conn
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
<Compile Include="DataConfig.fs" />
|
<Compile Include="DataConfig.fs" />
|
||||||
<Compile Include="Rethink.fs" />
|
<Compile Include="Rethink.fs" />
|
||||||
<Compile Include="SetUp.fs" />
|
<Compile Include="SetUp.fs" />
|
||||||
|
<Compile Include="Category.fs" />
|
||||||
<Compile Include="Page.fs" />
|
<Compile Include="Page.fs" />
|
||||||
<Compile Include="Post.fs" />
|
<Compile Include="Post.fs" />
|
||||||
<Compile Include="WebLog.fs" />
|
<Compile Include="WebLog.fs" />
|
||||||
|
297
src/myWebLog.Resources/Resources.Designer.cs
generated
297
src/myWebLog.Resources/Resources.Designer.cs
generated
@ -60,6 +60,132 @@ namespace myWebLog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Added.
|
||||||
|
/// </summary>
|
||||||
|
public static string Added {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Added", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Add New.
|
||||||
|
/// </summary>
|
||||||
|
public static string AddNew {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AddNew", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Add New Post.
|
||||||
|
/// </summary>
|
||||||
|
public static string AddNewPost {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AddNewPost", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Admin.
|
||||||
|
/// </summary>
|
||||||
|
public static string Admin {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Admin", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to and Published.
|
||||||
|
/// </summary>
|
||||||
|
public static string AndPublished {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("AndPublished", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Categories.
|
||||||
|
/// </summary>
|
||||||
|
public static string Categories {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Categories", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Dashboard.
|
||||||
|
/// </summary>
|
||||||
|
public static string Dashboard {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Dashboard", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Date.
|
||||||
|
/// </summary>
|
||||||
|
public static string Date {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Date", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Delete.
|
||||||
|
/// </summary>
|
||||||
|
public static string Delete {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Delete", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Edit.
|
||||||
|
/// </summary>
|
||||||
|
public static string Edit {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Edit", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Edit Post.
|
||||||
|
/// </summary>
|
||||||
|
public static string EditPost {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("EditPost", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Could not convert data-config.json to RethinkDB connection.
|
||||||
|
/// </summary>
|
||||||
|
public static string ErrDataConfig {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ErrDataConfig", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to is not properly configured for myWebLog.
|
||||||
|
/// </summary>
|
||||||
|
public static string ErrNotConfigured {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("ErrNotConfigured", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Log Off.
|
||||||
|
/// </summary>
|
||||||
|
public static string LogOff {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("LogOff", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Log On.
|
/// Looks up a localized string similar to Log On.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -68,5 +194,176 @@ namespace myWebLog {
|
|||||||
return ResourceManager.GetString("LogOn", resourceCulture);
|
return ResourceManager.GetString("LogOn", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to {0}{1} post successfully.
|
||||||
|
/// </summary>
|
||||||
|
public static string MsgPostEditSuccess {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("MsgPostEditSuccess", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Newer Posts.
|
||||||
|
/// </summary>
|
||||||
|
public static string NewerPosts {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("NewerPosts", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Older Posts.
|
||||||
|
/// </summary>
|
||||||
|
public static string OlderPosts {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("OlderPosts", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Page #.
|
||||||
|
/// </summary>
|
||||||
|
public static string PageHash {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PageHash", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Permalink.
|
||||||
|
/// </summary>
|
||||||
|
public static string Permalink {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Permalink", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Permanent link to.
|
||||||
|
/// </summary>
|
||||||
|
public static string PermanentLinkTo {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PermanentLinkTo", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Post Details.
|
||||||
|
/// </summary>
|
||||||
|
public static string PostDetails {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PostDetails", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Posts.
|
||||||
|
/// </summary>
|
||||||
|
public static string Posts {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Posts", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Post Status.
|
||||||
|
/// </summary>
|
||||||
|
public static string PostStatus {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PostStatus", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to PublishedDate.
|
||||||
|
/// </summary>
|
||||||
|
public static string PublishedDate {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PublishedDate", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Publish This Post.
|
||||||
|
/// </summary>
|
||||||
|
public static string PublishThisPost {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("PublishThisPost", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Save.
|
||||||
|
/// </summary>
|
||||||
|
public static string Save {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Save", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to starting with.
|
||||||
|
/// </summary>
|
||||||
|
public static string startingWith {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("startingWith", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Status.
|
||||||
|
/// </summary>
|
||||||
|
public static string Status {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Status", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Tags.
|
||||||
|
/// </summary>
|
||||||
|
public static string Tags {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Tags", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Time.
|
||||||
|
/// </summary>
|
||||||
|
public static string Time {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Time", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Title.
|
||||||
|
/// </summary>
|
||||||
|
public static string Title {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Title", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to Updated.
|
||||||
|
/// </summary>
|
||||||
|
public static string Updated {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("Updated", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized string similar to View.
|
||||||
|
/// </summary>
|
||||||
|
public static string View {
|
||||||
|
get {
|
||||||
|
return ResourceManager.GetString("View", resourceCulture);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,106 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
|
<data name="Added" xml:space="preserve">
|
||||||
|
<value>Added</value>
|
||||||
|
</data>
|
||||||
|
<data name="AddNew" xml:space="preserve">
|
||||||
|
<value>Add New</value>
|
||||||
|
</data>
|
||||||
|
<data name="AddNewPost" xml:space="preserve">
|
||||||
|
<value>Add New Post</value>
|
||||||
|
</data>
|
||||||
|
<data name="Admin" xml:space="preserve">
|
||||||
|
<value>Admin</value>
|
||||||
|
</data>
|
||||||
|
<data name="AndPublished" xml:space="preserve">
|
||||||
|
<value> and Published</value>
|
||||||
|
</data>
|
||||||
|
<data name="Categories" xml:space="preserve">
|
||||||
|
<value>Categories</value>
|
||||||
|
</data>
|
||||||
|
<data name="Dashboard" xml:space="preserve">
|
||||||
|
<value>Dashboard</value>
|
||||||
|
</data>
|
||||||
|
<data name="Date" xml:space="preserve">
|
||||||
|
<value>Date</value>
|
||||||
|
</data>
|
||||||
|
<data name="Delete" xml:space="preserve">
|
||||||
|
<value>Delete</value>
|
||||||
|
</data>
|
||||||
|
<data name="Edit" xml:space="preserve">
|
||||||
|
<value>Edit</value>
|
||||||
|
</data>
|
||||||
|
<data name="EditPost" xml:space="preserve">
|
||||||
|
<value>Edit Post</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrDataConfig" xml:space="preserve">
|
||||||
|
<value>Could not convert data-config.json to RethinkDB connection</value>
|
||||||
|
</data>
|
||||||
|
<data name="ErrNotConfigured" xml:space="preserve">
|
||||||
|
<value>is not properly configured for myWebLog</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogOff" xml:space="preserve">
|
||||||
|
<value>Log Off</value>
|
||||||
|
</data>
|
||||||
<data name="LogOn" xml:space="preserve">
|
<data name="LogOn" xml:space="preserve">
|
||||||
<value>Log On</value>
|
<value>Log On</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="MsgPostEditSuccess" xml:space="preserve">
|
||||||
|
<value>{0}{1} post successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="NewerPosts" xml:space="preserve">
|
||||||
|
<value>Newer Posts</value>
|
||||||
|
</data>
|
||||||
|
<data name="OlderPosts" xml:space="preserve">
|
||||||
|
<value>Older Posts</value>
|
||||||
|
</data>
|
||||||
|
<data name="PageHash" xml:space="preserve">
|
||||||
|
<value>Page #</value>
|
||||||
|
</data>
|
||||||
|
<data name="Permalink" xml:space="preserve">
|
||||||
|
<value>Permalink</value>
|
||||||
|
</data>
|
||||||
|
<data name="PermanentLinkTo" xml:space="preserve">
|
||||||
|
<value>Permanent link to</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostDetails" xml:space="preserve">
|
||||||
|
<value>Post Details</value>
|
||||||
|
</data>
|
||||||
|
<data name="Posts" xml:space="preserve">
|
||||||
|
<value>Posts</value>
|
||||||
|
</data>
|
||||||
|
<data name="PostStatus" xml:space="preserve">
|
||||||
|
<value>Post Status</value>
|
||||||
|
</data>
|
||||||
|
<data name="PublishedDate" xml:space="preserve">
|
||||||
|
<value>PublishedDate</value>
|
||||||
|
</data>
|
||||||
|
<data name="PublishThisPost" xml:space="preserve">
|
||||||
|
<value>Publish This Post</value>
|
||||||
|
</data>
|
||||||
|
<data name="Save" xml:space="preserve">
|
||||||
|
<value>Save</value>
|
||||||
|
</data>
|
||||||
|
<data name="startingWith" xml:space="preserve">
|
||||||
|
<value>starting with</value>
|
||||||
|
</data>
|
||||||
|
<data name="Status" xml:space="preserve">
|
||||||
|
<value>Status</value>
|
||||||
|
</data>
|
||||||
|
<data name="Tags" xml:space="preserve">
|
||||||
|
<value>Tags</value>
|
||||||
|
</data>
|
||||||
|
<data name="Time" xml:space="preserve">
|
||||||
|
<value>Time</value>
|
||||||
|
</data>
|
||||||
|
<data name="Title" xml:space="preserve">
|
||||||
|
<value>Title</value>
|
||||||
|
</data>
|
||||||
|
<data name="Updated" xml:space="preserve">
|
||||||
|
<value>Updated</value>
|
||||||
|
</data>
|
||||||
|
<data name="View" xml:space="preserve">
|
||||||
|
<value>View</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -15,6 +15,7 @@ open Nancy.Session.Persistable
|
|||||||
open Nancy.Session.RethinkDb
|
open Nancy.Session.RethinkDb
|
||||||
open Nancy.TinyIoc
|
open Nancy.TinyIoc
|
||||||
open Nancy.ViewEngines.SuperSimpleViewEngine
|
open Nancy.ViewEngines.SuperSimpleViewEngine
|
||||||
|
open NodaTime
|
||||||
open RethinkDb.Driver.Net
|
open RethinkDb.Driver.Net
|
||||||
open Suave
|
open Suave
|
||||||
open Suave.Owin
|
open Suave.Owin
|
||||||
@ -23,8 +24,7 @@ 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 -> ApplicationException("Could not convert data-config.json to RethinkDB connection", ex)
|
with ex -> raise <| ApplicationException(Resources.ErrDataConfig, ex)
|
||||||
|> raise
|
|
||||||
|
|
||||||
do
|
do
|
||||||
startUpCheck cfg
|
startUpCheck cfg
|
||||||
@ -75,6 +75,9 @@ type MyWebLogBootstrapper() =
|
|||||||
|> ignore
|
|> ignore
|
||||||
container.Register<IConnection>(cfg.conn)
|
container.Register<IConnection>(cfg.conn)
|
||||||
|> ignore
|
|> ignore
|
||||||
|
// NodaTime
|
||||||
|
container.Register<IClock>(SystemClock.Instance)
|
||||||
|
|> ignore
|
||||||
// I18N in SSVE
|
// I18N in SSVE
|
||||||
container.Register<seq<ISuperSimpleViewEngineMatcher>>(fun _ _ ->
|
container.Register<seq<ISuperSimpleViewEngineMatcher>>(fun _ _ ->
|
||||||
Seq.singleton (TranslateTokenViewEngineMatcher() :> ISuperSimpleViewEngineMatcher))
|
Seq.singleton (TranslateTokenViewEngineMatcher() :> ISuperSimpleViewEngineMatcher))
|
||||||
@ -116,7 +119,7 @@ type RequestEnvironment() =
|
|||||||
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 -> ApplicationException
|
||||||
(sprintf "%s is not properly configured for myWebLog" ctx.Request.Url.HostName)
|
(sprintf "%s %s" ctx.Request.Url.HostName Resources.ErrNotConfigured)
|
||||||
|> raise
|
|> raise
|
||||||
ctx.Items.[Keys.Version] <- version
|
ctx.Items.[Keys.Version] <- version
|
||||||
null)
|
null)
|
||||||
|
@ -1,23 +1,30 @@
|
|||||||
namespace myWebLog
|
[<AutoOpen>]
|
||||||
|
module myWebLog.ModuleExtensions
|
||||||
|
|
||||||
|
open myWebLog
|
||||||
open myWebLog.Entities
|
open myWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.Security
|
open Nancy.Security
|
||||||
|
|
||||||
/// Parent class for all myWebLog Nancy modules
|
/// Parent class for all myWebLog Nancy modules
|
||||||
[<AbstractClass>]
|
type NancyModule with
|
||||||
type MyWebLogModule() =
|
|
||||||
inherit NancyModule()
|
|
||||||
|
|
||||||
/// Strongly-typed access to the web log for the current request
|
/// Strongly-typed access to the web log for the current request
|
||||||
member this.WebLog = this.Context.Items.[Keys.WebLog] :?> WebLog
|
member this.WebLog = this.Context.Items.[Keys.WebLog] :?> WebLog
|
||||||
|
|
||||||
/// Display a view using the theme specified for the web log
|
/// Display a view using the theme specified for the web log
|
||||||
member this.ThemedRender view model = this.View.[(sprintf "%s/%s" this.WebLog.themePath view), model]
|
member this.ThemedView view model = this.View.[(sprintf "%s/%s" this.WebLog.themePath view), model]
|
||||||
|
|
||||||
/// Return a 404
|
/// Return a 404
|
||||||
member this.NotFound () = this.Negotiate.WithStatusCode 404
|
member this.NotFound () = this.Negotiate.WithStatusCode 404
|
||||||
|
|
||||||
|
/// Redirect a request, storing messages in the session if they exist
|
||||||
|
member this.Redirect url (model : MyWebLogModel) =
|
||||||
|
match List.length model.messages with
|
||||||
|
| 0 -> ()
|
||||||
|
| _ -> this.Session.[Keys.Messages] <- model.messages
|
||||||
|
this.Negotiate.WithHeader("Location", 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()
|
@ -1,28 +1,36 @@
|
|||||||
namespace myWebLog
|
namespace myWebLog
|
||||||
|
|
||||||
|
open FSharp.Markdown
|
||||||
|
open myWebLog.Data.Category
|
||||||
open myWebLog.Data.Page
|
open myWebLog.Data.Page
|
||||||
open myWebLog.Data.Post
|
open myWebLog.Data.Post
|
||||||
|
open myWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.Authentication.Forms
|
open Nancy.Authentication.Forms
|
||||||
|
open Nancy.ModelBinding
|
||||||
open Nancy.Security
|
open Nancy.Security
|
||||||
|
open Nancy.Session.Persistable
|
||||||
|
open NodaTime
|
||||||
open RethinkDb.Driver.Net
|
open RethinkDb.Driver.Net
|
||||||
open myWebLog.Entities
|
|
||||||
|
|
||||||
type PostModule(conn : IConnection) as this =
|
/// Routes dealing with posts (including the home page)
|
||||||
inherit MyWebLogModule()
|
type PostModule(conn : IConnection, clock : IClock) as this =
|
||||||
|
inherit NancyModule()
|
||||||
|
|
||||||
|
let getPage (parms : obj) = ((parms :?> DynamicDictionary).["page"] :?> int)
|
||||||
|
|
||||||
do
|
do
|
||||||
this.Get .["/" ] <- fun _ -> upcast this.HomePage ()
|
this.Get .["/" ] <- fun _ -> upcast this.HomePage ()
|
||||||
this.Get.["/posts/page/{page:int}"] <- fun parms -> upcast this.GetPageOfPublishedPosts (downcast parms)
|
this.Get .["/posts/page/{page:int}" ] <- fun parms -> upcast this.DisplayPageOfPublishedPosts (getPage parms)
|
||||||
|
|
||||||
this.Get .["/posts/list" ] <- fun _ -> upcast this.PostList 1
|
this.Get .["/posts/list" ] <- fun _ -> upcast this.PostList 1
|
||||||
this.Get.["/posts/list/page/{page:int"] <- fun parms -> upcast this.PostList
|
this.Get .["/posts/list/page/{page:int}"] <- fun parms -> upcast this.PostList (getPage parms)
|
||||||
((parms :?> DynamicDictionary).["page"] :?> int)
|
this.Get .["/post/{postId}/edit" ] <- fun parms -> upcast this.EditPost (downcast parms)
|
||||||
|
this.Post.["/post/{postId}/edit" ] <- fun parms -> upcast this.SavePost (downcast parms)
|
||||||
|
|
||||||
// ---- Display posts to users ----
|
// ---- Display posts to users ----
|
||||||
|
|
||||||
/// Display a page of published posts
|
/// Display a page of published posts
|
||||||
member private this.DisplayPageOfPublishedPosts pageNbr =
|
member this.DisplayPageOfPublishedPosts 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
|
model.posts <- findPageOfPublishedPosts conn this.WebLog.id pageNbr 10
|
||||||
@ -35,22 +43,18 @@ type PostModule(conn : IConnection) as this =
|
|||||||
model.urlPrefix <- "/posts"
|
model.urlPrefix <- "/posts"
|
||||||
model.pageTitle <- match pageNbr with
|
model.pageTitle <- match pageNbr with
|
||||||
| 1 -> ""
|
| 1 -> ""
|
||||||
| _ -> sprintf "Page #%i" pageNbr
|
| _ -> sprintf "%s%i" Resources.PageHash pageNbr
|
||||||
this.ThemedRender "posts" model
|
this.ThemedView "posts" 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.DisplayPageOfPublishedPosts 1
|
| "posts" -> this.DisplayPageOfPublishedPosts 1
|
||||||
| page -> match tryFindPage conn this.WebLog.id page with
|
| page -> match tryFindPageWithoutRevisions conn this.WebLog.id page 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.ThemedRender "page" model
|
this.ThemedView "page" model
|
||||||
| None -> this.Negotiate.WithStatusCode 404
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
/// Get a page of public posts (other than the first one if the home page is a page of posts)
|
|
||||||
member this.GetPageOfPublishedPosts (parameters : DynamicDictionary) =
|
|
||||||
this.DisplayPageOfPublishedPosts (parameters.["page"] :?> int)
|
|
||||||
|
|
||||||
// ---- Administer posts ----
|
// ---- Administer posts ----
|
||||||
|
|
||||||
@ -61,7 +65,78 @@ type PostModule(conn : IConnection) as this =
|
|||||||
model.pageNbr <- pageNbr
|
model.pageNbr <- pageNbr
|
||||||
model.posts <- findPageOfAllPosts conn this.WebLog.id pageNbr 25
|
model.posts <- findPageOfAllPosts conn this.WebLog.id pageNbr 25
|
||||||
model.hasNewer <- pageNbr > 1
|
model.hasNewer <- pageNbr > 1
|
||||||
model.hasOlder <- 25 > List.length model.posts
|
model.hasOlder <- List.length model.posts < 25
|
||||||
model.urlPrefix <- "/post/list"
|
model.urlPrefix <- "/post/list"
|
||||||
model.pageTitle <- "Posts"
|
model.pageTitle <- Resources.Posts
|
||||||
this.View.["admin/post/list", model]
|
this.View.["admin/post/list", model]
|
||||||
|
|
||||||
|
/// Edit a post
|
||||||
|
member this.EditPost (parameters : DynamicDictionary) =
|
||||||
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
|
let postId : string = downcast parameters.["postId"]
|
||||||
|
match (match postId with
|
||||||
|
| "new" -> Some Post.empty
|
||||||
|
| _ -> tryFindPost conn this.WebLog.id postId) with
|
||||||
|
| Some post -> let rev = match post.revisions
|
||||||
|
|> List.sortByDescending (fun r -> r.asOf)
|
||||||
|
|> List.tryHead with
|
||||||
|
| Some r -> r
|
||||||
|
| None -> Revision.empty
|
||||||
|
let model = EditPostModel(this.Context, this.WebLog, post, rev)
|
||||||
|
model.categories <- getAllCategories conn this.WebLog.id
|
||||||
|
|> List.map (fun cat -> string (fst cat).id,
|
||||||
|
sprintf "%s%s"
|
||||||
|
(String.replicate (snd cat) " ")
|
||||||
|
(fst cat).name)
|
||||||
|
model.pageTitle <- match post.id with
|
||||||
|
| "new" -> Resources.AddNewPost
|
||||||
|
| _ -> Resources.EditPost
|
||||||
|
this.View.["admin/post/edit"]
|
||||||
|
| None -> this.NotFound ()
|
||||||
|
|
||||||
|
/// Save a post
|
||||||
|
member this.SavePost (parameters : DynamicDictionary) =
|
||||||
|
this.RequiresAccessLevel AuthorizationLevel.Administrator
|
||||||
|
this.ValidateCsrfToken ()
|
||||||
|
let postId : string = downcast parameters.["postId"]
|
||||||
|
let form = this.Bind<EditPostForm>()
|
||||||
|
let now = clock.Now.Ticks
|
||||||
|
match (match postId with
|
||||||
|
| "new" -> Some Post.empty
|
||||||
|
| _ -> tryFindPost conn this.WebLog.id postId) with
|
||||||
|
| Some p -> let justPublished = p.publishedOn = int64 0 && form.publishNow
|
||||||
|
let post = match postId with
|
||||||
|
| "new" -> { p with
|
||||||
|
webLogId = this.WebLog.id
|
||||||
|
authorId = (this.Request.PersistableSession.GetOrDefault<User>
|
||||||
|
(Keys.User, User.empty)).id }
|
||||||
|
| _ -> p
|
||||||
|
let pId = { post with
|
||||||
|
status = match form.publishNow with
|
||||||
|
| true -> PostStatus.Published
|
||||||
|
| _ -> PostStatus.Draft
|
||||||
|
title = form.title
|
||||||
|
permalink = form.permalink
|
||||||
|
publishedOn = match justPublished with | true -> now | _ -> int64 0
|
||||||
|
updatedOn = now
|
||||||
|
text = match form.source with
|
||||||
|
| RevisionSource.Markdown -> Markdown.TransformHtml form.text
|
||||||
|
| _ -> form.text
|
||||||
|
categoryIds = Array.toList form.categories
|
||||||
|
tags = form.tags.Split ','
|
||||||
|
|> Seq.map (fun t -> t.Trim().ToLowerInvariant())
|
||||||
|
|> Seq.toList
|
||||||
|
revisions = { asOf = now
|
||||||
|
sourceType = form.source
|
||||||
|
text = form.text } :: post.revisions }
|
||||||
|
|> savePost conn
|
||||||
|
let model = MyWebLogModel(this.Context, this.WebLog)
|
||||||
|
{ level = Level.Info
|
||||||
|
message = System.String.Format
|
||||||
|
(Resources.MsgPostEditSuccess,
|
||||||
|
(match postId with | "new" -> Resources.Added | _ -> Resources.Updated),
|
||||||
|
(match justPublished with | true -> Resources.AndPublished | _ -> ""))
|
||||||
|
details = None }
|
||||||
|
|> model.addMessage
|
||||||
|
this.Redirect (sprintf "/post/%s/edit" pId) model
|
||||||
|
| None -> this.NotFound ()
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
open myWebLog.Entities
|
open myWebLog.Entities
|
||||||
open Nancy
|
open Nancy
|
||||||
open Nancy.Session.Persistable
|
open Nancy.Session.Persistable
|
||||||
|
open NodaTime
|
||||||
|
open NodaTime.Text
|
||||||
|
|
||||||
|
|
||||||
/// Levels for a user message
|
/// Levels for a user message
|
||||||
module Level =
|
module Level =
|
||||||
@ -57,6 +60,23 @@ type MyWebLogModel(ctx : NancyContext, webLog : WebLog) =
|
|||||||
/// 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
|
||||||
|
|
||||||
|
/// Convert ticks to a zoned date/time for the current web log
|
||||||
|
member this.zonedTime ticks = Instant(ticks).InZone(DateTimeZoneProviders.Tzdb.[this.webLog.timeZone])
|
||||||
|
|
||||||
|
/// Display a long date
|
||||||
|
member this.displayLongDate ticks =
|
||||||
|
this.zonedTime ticks
|
||||||
|
|> ZonedDateTimePattern.CreateWithCurrentCulture("MMMM d',' yyyy", DateTimeZoneProviders.Tzdb).Format
|
||||||
|
|
||||||
|
/// Display a short date
|
||||||
|
member this.displayShortDate ticks =
|
||||||
|
this.zonedTime ticks
|
||||||
|
|> ZonedDateTimePattern.CreateWithCurrentCulture("MMM d',' yyyy", DateTimeZoneProviders.Tzdb).Format
|
||||||
|
|
||||||
|
/// Display the time
|
||||||
|
member this.displayTime ticks =
|
||||||
|
(this.zonedTime ticks
|
||||||
|
|> ZonedDateTimePattern.CreateWithCurrentCulture("h':'mmtt", DateTimeZoneProviders.Tzdb).Format).ToLower()
|
||||||
|
|
||||||
/// Model for all page-of-posts pages
|
/// Model for all page-of-posts pages
|
||||||
type PostsModel(ctx, webLog) =
|
type PostsModel(ctx, webLog) =
|
||||||
@ -93,3 +113,52 @@ type PageModel(ctx, webLog, page) =
|
|||||||
|
|
||||||
/// The page to be displayed
|
/// The page to be displayed
|
||||||
member this.page : Page = page
|
member this.page : Page = page
|
||||||
|
|
||||||
|
|
||||||
|
/// Form for editing a post
|
||||||
|
type EditPostForm() =
|
||||||
|
/// The title of the post
|
||||||
|
member val title = "" with get, set
|
||||||
|
/// The permalink for the post
|
||||||
|
member val permalink = "" with get, set
|
||||||
|
/// The source type for this revision
|
||||||
|
member val source = "" with get, set
|
||||||
|
/// The text
|
||||||
|
member val text = "" with get, set
|
||||||
|
/// Tags for the post
|
||||||
|
member val tags = "" with get, set
|
||||||
|
/// The selected category Ids for the post
|
||||||
|
member val categories = Array.empty<string> with get, set
|
||||||
|
/// Whether the post should be published
|
||||||
|
member val publishNow = true with get, set
|
||||||
|
|
||||||
|
/// Fill the form with applicable values from a post
|
||||||
|
member this.forPost post =
|
||||||
|
this.title <- post.title
|
||||||
|
this.permalink <- post.permalink
|
||||||
|
this.tags <- List.reduce (fun acc x -> sprintf "%s, %s" acc x) post.tags
|
||||||
|
this.categories <- List.toArray post.categoryIds
|
||||||
|
this
|
||||||
|
|
||||||
|
/// Fill the form with applicable values from a revision
|
||||||
|
member this.forRevision rev =
|
||||||
|
this.source <- rev.sourceType
|
||||||
|
this.text <- rev.text
|
||||||
|
this
|
||||||
|
|
||||||
|
/// View model for the edit post page
|
||||||
|
type EditPostModel(ctx, webLog, post, revision) =
|
||||||
|
inherit MyWebLogModel(ctx, webLog)
|
||||||
|
|
||||||
|
/// The form
|
||||||
|
member val form = EditPostForm().forPost(post).forRevision(revision) with get, set
|
||||||
|
/// The post being edited
|
||||||
|
member val post = post with get, set
|
||||||
|
/// The categories to which the post may be assigned
|
||||||
|
member val categories = List.empty<string * string> with get, set
|
||||||
|
/// Whether the post is currently published
|
||||||
|
member this.isPublished = PostStatus.Published = this.post.status
|
||||||
|
/// The published date
|
||||||
|
member this.publishedDate = this.displayLongDate this.post.publishedOn
|
||||||
|
/// The published time
|
||||||
|
member this.publishedTime = this.displayTime this.post.publishedOn
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
<Compile Include="AssemblyInfo.fs" />
|
<Compile Include="AssemblyInfo.fs" />
|
||||||
<Compile Include="Keys.fs" />
|
<Compile Include="Keys.fs" />
|
||||||
<Compile Include="ViewModels.fs" />
|
<Compile Include="ViewModels.fs" />
|
||||||
<Compile Include="MyWebLogModule.fs" />
|
<Compile Include="ModuleExtensions.fs" />
|
||||||
<Compile Include="PostModule.fs" />
|
<Compile Include="PostModule.fs" />
|
||||||
<Compile Include="App.fs" />
|
<Compile Include="App.fs" />
|
||||||
<Content Include="packages.config" />
|
<Content Include="packages.config" />
|
||||||
@ -67,10 +67,42 @@
|
|||||||
<HintPath>..\packages\Common.Logging.Core.3.3.1\lib\net40\Common.Logging.Core.dll</HintPath>
|
<HintPath>..\packages\Common.Logging.Core.3.3.1\lib\net40\Common.Logging.Core.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="CSharpFormat">
|
||||||
|
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\CSharpFormat.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FSharp.CodeFormat">
|
||||||
|
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.CodeFormat.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FSharp.Compiler.Service">
|
||||||
|
<HintPath>..\packages\FSharp.Compiler.Service.2.0.0.6\lib\net45\FSharp.Compiler.Service.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="FSharp.Core">
|
<Reference Include="FSharp.Core">
|
||||||
<HintPath>..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll</HintPath>
|
<HintPath>..\packages\FSharp.Core.4.0.0.1\lib\net40\FSharp.Core.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="FSharp.Formatting.Common">
|
||||||
|
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Formatting.Common.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FSharp.Literate">
|
||||||
|
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Literate.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FSharp.Markdown">
|
||||||
|
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.Markdown.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FSharp.MetadataFormat">
|
||||||
|
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\FSharp.MetadataFormat.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FSharpVSPowerTools.Core">
|
||||||
|
<HintPath>..\packages\FSharpVSPowerTools.Core.2.3.0\lib\net45\FSharpVSPowerTools.Core.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="mscorlib" />
|
<Reference Include="mscorlib" />
|
||||||
<Reference Include="Nancy">
|
<Reference Include="Nancy">
|
||||||
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
|
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
|
||||||
@ -92,6 +124,14 @@
|
|||||||
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="NodaTime">
|
||||||
|
<HintPath>..\packages\NodaTime.1.3.2\lib\net35-Client\NodaTime.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="RazorEngine">
|
||||||
|
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\RazorEngine.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="RethinkDb.Driver">
|
<Reference Include="RethinkDb.Driver">
|
||||||
<HintPath>..\packages\RethinkDb.Driver.2.3.8\lib\net45\RethinkDb.Driver.dll</HintPath>
|
<HintPath>..\packages\RethinkDb.Driver.2.3.8\lib\net45\RethinkDb.Driver.dll</HintPath>
|
||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
@ -101,8 +141,14 @@
|
|||||||
<Private>True</Private>
|
<Private>True</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="System" />
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.ComponentModel.DataAnnotations" />
|
||||||
<Reference Include="System.Core" />
|
<Reference Include="System.Core" />
|
||||||
<Reference Include="System.Numerics" />
|
<Reference Include="System.Numerics" />
|
||||||
|
<Reference Include="System.Web.Razor">
|
||||||
|
<HintPath>..\packages\FSharp.Formatting.2.14.4\lib\net40\System.Web.Razor.dll</HintPath>
|
||||||
|
<Private>True</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\myWebLog.Data\myWebLog.Data.fsproj">
|
<ProjectReference Include="..\myWebLog.Data\myWebLog.Data.fsproj">
|
||||||
|
@ -2,12 +2,16 @@
|
|||||||
<packages>
|
<packages>
|
||||||
<package id="Common.Logging" version="3.3.1" targetFramework="net452" />
|
<package id="Common.Logging" version="3.3.1" targetFramework="net452" />
|
||||||
<package id="Common.Logging.Core" version="3.3.1" targetFramework="net452" />
|
<package id="Common.Logging.Core" version="3.3.1" targetFramework="net452" />
|
||||||
|
<package id="FSharp.Compiler.Service" version="2.0.0.6" targetFramework="net452" />
|
||||||
<package id="FSharp.Core" version="4.0.0.1" targetFramework="net452" />
|
<package id="FSharp.Core" version="4.0.0.1" targetFramework="net452" />
|
||||||
|
<package id="FSharp.Formatting" version="2.14.4" targetFramework="net452" />
|
||||||
|
<package id="FSharpVSPowerTools.Core" version="2.3.0" targetFramework="net452" />
|
||||||
<package id="Nancy" version="1.4.3" targetFramework="net452" />
|
<package id="Nancy" version="1.4.3" targetFramework="net452" />
|
||||||
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" />
|
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net452" />
|
||||||
<package id="Nancy.Session.Persistable" version="0.8.6" targetFramework="net452" />
|
<package id="Nancy.Session.Persistable" version="0.8.6" targetFramework="net452" />
|
||||||
<package id="Nancy.Session.RethinkDB" version="0.8.6" targetFramework="net452" />
|
<package id="Nancy.Session.RethinkDB" version="0.8.6" targetFramework="net452" />
|
||||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
|
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
|
||||||
|
<package id="NodaTime" version="1.3.2" targetFramework="net452" />
|
||||||
<package id="RethinkDb.Driver" version="2.3.8" targetFramework="net452" />
|
<package id="RethinkDb.Driver" version="2.3.8" targetFramework="net452" />
|
||||||
<package id="Suave" version="1.1.3" targetFramework="net452" />
|
<package id="Suave" version="1.1.3" targetFramework="net452" />
|
||||||
</packages>
|
</packages>
|
10
src/myWebLog/content/scripts/tinymce-init.js
Normal file
10
src/myWebLog/content/scripts/tinymce-init.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
tinymce.init({
|
||||||
|
menubar: false,
|
||||||
|
plugins: [
|
||||||
|
"advlist autolink link image lists charmap print preview hr anchor pagebreak spellchecker",
|
||||||
|
"searchreplace wordcount visualblocks visualchars code fullscreen insertdatetime media nonbreaking",
|
||||||
|
"save table contextmenu directionality emoticons template paste textcolor"
|
||||||
|
],
|
||||||
|
selector: "textarea",
|
||||||
|
toolbar: "styleselect | forecolor backcolor | bullist numlist | link unlink anchor | paste pastetext | spellchecker | visualblocks visualchars | code fullscreen"
|
||||||
|
})
|
@ -66,8 +66,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Content Include="content\scripts\tinymce-init.js" />
|
||||||
<Content Include="content\styles\admin.css" />
|
<Content Include="content\styles\admin.css" />
|
||||||
<Content Include="views\admin\admin-layout.html" />
|
<Content Include="views\admin\admin-layout.html" />
|
||||||
|
<Content Include="views\admin\post\edit.html" />
|
||||||
<Content Include="views\admin\post\list.html" />
|
<Content Include="views\admin\post\list.html" />
|
||||||
<Content Include="views\default\index-content.html" />
|
<Content Include="views\default\index-content.html" />
|
||||||
<Content Include="views\default\index.html" />
|
<Content Include="views\default\index.html" />
|
||||||
|
95
src/myWebLog/views/admin/post/edit.html
Normal file
95
src/myWebLog/views/admin/post/edit.html
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
@Master['admin/admin-layout']
|
||||||
|
|
||||||
|
@Section['Content']
|
||||||
|
<form action='/post/@Model.post.id/edit' method="post">
|
||||||
|
@AntiForgeryToken
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="title">@Translate.Title</label>
|
||||||
|
<input type="text" name="title" id="title" class="form-control" value="@Model.form.title" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="permalink">@Translate.Permalink</label>
|
||||||
|
<input type="text" name="permalink" id="permalink" class="form-control" value="@Model.form.permalink" />
|
||||||
|
<!-- // FIXME: hard-coded "http" -->
|
||||||
|
<p class="form-hint"><em>@Translate.startingWith</em> http://@Model.webLog.urlBase/ </p>
|
||||||
|
</div>
|
||||||
|
<!-- // TODO: Markdown / HTML choice -->
|
||||||
|
<div class="form-group">
|
||||||
|
<textarea name="text" id="text" rows="15">@Model.form.text</textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="tags">@Translate.Tags</label>
|
||||||
|
<input type="text" name="tags" id="tags" class="form-control" value="@Model.form.tags" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">@Translate.PostDetails</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label">@Translate.PostStatus</label>
|
||||||
|
<p class="static-control">@Model.post.status</p>
|
||||||
|
</div>
|
||||||
|
@If.isPublished
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label">@Translate.PublishedDate</label>
|
||||||
|
<p class="static-control">@Model.publishedDate<br />@Model.publishedTime</p>
|
||||||
|
</div>
|
||||||
|
@EndIf
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">@Translate.Categories</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body" style="max-height:350px;overflow:scroll;">
|
||||||
|
<!-- // TODO: how to check the ones that are already selected? -->
|
||||||
|
@Each.categories
|
||||||
|
<!-- - var tab = 0
|
||||||
|
while tab < item.indent
|
||||||
|
|
|
||||||
|
- tab++
|
||||||
|
- var attributes = {}
|
||||||
|
if -1 < currentCategories.indexOf(item.category.id)
|
||||||
|
- attributes.checked = 'checked' -->
|
||||||
|
<input type="checkbox" id="category-@Current.Item1" name="category", value="@Current.Item1" />
|
||||||
|
|
||||||
|
<!-- // FIXME: the title should be the category description -->
|
||||||
|
<label for="category-@Current.Item1" title="@Current.Item2">@Current.Item2</label>
|
||||||
|
<br/>
|
||||||
|
@EndEach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
@If.isPublished
|
||||||
|
<input type="hidden" name="publishNow" value="true" />
|
||||||
|
@EndIf
|
||||||
|
@IfNot.isPublished
|
||||||
|
<div>
|
||||||
|
<input type="checkbox" name="publishNow" id="publishNow" value="true" checked="checked" />
|
||||||
|
<label for="publishNow">@Translate.PublishThisPost</label>
|
||||||
|
</div>
|
||||||
|
@EndIf
|
||||||
|
<p>
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="fa fa-floppy-o"></i> @Translate.Save
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
@EndSection
|
||||||
|
|
||||||
|
@Section['Scripts']
|
||||||
|
<script type="text/javascript" src="/content/scripts/tinymce-init.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
/** <![CDATA[ */
|
||||||
|
$(document).ready(function () { $("#title").focus() })
|
||||||
|
/** ]]> */
|
||||||
|
</script>
|
||||||
|
@EndSection
|
@ -8,7 +8,7 @@
|
|||||||
<div class="col-xs-12">
|
<div class="col-xs-12">
|
||||||
<article>
|
<article>
|
||||||
<h1>
|
<h1>
|
||||||
<a href="/@Current.permalink" title="Permanent Link to "@Current.title@quot;">@Current.title</a>
|
<a href="/@Current.permalink" title="@Translate.PermanentLinkTo "@Current.title@quot;">@Current.title</a>
|
||||||
</h1>
|
</h1>
|
||||||
<!-- var pubDate = moment(post.publishedDate) -->
|
<!-- var pubDate = moment(post.publishedDate) -->
|
||||||
<p>
|
<p>
|
||||||
|
Loading…
Reference in New Issue
Block a user