diff --git a/src/MyWebLog.App/App.fs b/src/MyWebLog.App/App.fs deleted file mode 100644 index 96a3a13..0000000 --- a/src/MyWebLog.App/App.fs +++ /dev/null @@ -1,155 +0,0 @@ -module MyWebLog.App - -open MyWebLog -open MyWebLog.Data -open MyWebLog.Data.RethinkDB -open MyWebLog.Entities -open MyWebLog.Logic.WebLog -open MyWebLog.Resources -open Nancy -open Nancy.Authentication.Forms -open Nancy.Bootstrapper -open Nancy.Conventions -open Nancy.Cryptography -open Nancy.Owin -open Nancy.Security -open Nancy.Session.Persistable -//open Nancy.Session.Relational -open Nancy.Session.RethinkDB -open Nancy.TinyIoc -open Nancy.ViewEngines.SuperSimpleViewEngine -open NodaTime -open RethinkDb.Driver.Net -open Suave -open Suave.Owin -open System -open System.IO -open System.Reflection -open System.Security.Claims -open System.Text.RegularExpressions - -/// Establish the configuration for this instance -let cfg = try AppConfig.FromJson (System.IO.File.ReadAllText "config.json") - with ex -> raise <| Exception (Strings.get "ErrBadAppConfig", ex) - -let data = lazy (RethinkMyWebLogData (cfg.DataConfig.Conn, cfg.DataConfig) :> IMyWebLogData) - -/// Support RESX lookup via the @Translate SSVE alias -type TranslateTokenViewEngineMatcher() = - static let regex = Regex ("@Translate\.(?[a-zA-Z0-9-_]+);?", RegexOptions.Compiled) - interface ISuperSimpleViewEngineMatcher with - member this.Invoke (content, model, host) = - let translate (m : Match) = Strings.get m.Groups.["TranslationKey"].Value - regex.Replace(content, translate) - - -/// Handle forms authentication -type MyWebLogUser (claims : Claim seq) = - inherit ClaimsPrincipal (ClaimsIdentity (claims, "forms")) - - new (user : User) = - // TODO: refactor the User.Claims property to produce this, and just pass it as the constructor - let claims = - seq { - yield Claim (ClaimTypes.Name, user.PreferredName) - for claim in user.Claims -> Claim (ClaimTypes.Role, claim) - } - MyWebLogUser claims - -type MyWebLogUserMapper (container : TinyIoCContainer) = - - interface IUserMapper with - member this.GetUserFromIdentifier (identifier, context) = - match context.Request.PersistableSession.GetOrDefault (Keys.User, User.Empty) with - | user when user.Id = string identifier -> upcast MyWebLogUser user - | _ -> null - - -/// Set up the application environment -type MyWebLogBootstrapper() = - inherit DefaultNancyBootstrapper() - - override this.ConfigureRequestContainer (container, context) = - base.ConfigureRequestContainer (container, context) - /// User mapper for forms authentication - container.Register() - |> ignore - - override this.ConfigureConventions (conventions) = - base.ConfigureConventions conventions - conventions.StaticContentsConventions.Add - (StaticContentConventionBuilder.AddDirectory ("admin/content", "views/admin/content")) - // Make theme content available at [theme-name]/ - Directory.EnumerateDirectories (Path.Combine [| "views"; "themes" |]) - |> Seq.map (fun themeDir -> themeDir, Path.Combine [| themeDir; "content" |]) - |> Seq.filter (fun (_, contentDir) -> Directory.Exists contentDir) - |> Seq.iter (fun (themeDir, contentDir) -> - conventions.StaticContentsConventions.Add - (StaticContentConventionBuilder.AddDirectory ((Path.GetFileName themeDir), contentDir))) - - override this.ConfigureApplicationContainer (container) = - base.ConfigureApplicationContainer container - container.Register cfg - |> ignore - data.Force().SetUp () - container.Register (data.Force ()) - |> ignore - // NodaTime - container.Register SystemClock.Instance - |> ignore - // I18N in SSVE - container.Register (fun _ _ -> - Seq.singleton (TranslateTokenViewEngineMatcher () :> ISuperSimpleViewEngineMatcher)) - |> ignore - - override this.ApplicationStartup (container, pipelines) = - base.ApplicationStartup (container, pipelines) - // Forms authentication configuration - let auth = - FormsAuthenticationConfiguration ( - CryptographyConfiguration = - CryptographyConfiguration ( - AesEncryptionProvider (PassphraseKeyGenerator (cfg.AuthEncryptionPassphrase, cfg.AuthSalt)), - DefaultHmacProvider (PassphraseKeyGenerator (cfg.AuthHmacPassphrase, cfg.AuthSalt))), - RedirectUrl = "~/user/log-on", - UserMapper = container.Resolve ()) - FormsAuthentication.Enable (pipelines, auth) - // CSRF - Csrf.Enable pipelines - // Sessions - let sessions = RethinkDBSessionConfiguration cfg.DataConfig.Conn - sessions.Database <- cfg.DataConfig.Database - //let sessions = RelationalSessionConfiguration(ConfigurationManager.ConnectionStrings.["SessionStore"].ConnectionString) - PersistableSessions.Enable (pipelines, sessions) - () - - override this.Configure (environment) = - base.Configure environment - environment.Tracing (true, true) - - -let version = - let v = typeof.GetTypeInfo().Assembly.GetName().Version - match v.Build with - | 0 -> match v.Minor with 0 -> string v.Major | _ -> sprintf "%d.%d" v.Major v.Minor - | _ -> sprintf "%d.%d.%d" v.Major v.Minor v.Build - |> sprintf "v%s" - -/// Set up the request environment -type RequestEnvironment() = - interface IRequestStartup with - member this.Initialize (pipelines, context) = - let establishEnv (ctx : NancyContext) = - ctx.Items.[Keys.RequestStart] <- DateTime.Now.Ticks - match tryFindWebLogByUrlBase (data.Force ()) ctx.Request.Url.HostName with - | Some webLog -> ctx.Items.[Keys.WebLog] <- webLog - | None -> // TODO: redirect to domain set up page - Exception (sprintf "%s %s" ctx.Request.Url.HostName (Strings.get "ErrNotConfigured")) - |> raise - ctx.Items.[Keys.Version] <- version - null - pipelines.BeforeRequest.AddItemToStartOfPipeline establishEnv - -let Run () = - OwinApp.ofMidFunc "/" (NancyMiddleware.UseNancy (NancyOptions (Bootstrapper = new MyWebLogBootstrapper ()))) - |> startWebServer defaultConfig diff --git a/src/MyWebLog.App/AppConfig.fs b/src/MyWebLog.App/AppConfig.fs deleted file mode 100644 index 915a18e..0000000 --- a/src/MyWebLog.App/AppConfig.fs +++ /dev/null @@ -1,33 +0,0 @@ -namespace MyWebLog - -open MyWebLog.Data.RethinkDB -open Newtonsoft.Json -open System.Text - -/// Configuration for this myWebLog instance -type AppConfig = - { /// The text from which to derive salt to use for passwords - [] - PasswordSaltString : string - /// The text from which to derive salt to use for forms authentication - [] - AuthSaltString : string - /// The encryption passphrase to use for forms authentication - [] - AuthEncryptionPassphrase : string - /// The HMAC passphrase to use for forms authentication - [] - AuthHmacPassphrase : string - /// The data configuration - [] - DataConfig : DataConfig } - with - /// The salt to use for passwords - member this.PasswordSalt = Encoding.UTF8.GetBytes this.PasswordSaltString - /// The salt to use for forms authentication - member this.AuthSalt = Encoding.UTF8.GetBytes this.AuthSaltString - - /// Deserialize the configuration from the JSON file - static member FromJson json = - let cfg = JsonConvert.DeserializeObject json - { cfg with DataConfig = DataConfig.Connect cfg.DataConfig } \ No newline at end of file diff --git a/src/MyWebLog.App/AssemblyInfo.fs b/src/MyWebLog.App/AssemblyInfo.fs deleted file mode 100644 index 0fb972b..0000000 --- a/src/MyWebLog.App/AssemblyInfo.fs +++ /dev/null @@ -1,21 +0,0 @@ -namespace MyWebLog.AssemblyInfo - -open System.Reflection -open System.Runtime.CompilerServices -open System.Runtime.InteropServices - -[] -[] -[] -[] -[] -[] -[] -[] -[] -[] -[] -[] - -do - () \ No newline at end of file diff --git a/src/MyWebLog.App/Data/Category.fs b/src/MyWebLog.App/Data/Category.fs deleted file mode 100644 index 94c13cc..0000000 --- a/src/MyWebLog.App/Data/Category.fs +++ /dev/null @@ -1,140 +0,0 @@ -module MyWebLog.Data.RethinkDB.Category - -open MyWebLog.Entities -open RethinkDb.Driver.Ast - -let private r = RethinkDb.Driver.RethinkDB.R - -/// Get all categories for a web log -let getAllCategories conn (webLogId : string) = - async { - return! r.Table(Table.Category) - .GetAll(webLogId).OptArg("index", "WebLogId") - .OrderBy("Name") - .RunResultAsync conn - } - |> Async.RunSynchronously - -/// Get a specific category by its Id -let tryFindCategory conn webLogId catId : Category option = - async { - let! c = - r.Table(Table.Category) - .Get(catId) - .RunResultAsync conn - return - match box c with - | null -> None - | catt -> - let cat : Category = unbox catt - match cat.WebLogId = webLogId with true -> Some cat | _ -> None - } - |> Async.RunSynchronously - -/// Add a category -let addCategory conn (cat : Category) = - async { - do! r.Table(Table.Category) - .Insert(cat) - .RunResultAsync conn - } - |> Async.RunSynchronously - -type CategoryUpdateRecord = - { Name : string - Slug : string - Description : string option - ParentId : string option - } -/// Update a category -let updateCategory conn (cat : Category) = - match tryFindCategory conn cat.WebLogId cat.Id with - | Some _ -> - async { - do! r.Table(Table.Category) - .Get(cat.Id) - .Update( - { CategoryUpdateRecord.Name = cat.Name - Slug = cat.Slug - Description = cat.Description - ParentId = cat.ParentId - }) - .RunResultAsync conn - } - |> Async.RunSynchronously - | _ -> () - -/// Update a category's children -let updateChildren conn webLogId parentId (children : string list) = - match tryFindCategory conn webLogId parentId with - | Some _ -> - async { - do! r.Table(Table.Category) - .Get(parentId) - .Update(dict [ "Children", children ]) - .RunResultAsync conn - } - |> Async.RunSynchronously - | _ -> () - -/// Delete a category -let deleteCategory conn (cat : Category) = - async { - // Remove the category from its parent - match cat.ParentId with - | Some parentId -> - match tryFindCategory conn cat.WebLogId parentId with - | Some parent -> parent.Children - |> List.filter (fun childId -> childId <> cat.Id) - |> updateChildren conn cat.WebLogId parentId - | _ -> () - | _ -> () - // Move this category's children to its parent - cat.Children - |> List.map (fun childId -> - match tryFindCategory conn cat.WebLogId childId with - | Some _ -> - async { - do! r.Table(Table.Category) - .Get(childId) - .Update(dict [ "ParentId", cat.ParentId ]) - .RunResultAsync conn - } - |> Some - | _ -> None) - |> List.filter Option.isSome - |> List.map Option.get - |> List.iter Async.RunSynchronously - // Remove the category from posts where it is assigned - let! posts = - r.Table(Table.Post) - .GetAll(cat.WebLogId).OptArg("index", "WebLogId") - .Filter(ReqlFunction1 (fun p -> upcast p.["CategoryIds"].Contains cat.Id)) - .RunResultAsync conn - |> Async.AwaitTask - posts - |> List.map (fun post -> - async { - do! r.Table(Table.Post) - .Get(post.Id) - .Update(dict [ "CategoryIds", post.CategoryIds |> List.filter (fun c -> c <> cat.Id) ]) - .RunResultAsync conn - }) - |> List.iter Async.RunSynchronously - // Now, delete the category - do! r.Table(Table.Category) - .Get(cat.Id) - .Delete() - .RunResultAsync conn - } - |> Async.RunSynchronously - -/// Get a category by its slug -let tryFindCategoryBySlug conn (webLogId : string) (slug : string) = - async { - let! cat = r.Table(Table.Category) - .GetAll(r.Array (webLogId, slug)).OptArg("index", "Slug") - .RunResultAsync conn - return cat |> List.tryHead - } - |> Async.RunSynchronously diff --git a/src/MyWebLog.App/Data/DataConfig.fs b/src/MyWebLog.App/Data/DataConfig.fs deleted file mode 100644 index 0d993c2..0000000 --- a/src/MyWebLog.App/Data/DataConfig.fs +++ /dev/null @@ -1,43 +0,0 @@ -namespace MyWebLog.Data.RethinkDB - -open RethinkDb.Driver -open RethinkDb.Driver.Net -open Newtonsoft.Json - -/// Data configuration -type DataConfig = - { /// The hostname for the RethinkDB server - [] - Hostname : string - /// The port for the RethinkDB server - [] - Port : int - /// The authorization key to use when connecting to the server - [] - AuthKey : string - /// How long an attempt to connect to the server should wait before giving up - [] - Timeout : int - /// The name of the default database to use on the connection - [] - Database : string - /// A connection to the RethinkDB server using the configuration in this object - [] - Conn : IConnection } -with - /// Use RethinkDB defaults for non-provided options, and connect to the server - static member Connect config = - let host cfg = match cfg.Hostname with null -> { cfg with Hostname = RethinkDBConstants.DefaultHostname } | _ -> cfg - let port cfg = match cfg.Port with 0 -> { cfg with Port = RethinkDBConstants.DefaultPort } | _ -> cfg - let auth cfg = match cfg.AuthKey with null -> { cfg with AuthKey = RethinkDBConstants.DefaultAuthkey } | _ -> cfg - let timeout cfg = match cfg.Timeout with 0 -> { cfg with Timeout = RethinkDBConstants.DefaultTimeout } | _ -> cfg - let db cfg = match cfg.Database with null -> { cfg with Database = RethinkDBConstants.DefaultDbName } | _ -> cfg - let connect cfg = - { cfg with Conn = RethinkDB.R.Connection() - .Hostname(cfg.Hostname) - .Port(cfg.Port) - .AuthKey(cfg.AuthKey) - .Db(cfg.Database) - .Timeout(cfg.Timeout) - .Connect () } - (host >> port >> auth >> timeout >> db >> connect) config diff --git a/src/MyWebLog.App/Data/Extensions.fs b/src/MyWebLog.App/Data/Extensions.fs deleted file mode 100644 index 82c7645..0000000 --- a/src/MyWebLog.App/Data/Extensions.fs +++ /dev/null @@ -1,16 +0,0 @@ -[] -module MyWebLog.Data.RethinkDB.Extensions - -open System.Threading.Tasks - -// H/T: Suave -type AsyncBuilder with - /// An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on - /// a standard .NET task - member x.Bind(t : Task<'T>, f:'T -> Async<'R>) : Async<'R> = async.Bind(Async.AwaitTask t, f) - - /// An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on - /// a standard .NET task which does not commpute a value - member x.Bind(t : Task, f : unit -> Async<'R>) : Async<'R> = async.Bind(Async.AwaitTask t, f) - - member x.ReturnFrom(t : Task<'T>) = Async.AwaitTask t diff --git a/src/MyWebLog.App/Data/Page.fs b/src/MyWebLog.App/Data/Page.fs deleted file mode 100644 index 34db086..0000000 --- a/src/MyWebLog.App/Data/Page.fs +++ /dev/null @@ -1,98 +0,0 @@ -module MyWebLog.Data.RethinkDB.Page - -open MyWebLog.Entities -open RethinkDb.Driver.Ast - -let private r = RethinkDb.Driver.RethinkDB.R - -/// Try to find a page by its Id, optionally including revisions -let tryFindPageById conn webLogId (pageId : string) includeRevs = - async { - let q = - r.Table(Table.Page) - .Get pageId - let! thePage = - match includeRevs with - | true -> q.RunResultAsync conn - | _ -> q.Without("Revisions").RunResultAsync conn - return - match box thePage with - | null -> None - | page -> - let pg : Page = unbox page - match pg.WebLogId = webLogId with true -> Some pg | _ -> None - } - |> Async.RunSynchronously - -/// Find a page by its permalink -let tryFindPageByPermalink conn (webLogId : string) (permalink : string) = - async { - let! pg = - r.Table(Table.Page) - .GetAll(r.Array (webLogId, permalink)).OptArg("index", "Permalink") - .Without("Revisions") - .RunResultAsync conn - return List.tryHead pg - } - |> Async.RunSynchronously - -/// Get a list of all pages (excludes page text and revisions) -let findAllPages conn (webLogId : string) = - async { - return! - r.Table(Table.Page) - .GetAll(webLogId).OptArg("index", "WebLogId") - .OrderBy("Title") - .Without("Text", "Revisions") - .RunResultAsync conn - } - |> Async.RunSynchronously - -/// Add a page -let addPage conn (page : Page) = - async { - do! r.Table(Table.Page) - .Insert(page) - .RunResultAsync conn - } - |> (Async.RunSynchronously >> ignore) - -type PageUpdateRecord = - { Title : string - Permalink : string - PublishedOn : int64 - UpdatedOn : int64 - ShowInPageList : bool - Text : string - Revisions : Revision list } -/// Update a page -let updatePage conn (page : Page) = - match tryFindPageById conn page.WebLogId page.Id false with - | Some _ -> - async { - do! r.Table(Table.Page) - .Get(page.Id) - .Update({ PageUpdateRecord.Title = page.Title - Permalink = page.Permalink - PublishedOn = page.PublishedOn - UpdatedOn = page.UpdatedOn - ShowInPageList = page.ShowInPageList - Text = page.Text - Revisions = page.Revisions }) - .RunResultAsync conn - } - |> (Async.RunSynchronously >> ignore) - | _ -> () - -/// Delete a page -let deletePage conn webLogId pageId = - match tryFindPageById conn webLogId pageId false with - | Some _ -> - async { - do! r.Table(Table.Page) - .Get(pageId) - .Delete() - .RunResultAsync conn - } - |> (Async.RunSynchronously >> ignore) - | _ -> () diff --git a/src/MyWebLog.App/Data/Post.fs b/src/MyWebLog.App/Data/Post.fs deleted file mode 100644 index 4d4bb14..0000000 --- a/src/MyWebLog.App/Data/Post.fs +++ /dev/null @@ -1,225 +0,0 @@ -module MyWebLog.Data.RethinkDB.Post - -open MyWebLog.Entities -open RethinkDb.Driver.Ast - -let private r = RethinkDb.Driver.RethinkDB.R - -/// Shorthand to select all published posts for a web log -let private publishedPosts (webLogId : string) = - r.Table(Table.Post) - .GetAll(r.Array (webLogId, PostStatus.Published)).OptArg("index", "WebLogAndStatus") - .Without("Revisions") - // This allows us to count comments without retrieving them all - .Merge(ReqlFunction1 (fun p -> - upcast r.HashMap( - "Comments", r.Table(Table.Comment) - .GetAll(p.["id"]).OptArg("index", "PostId") - .Pluck("id") - .CoerceTo("array")))) - - -/// Shorthand to sort posts by published date, slice for the given page, and return a list -let private toPostList conn pageNbr nbrPerPage (filter : ReqlExpr) = - async { - return! - filter - .OrderBy(r.Desc "PublishedOn") - .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) - .RunResultAsync conn - } - |> Async.RunSynchronously - -/// Shorthand to get a newer or older post -let private adjacentPost conn (post : Post) (theFilter : ReqlExpr -> obj) (sort : obj) = - async { - let! post = - (publishedPosts post.WebLogId) - .Filter(theFilter) - .OrderBy(sort) - .Limit(1) - .RunResultAsync conn - return List.tryHead post - } - |> Async.RunSynchronously - -/// Find a newer post -let private newerPost conn post theFilter = adjacentPost conn post theFilter <| r.Asc "PublishedOn" - -/// Find an older post -let private olderPost conn post theFilter = adjacentPost conn post theFilter <| r.Desc "PublishedOn" - -/// Get a page of published posts -let findPageOfPublishedPosts conn webLogId pageNbr nbrPerPage = - publishedPosts webLogId - |> toPostList conn pageNbr nbrPerPage - -/// Get a page of published posts assigned to a given category -let findPageOfCategorizedPosts conn webLogId (categoryId : string) pageNbr nbrPerPage = - (publishedPosts webLogId) - .Filter(ReqlFunction1 (fun p -> upcast p.["CategoryIds"].Contains categoryId)) - |> toPostList conn pageNbr nbrPerPage - -/// Get a page of published posts tagged with a given tag -let findPageOfTaggedPosts conn webLogId (tag : string) pageNbr nbrPerPage = - (publishedPosts webLogId) - .Filter(ReqlFunction1 (fun p -> upcast p.["Tags"].Contains tag)) - |> toPostList conn pageNbr nbrPerPage - -/// 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) - -/// Try to get the next newest post assigned to the given category -let tryFindNewerCategorizedPost conn (categoryId : string) post = - newerPost conn post (fun p -> upcast p.["PublishedOn"].Gt(post.PublishedOn) - .And(p.["CategoryIds"].Contains categoryId)) - -/// Try to get the next newest tagged post from the given tagged post -let tryFindNewerTaggedPost conn (tag : string) post = - 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 -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 -let tryFindOlderCategorizedPost conn (categoryId : string) post = - olderPost conn post (fun p -> upcast p.["PublishedOn"].Lt(post.PublishedOn) - .And(p.["CategoryIds"].Contains categoryId)) - -/// Try to get the next oldest tagged post from the given tagged post -let tryFindOlderTaggedPost conn (tag : string) post = - 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 -let findPageOfAllPosts conn (webLogId : string) pageNbr nbrPerPage = - // FIXME: sort unpublished posts by their last updated date - async { - // .orderBy(r.desc(r.branch(r.row("Status").eq("Published"), r.row("PublishedOn"), r.row("UpdatedOn")))) - return! - r.Table(Table.Post) - .GetAll(webLogId).OptArg("index", "WebLogId") - .OrderBy(r.Desc (ReqlFunction1 (fun p -> - upcast r.Branch (p.["Status"].Eq("Published"), p.["PublishedOn"], p.["UpdatedOn"])))) - .Slice((pageNbr - 1) * nbrPerPage, pageNbr * nbrPerPage) - .RunResultAsync conn - } - |> Async.RunSynchronously - -/// Try to find a post by its Id and web log Id -let tryFindPost conn webLogId postId : Post option = - async { - let! p = - r.Table(Table.Post) - .Get(postId) - .RunAtomAsync conn - return - match box p with - | null -> None - | pst -> - let post : Post = unbox pst - match post.WebLogId = webLogId with true -> Some post | _ -> None - } - |> Async.RunSynchronously - -/// Try to find a post by its permalink -let tryFindPostByPermalink conn webLogId permalink = - async { - let! post = - r.Table(Table.Post) - .GetAll(r.Array (webLogId, permalink)).OptArg("index", "Permalink") - .Filter(ReqlFunction1 (fun p -> upcast p.["Status"].Eq PostStatus.Published)) - .Without("Revisions") - .Merge(ReqlFunction1 (fun p -> - upcast r.HashMap( - "Categories", r.Table(Table.Category) - .GetAll(r.Args p.["CategoryIds"]) - .Without("Children") - .OrderBy("Name") - .CoerceTo("array")).With( - "Comments", r.Table(Table.Comment) - .GetAll(p.["id"]).OptArg("index", "PostId") - .OrderBy("PostedOn") - .CoerceTo("array")))) - .RunResultAsync conn - return List.tryHead post - } - |> Async.RunSynchronously - -/// Try to find a post by its prior permalink -let tryFindPostByPriorPermalink conn (webLogId : string) (permalink : string) = - async { - let! post = - r.Table(Table.Post) - .GetAll(webLogId).OptArg("index", "WebLogId") - .Filter(ReqlFunction1 (fun p -> - upcast p.["PriorPermalinks"].Contains(permalink).And(p.["Status"].Eq PostStatus.Published))) - .Without("Revisions") - .RunResultAsync conn - return List.tryHead post - } - |> Async.RunSynchronously - -/// Get a set of posts for RSS -let findFeedPosts conn webLogId nbr : (Post * User option) list = - let tryFindUser userId = - async { - let! u = - r.Table(Table.User) - .Get(userId) - .RunAtomAsync conn - return match box u with null -> None | user -> Some <| unbox user - } - |> Async.RunSynchronously - (publishedPosts webLogId) - .Merge(ReqlFunction1 (fun post -> - upcast r.HashMap( - "Categories", r.Table(Table.Category) - .GetAll(r.Args post.["CategoryIds"]) - .OrderBy("Name") - .Pluck("id", "Name") - .CoerceTo("array")))) - |> toPostList conn 1 nbr - |> List.map (fun post -> post, tryFindUser post.AuthorId) - -/// Add a post -let addPost conn post = - async { - do! r.Table(Table.Post) - .Insert(post) - .RunResultAsync conn - } - |> (Async.RunSynchronously >> ignore) - -/// Update a post -let updatePost conn (post : Post) = - async { - do! r.Table(Table.Post) - .Get(post.Id) - .Replace( { post with Categories = [] - Comments = [] } ) - .RunResultAsync conn - } - |> (Async.RunSynchronously >> ignore) - -/// Save a post -let savePost conn (post : Post) = - match post.Id with - | "new" -> - let newPost = { post with Id = string <| System.Guid.NewGuid() } - async { - do! r.Table(Table.Post) - .Insert(newPost) - .RunResultAsync conn - } - |> Async.RunSynchronously - newPost.Id - | _ -> - async { - do! r.Table(Table.Post) - .Get(post.Id) - .Replace( { post with Categories = [] - Comments = [] } ) - .RunResultAsync conn - } - |> Async.RunSynchronously - post.Id diff --git a/src/MyWebLog.App/Data/RethinkMyWebLogData.fs b/src/MyWebLog.App/Data/RethinkMyWebLogData.fs deleted file mode 100644 index a7cf789..0000000 --- a/src/MyWebLog.App/Data/RethinkMyWebLogData.fs +++ /dev/null @@ -1,48 +0,0 @@ -namespace MyWebLog.Data.RethinkDB - -open MyWebLog.Data -open RethinkDb.Driver.Net - -/// RethinkDB implementation of myWebLog data persistence -type RethinkMyWebLogData(conn : IConnection, cfg : DataConfig) = - interface IMyWebLogData with - member __.SetUp = fun () -> SetUp.startUpCheck cfg - - member __.AllCategories = Category.getAllCategories conn - member __.CategoryById = Category.tryFindCategory conn - member __.CategoryBySlug = Category.tryFindCategoryBySlug conn - member __.AddCategory = Category.addCategory conn - member __.UpdateCategory = Category.updateCategory conn - member __.UpdateChildren = Category.updateChildren conn - member __.DeleteCategory = Category.deleteCategory conn - - member __.PageById = Page.tryFindPageById conn - member __.PageByPermalink = Page.tryFindPageByPermalink conn - member __.AllPages = Page.findAllPages conn - member __.AddPage = Page.addPage conn - member __.UpdatePage = Page.updatePage conn - member __.DeletePage = Page.deletePage conn - - member __.PageOfPublishedPosts = Post.findPageOfPublishedPosts conn - member __.PageOfCategorizedPosts = Post.findPageOfCategorizedPosts conn - member __.PageOfTaggedPosts = Post.findPageOfTaggedPosts conn - member __.NewerPost = Post.tryFindNewerPost conn - member __.NewerCategorizedPost = Post.tryFindNewerCategorizedPost conn - member __.NewerTaggedPost = Post.tryFindNewerTaggedPost conn - member __.OlderPost = Post.tryFindOlderPost conn - member __.OlderCategorizedPost = Post.tryFindOlderCategorizedPost conn - member __.OlderTaggedPost = Post.tryFindOlderTaggedPost conn - member __.PageOfAllPosts = Post.findPageOfAllPosts conn - member __.PostById = Post.tryFindPost conn - member __.PostByPermalink = Post.tryFindPostByPermalink conn - member __.PostByPriorPermalink = Post.tryFindPostByPriorPermalink conn - member __.FeedPosts = Post.findFeedPosts conn - member __.AddPost = Post.addPost conn - member __.UpdatePost = Post.updatePost conn - - member __.LogOn = User.tryUserLogOn conn - member __.SetUserPassword = User.setUserPassword conn - - member __.WebLogByUrlBase = WebLog.tryFindWebLogByUrlBase conn - member __.DashboardCounts = WebLog.findDashboardCounts conn - \ No newline at end of file diff --git a/src/MyWebLog.App/Data/SetUp.fs b/src/MyWebLog.App/Data/SetUp.fs deleted file mode 100644 index bd0341a..0000000 --- a/src/MyWebLog.App/Data/SetUp.fs +++ /dev/null @@ -1,100 +0,0 @@ -module MyWebLog.Data.RethinkDB.SetUp - -open RethinkDb.Driver.Ast -open System - -let private r = RethinkDb.Driver.RethinkDB.R -let private logStep step = Console.Out.WriteLine (sprintf "[myWebLog] %s" step) -let private logStepStart text = Console.Out.Write (sprintf "[myWebLog] %s..." text) -let private logStepDone () = Console.Out.WriteLine (" done.") - -/// Ensure the myWebLog database exists -let private checkDatabase (cfg : DataConfig) = - async { - logStep "|> Checking database" - let! dbs = r.DbList().RunResultAsync cfg.Conn - match List.contains cfg.Database dbs with - | true -> () - | _ -> logStepStart (sprintf " %s database not found - creating" cfg.Database) - do! r.DbCreate(cfg.Database).RunResultAsync cfg.Conn - logStepDone () - } - - -/// Ensure all required tables exist -let private checkTables cfg = - async { - logStep "|> Checking tables" - let! tables = r.Db(cfg.Database).TableList().RunResultAsync cfg.Conn - [ Table.Category; Table.Comment; Table.Page; Table.Post; Table.User; Table.WebLog ] - |> List.filter (fun tbl -> not (List.contains tbl tables)) - |> List.iter (fun tbl -> logStepStart (sprintf " Creating table %s" tbl) - async { do! (r.TableCreate tbl).RunResultAsync cfg.Conn } |> Async.RunSynchronously - logStepDone ()) - } - -/// Shorthand to get the table -let private tbl cfg table = r.Db(cfg.Database).Table table - -/// Create the given index -let private createIndex cfg table (index : string * (ReqlExpr -> obj) option) = - async { - let idxName, idxFunc = index - logStepStart (sprintf """ Creating index "%s" on table %s""" idxName table) - do! (match idxFunc with - | Some f -> (tbl cfg table).IndexCreate(idxName, f) - | None -> (tbl cfg table).IndexCreate(idxName)) - .RunResultAsync cfg.Conn - logStepDone () - } - -/// Ensure that the given indexes exist, and create them if required -let private ensureIndexes cfg (indexes : (string * (string * (ReqlExpr -> obj) option) list) list) = - let ensureForTable (tblName, idxs) = - async { - let! idx = (tbl cfg tblName).IndexList().RunResultAsync cfg.Conn - idxs - |> List.filter (fun (idxName, _) -> not (List.contains idxName idx)) - |> List.map (fun index -> createIndex cfg tblName index) - |> List.iter Async.RunSynchronously - } - |> Async.RunSynchronously - indexes - |> List.iter ensureForTable - -/// Create an index on web log Id and the given field -let private webLogField (name : string) : (ReqlExpr -> obj) option = - Some <| fun row -> upcast r.Array(row.["WebLogId"], row.[name]) - -/// Ensure all the required indexes exist -let private checkIndexes cfg = - logStep "|> Checking indexes" - [ Table.Category, [ "WebLogId", None - "Slug", webLogField "Slug" - ] - Table.Comment, [ "PostId", None - ] - Table.Page, [ "WebLogId", None - "Permalink", webLogField "Permalink" - ] - Table.Post, [ "WebLogId", None - "WebLogAndStatus", webLogField "Status" - "Permalink", webLogField "Permalink" - ] - Table.User, [ "UserName", None - ] - Table.WebLog, [ "UrlBase", None - ] - ] - |> ensureIndexes cfg - -/// Start up checks to ensure the database, tables, and indexes exist -let startUpCheck cfg = - async { - logStep "Database Start Up Checks Starting" - do! checkDatabase cfg - do! checkTables cfg - checkIndexes cfg - logStep "Database Start Up Checks Complete" - } - |> Async.RunSynchronously diff --git a/src/MyWebLog.App/Data/Table.fs b/src/MyWebLog.App/Data/Table.fs deleted file mode 100644 index 8a84652..0000000 --- a/src/MyWebLog.App/Data/Table.fs +++ /dev/null @@ -1,21 +0,0 @@ -/// Constants for tables used in myWebLog -[] -module MyWebLog.Data.RethinkDB.Table - -/// The Category table -let Category = "Category" - -/// The Comment table -let Comment = "Comment" - -/// The Page table -let Page = "Page" - -/// The Post table -let Post = "Post" - -/// The WebLog table -let WebLog = "WebLog" - -/// The User table -let User = "User" \ No newline at end of file diff --git a/src/MyWebLog.App/Data/User.fs b/src/MyWebLog.App/Data/User.fs deleted file mode 100644 index 0e4cecf..0000000 --- a/src/MyWebLog.App/Data/User.fs +++ /dev/null @@ -1,31 +0,0 @@ -module MyWebLog.Data.RethinkDB.User - -open MyWebLog.Entities -open RethinkDb.Driver.Ast - -let private r = RethinkDb.Driver.RethinkDB.R - -/// Log on a user -// NOTE: The significant length of a RethinkDB index is 238 - [PK size]; as we're storing 1,024 characters of password, -// including it in an index does not get any performance gain, and would unnecessarily bloat the index. See -// http://rethinkdb.com/docs/secondary-indexes/java/ for more information. -let tryUserLogOn conn (email : string) (passwordHash : string) = - async { - let! user = - r.Table(Table.User) - .GetAll(email).OptArg("index", "UserName") - .Filter(ReqlFunction1 (fun u -> upcast u.["PasswordHash"].Eq passwordHash)) - .RunResultAsync conn - return user |> List.tryHead - } - |> Async.RunSynchronously - -/// Set a user's password -let setUserPassword conn (email : string) (passwordHash : string) = - async { - do! r.Table(Table.User) - .GetAll(email).OptArg("index", "UserName") - .Update(dict [ "PasswordHash", passwordHash ]) - .RunResultAsync conn - } - |> Async.RunSynchronously \ No newline at end of file diff --git a/src/MyWebLog.App/Data/WebLog.fs b/src/MyWebLog.App/Data/WebLog.fs deleted file mode 100644 index 6d236f8..0000000 --- a/src/MyWebLog.App/Data/WebLog.fs +++ /dev/null @@ -1,39 +0,0 @@ -module MyWebLog.Data.RethinkDB.WebLog - -open MyWebLog.Entities -open RethinkDb.Driver.Ast - -let private r = RethinkDb.Driver.RethinkDB.R - -/// Detemine the web log by the URL base -let tryFindWebLogByUrlBase conn (urlBase : string) = - async { - let! cursor = - r.Table(Table.WebLog) - .GetAll(urlBase).OptArg("index", "UrlBase") - .Merge(ReqlFunction1 (fun w -> - upcast r.HashMap( - "PageList", r.Table(Table.Page) - .GetAll(w.G("id")).OptArg("index", "WebLogId") - .Filter(ReqlFunction1 (fun pg -> upcast pg.["ShowInPageList"].Eq true)) - .OrderBy("Title") - .Pluck("Title", "Permalink") - .CoerceTo("array")))) - .RunCursorAsync conn - return cursor |> Seq.tryHead - } - |> Async.RunSynchronously - -/// Get counts for the admin dashboard -let findDashboardCounts conn (webLogId : string) = - async { - return! - r.Expr( - r.HashMap( - "Pages", r.Table(Table.Page ).GetAll(webLogId).OptArg("index", "WebLogId").Count()).With( - "Posts", r.Table(Table.Post ).GetAll(webLogId).OptArg("index", "WebLogId").Count()).With( - "Categories", r.Table(Table.Category).GetAll(webLogId).OptArg("index", "WebLogId").Count())) - .RunResultAsync conn - } - |> Async.RunSynchronously - \ No newline at end of file diff --git a/src/MyWebLog.App/Entities/Entities.fs b/src/MyWebLog.App/Entities/Entities.fs deleted file mode 100644 index b35a111..0000000 --- a/src/MyWebLog.App/Entities/Entities.fs +++ /dev/null @@ -1,301 +0,0 @@ -namespace MyWebLog.Entities - -open Newtonsoft.Json - -// --- Constants --- - -/// Constants to use for revision source language -[] -module RevisionSource = - [] - let Markdown = "markdown" - [] - let HTML = "html" - -/// Constants to use for authorization levels -[] -module AuthorizationLevel = - [] - let Administrator = "Administrator" - [] - let User = "User" - -/// Constants to use for post statuses -[] -module PostStatus = - [] - let Draft = "Draft" - [] - let Published = "Published" - -/// Constants to use for comment statuses -[] -module CommentStatus = - [] - let Approved = "Approved" - [] - let Pending = "Pending" - [] - let Spam = "Spam" - -// --- Entities --- - -/// A revision of a post or page -type Revision = - { /// The instant which this revision was saved - AsOf : int64 - /// The source language - SourceType : string - /// The text - Text : string } -with - /// An empty revision - static member Empty = - { AsOf = int64 0 - SourceType = RevisionSource.HTML - Text = "" } - -/// A page with static content -type Page = - { /// The Id - [] - Id : string - /// The Id of the web log to which this page belongs - WebLogId : string - /// The Id of the author of this page - AuthorId : string - /// The title of the page - Title : string - /// The link at which this page is displayed - Permalink : string - /// The instant this page was published - PublishedOn : int64 - /// The instant this page was last updated - UpdatedOn : int64 - /// Whether this page shows as part of the web log's navigation - ShowInPageList : bool - /// The current text of the page - Text : string - /// Revisions of this page - Revisions : Revision list } -with - static member Empty = - { Id = "" - WebLogId = "" - AuthorId = "" - Title = "" - Permalink = "" - PublishedOn = int64 0 - UpdatedOn = int64 0 - ShowInPageList = false - Text = "" - Revisions = [] - } - - -/// An entry in the list of pages displayed as part of the web log (derived via query) -type PageListEntry = - { Permalink : string - Title : string } - -/// A web log -type WebLog = - { /// The Id - [] - Id : string - /// The name - Name : string - /// The subtitle - Subtitle : string option - /// The default page ("posts" or a page Id) - DefaultPage : string - /// The path of the theme (within /views/themes) - ThemePath : string - /// The URL base - UrlBase : string - /// The time zone in which dates/times should be displayed - TimeZone : string - /// A list of pages to be rendered as part of the site navigation (not stored) - PageList : PageListEntry list } -with - /// An empty web log - static member Empty = - { Id = "" - Name = "" - Subtitle = None - DefaultPage = "" - ThemePath = "default" - UrlBase = "" - TimeZone = "America/New_York" - PageList = [] } - - -/// An authorization between a user and a web log -type Authorization = - { /// The Id of the web log to which this authorization grants access - WebLogId : string - /// The level of access granted by this authorization - Level : string } - - -/// A user of myWebLog -type User = - { /// The Id - [] - Id : string - /// The user name (e-mail address) - UserName : string - /// The first name - FirstName : string - /// The last name - LastName : string - /// The user's preferred name - PreferredName : string - /// The hash of the user's password - PasswordHash : string - /// The URL of the user's personal site - Url : string option - /// The user's authorizations - Authorizations : Authorization list } -with - /// An empty user - static member Empty = - { Id = "" - UserName = "" - FirstName = "" - LastName = "" - PreferredName = "" - PasswordHash = "" - Url = None - Authorizations = [] } - - /// Claims for this user - [] - member this.Claims = this.Authorizations - |> List.map (fun auth -> sprintf "%s|%s" auth.WebLogId auth.Level) - - -/// A category to which posts may be assigned -type Category = - { /// The Id - [] - Id : string - /// The Id of the web log to which this category belongs - WebLogId : string - /// The displayed name - Name : string - /// The slug (used in category URLs) - Slug : string - /// A longer description of the category - Description : string option - /// The parent Id of this category (if a subcategory) - ParentId : string option - /// The categories for which this category is the parent - Children : string list } -with - /// An empty category - static member Empty = - { Id = "new" - WebLogId = "" - Name = "" - Slug = "" - Description = None - ParentId = None - Children = [] } - - -/// A comment (applies to a post) -type Comment = - { /// The Id - [] - Id : string - /// The Id of the post to which this comment applies - PostId : string - /// The Id of the comment to which this comment is a reply - InReplyToId : string option - /// The name of the commentor - Name : string - /// The e-mail address of the commentor - Email : string - /// The URL of the commentor's personal website - Url : string option - /// The status of the comment - Status : string - /// The instant the comment was posted - PostedOn : int64 - /// The text of the comment - Text : string } -with - static member Empty = - { Id = "" - PostId = "" - InReplyToId = None - Name = "" - Email = "" - Url = None - Status = CommentStatus.Pending - PostedOn = int64 0 - Text = "" } - - -/// A post -type Post = - { /// The Id - [] - Id : string - /// The Id of the web log to which this post belongs - WebLogId : string - /// The Id of the author of this post - AuthorId : string - /// The status - Status : string - /// The title - Title : string - /// The link at which the post resides - Permalink : string - /// The instant on which the post was originally published - PublishedOn : int64 - /// The instant on which the post was last updated - UpdatedOn : int64 - /// The text of the post - Text : string - /// The Ids of the categories to which this is assigned - CategoryIds : string list - /// The tags for the post - Tags : string list - /// The permalinks at which this post may have once resided - PriorPermalinks : string list - /// Revisions of this post - Revisions : Revision list - /// The categories to which this is assigned (not stored in database) - Categories : Category list - /// The comments (not stored in database) - Comments : Comment list } -with - static member Empty = - { Id = "new" - WebLogId = "" - AuthorId = "" - Status = PostStatus.Draft - Title = "" - Permalink = "" - PublishedOn = int64 0 - UpdatedOn = int64 0 - Text = "" - CategoryIds = [] - Tags = [] - PriorPermalinks = [] - Revisions = [] - Categories = [] - Comments = [] } - -// --- UI Support --- - -/// Counts of items displayed on the admin dashboard -type DashboardCounts = - { /// The number of pages for the web log - Pages : int - /// The number of pages for the web log - Posts : int - /// The number of categories for the web log - Categories : int } diff --git a/src/MyWebLog.App/Entities/IMyWebLogData.fs b/src/MyWebLog.App/Entities/IMyWebLogData.fs deleted file mode 100644 index 2972f79..0000000 --- a/src/MyWebLog.App/Entities/IMyWebLogData.fs +++ /dev/null @@ -1,117 +0,0 @@ -namespace MyWebLog.Data - -open MyWebLog.Entities - -/// Interface required to provide data to myWebLog's logic layer -type IMyWebLogData = - /// Function to set up the data store - abstract SetUp : (unit -> unit) - - // --- Category --- - - /// Get all categories for a web log - abstract AllCategories : (string -> Category list) - - /// Try to find a category by its Id and web log Id (web log, category Ids) - abstract CategoryById : (string -> string -> Category option) - - /// Try to find a category by its slug (web log Id, slug) - abstract CategoryBySlug : (string -> string -> Category option) - - /// Add a category - abstract AddCategory : (Category -> unit) - - /// Update a category - abstract UpdateCategory : (Category -> unit) - - /// Update a category's children - abstract UpdateChildren : (string -> string -> string list -> unit) - - /// Delete a Category - abstract DeleteCategory : (Category -> unit) - - // --- Page --- - - /// Try to find a page by its Id and web log Id (web log, page Ids), choosing whether to include revisions - abstract PageById : (string -> string -> bool -> Page option) - - /// Try to find a page by its permalink and web log Id (web log Id, permalink) - abstract PageByPermalink : (string -> string -> Page option) - - /// Get all pages for a web log - abstract AllPages : (string -> Page list) - - /// Add a page - abstract AddPage : (Page -> unit) - - /// Update a page - abstract UpdatePage : (Page -> unit) - - /// Delete a page by its Id and web log Id (web log, page Ids) - abstract DeletePage : (string -> string -> unit) - - // --- Post --- - - /// Find a page of published posts for the given web log (web log Id, page #, # per page) - abstract PageOfPublishedPosts : (string -> int -> int -> Post list) - - /// Find a page of published posts within a given category (web log Id, cat Id, page #, # per page) - abstract PageOfCategorizedPosts : (string -> string -> int -> int -> Post list) - - /// Find a page of published posts tagged with a given tag (web log Id, tag, page #, # per page) - abstract PageOfTaggedPosts : (string -> string -> int -> int -> Post list) - - /// Try to find the next newer published post for the given post - abstract NewerPost : (Post -> Post option) - - /// Try to find the next newer published post within a given category - abstract NewerCategorizedPost : (string -> Post -> Post option) - - /// Try to find the next newer published post tagged with a given tag - abstract NewerTaggedPost : (string -> Post -> Post option) - - /// Try to find the next older published post for the given post - abstract OlderPost : (Post -> Post option) - - /// Try to find the next older published post within a given category - abstract OlderCategorizedPost : (string -> Post -> Post option) - - /// Try to find the next older published post tagged with a given tag - abstract OlderTaggedPost : (string -> Post -> Post option) - - /// Find a page of all posts for the given web log (web log Id, page #, # per page) - abstract PageOfAllPosts : (string -> int -> int -> Post list) - - /// Try to find a post by its Id and web log Id (web log, post Ids) - abstract PostById : (string -> string -> Post option) - - /// Try to find a post by its permalink (web log Id, permalink) - abstract PostByPermalink : (string -> string -> Post option) - - /// Try to find a post by a prior permalink (web log Id, permalink) - abstract PostByPriorPermalink : (string -> string -> Post option) - - /// Get posts for the RSS feed for the given web log and number of posts - abstract FeedPosts : (string -> int -> (Post * User option) list) - - /// Add a post - abstract AddPost : (Post -> unit) - - /// Update a post - abstract UpdatePost : (Post -> unit) - - // --- User --- - - /// Attempt to log on a user - abstract LogOn : (string -> string -> User option) - - /// Set a user's password (e-mail, password hash) - abstract SetUserPassword : (string -> string -> unit) - - // --- WebLog --- - - /// Get a web log by its URL base - abstract WebLogByUrlBase : (string -> WebLog option) - - /// Get dashboard counts for a web log - abstract DashboardCounts : (string -> DashboardCounts) diff --git a/src/MyWebLog.App/Keys.fs b/src/MyWebLog.App/Keys.fs deleted file mode 100644 index 741b6b4..0000000 --- a/src/MyWebLog.App/Keys.fs +++ /dev/null @@ -1,17 +0,0 @@ -[] -module MyWebLog.Keys - -/// Messages stored in the session -let Messages = "messages" - -/// The request start time (stored in the context for each request) -let RequestStart = "request-start" - -/// The current user -let User = "user" - -/// The version of myWebLog -let Version = "version" - -/// The web log -let WebLog = "web-log" \ No newline at end of file diff --git a/src/MyWebLog.App/Logic/Category.fs b/src/MyWebLog.App/Logic/Category.fs deleted file mode 100644 index e91e771..0000000 --- a/src/MyWebLog.App/Logic/Category.fs +++ /dev/null @@ -1,56 +0,0 @@ -module MyWebLog.Logic.Category - -open MyWebLog.Data -open MyWebLog.Entities - -/// 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 - -/// Find all categories for a given web log -let findAllCategories (data : IMyWebLogData) webLogId = - data.AllCategories webLogId - |> sortCategories - -/// Try to find a category for a given web log Id and category Id -let tryFindCategory (data : IMyWebLogData) webLogId catId = data.CategoryById webLogId catId - -/// Try to find a category by its slug for a given web log -let tryFindCategoryBySlug (data : IMyWebLogData) webLogId slug = data.CategoryBySlug webLogId slug - -/// Save a category -let saveCategory (data : IMyWebLogData) (cat : Category) = - match cat.Id with - | "new" -> let newCat = { cat with Id = string <| System.Guid.NewGuid() } - data.AddCategory newCat - newCat.Id - | _ -> data.UpdateCategory cat - cat.Id - -/// Remove a category from its parent -let removeCategoryFromParent (data : IMyWebLogData) webLogId parentId catId = - match tryFindCategory data webLogId parentId with - | Some parent -> parent.Children - |> List.filter (fun childId -> childId <> catId) - |> data.UpdateChildren webLogId parentId - | None -> () - -/// Add a category to a given parent -let addCategoryToParent (data : IMyWebLogData) webLogId parentId catId = - match tryFindCategory data webLogId parentId with - | Some parent -> catId :: parent.Children - |> data.UpdateChildren webLogId parentId - | None -> () - -/// Delete a category -let deleteCategory (data : IMyWebLogData) cat = data.DeleteCategory cat diff --git a/src/MyWebLog.App/Logic/Page.fs b/src/MyWebLog.App/Logic/Page.fs deleted file mode 100644 index 81fb0f4..0000000 --- a/src/MyWebLog.App/Logic/Page.fs +++ /dev/null @@ -1,29 +0,0 @@ -/// Logic for manipulating entities -module MyWebLog.Logic.Page - -open MyWebLog.Data -open MyWebLog.Entities - -/// Find a page by its Id and web log Id -let tryFindPage (data : IMyWebLogData) webLogId pageId = data.PageById webLogId pageId true - -/// Find a page by its Id and web log Id, without the revision list -let tryFindPageWithoutRevisions (data : IMyWebLogData) webLogId pageId = data.PageById webLogId pageId false - -/// Find a page by its permalink -let tryFindPageByPermalink (data : IMyWebLogData) webLogId permalink = data.PageByPermalink webLogId permalink - -/// Find a list of all pages (excludes text and revisions) -let findAllPages (data : IMyWebLogData) webLogId = data.AllPages webLogId - -/// Save a page -let savePage (data : IMyWebLogData) (page : Page) = - match page.Id with - | "new" -> let newPg = { page with Id = string <| System.Guid.NewGuid () } - data.AddPage newPg - newPg.Id - | _ -> data.UpdatePage page - page.Id - -/// Delete a page -let deletePage (data : IMyWebLogData) webLogId pageId = data.DeletePage webLogId pageId diff --git a/src/MyWebLog.App/Logic/Post.fs b/src/MyWebLog.App/Logic/Post.fs deleted file mode 100644 index 1918cbf..0000000 --- a/src/MyWebLog.App/Logic/Post.fs +++ /dev/null @@ -1,60 +0,0 @@ -/// Logic for manipulating entities -module MyWebLog.Logic.Post - -open MyWebLog.Data -open MyWebLog.Entities - -/// Find a page of published posts -let findPageOfPublishedPosts (data : IMyWebLogData) webLogId pageNbr nbrPerPage = - data.PageOfPublishedPosts webLogId pageNbr nbrPerPage - -/// Find a pages of published posts in a given category -let findPageOfCategorizedPosts (data : IMyWebLogData) webLogId catId pageNbr nbrPerPage = - data.PageOfCategorizedPosts webLogId catId pageNbr nbrPerPage - -/// Find a page of published posts tagged with a given tag -let findPageOfTaggedPosts (data : IMyWebLogData) webLogId tag pageNbr nbrPerPage = - data.PageOfTaggedPosts webLogId tag pageNbr nbrPerPage - -/// Find the next newer published post for the given post -let tryFindNewerPost (data : IMyWebLogData) post = data.NewerPost post - -/// Find the next newer published post in a given category for the given post -let tryFindNewerCategorizedPost (data : IMyWebLogData) catId post = data.NewerCategorizedPost catId post - -/// Find the next newer published post tagged with a given tag for the given post -let tryFindNewerTaggedPost (data : IMyWebLogData) tag post = data.NewerTaggedPost tag post - -/// Find the next older published post for the given post -let tryFindOlderPost (data : IMyWebLogData) post = data.OlderPost post - -/// Find the next older published post in a given category for the given post -let tryFindOlderCategorizedPost (data : IMyWebLogData) catId post = data.OlderCategorizedPost catId post - -/// Find the next older published post tagged with a given tag for the given post -let tryFindOlderTaggedPost (data : IMyWebLogData) tag post = data.OlderTaggedPost tag post - -/// Find a page of all posts for a web log -let findPageOfAllPosts (data : IMyWebLogData) webLogId pageNbr nbrPerPage = - data.PageOfAllPosts webLogId pageNbr nbrPerPage - -/// Try to find a post by its Id -let tryFindPost (data : IMyWebLogData) webLogId postId = data.PostById webLogId postId - -/// Try to find a post by its permalink -let tryFindPostByPermalink (data : IMyWebLogData) webLogId permalink = data.PostByPermalink webLogId permalink - -/// Try to find a post by its prior permalink -let tryFindPostByPriorPermalink (data : IMyWebLogData) webLogId permalink = data.PostByPriorPermalink webLogId permalink - -/// Find posts for the RSS feed -let findFeedPosts (data : IMyWebLogData) webLogId nbrOfPosts = data.FeedPosts webLogId nbrOfPosts - -/// Save a post -let savePost (data : IMyWebLogData) (post : Post) = - match post.Id with - | "new" -> let newPost = { post with Id = string <| System.Guid.NewGuid() } - data.AddPost newPost - newPost.Id - | _ -> data.UpdatePost post - post.Id diff --git a/src/MyWebLog.App/Logic/User.fs b/src/MyWebLog.App/Logic/User.fs deleted file mode 100644 index 4c6c1dc..0000000 --- a/src/MyWebLog.App/Logic/User.fs +++ /dev/null @@ -1,9 +0,0 @@ -/// Logic for manipulating entities -module MyWebLog.Logic.User - -open MyWebLog.Data - -/// Try to log on a user -let tryUserLogOn (data : IMyWebLogData) email passwordHash = data.LogOn email passwordHash - -let setUserPassword (data : IMyWebLogData) = data.SetUserPassword \ No newline at end of file diff --git a/src/MyWebLog.App/Logic/WebLog.fs b/src/MyWebLog.App/Logic/WebLog.fs deleted file mode 100644 index a1dfc70..0000000 --- a/src/MyWebLog.App/Logic/WebLog.fs +++ /dev/null @@ -1,11 +0,0 @@ -/// Logic for manipulating entities -module MyWebLog.Logic.WebLog - -open MyWebLog.Data -open MyWebLog.Entities - -/// Find a web log by its URL base -let tryFindWebLogByUrlBase (data : IMyWebLogData) urlBase = data.WebLogByUrlBase urlBase - -/// Find the counts for the admin dashboard -let findDashboardCounts (data : IMyWebLogData) webLogId = data.DashboardCounts webLogId diff --git a/src/MyWebLog.App/Modules/AdminModule.fs b/src/MyWebLog.App/Modules/AdminModule.fs deleted file mode 100644 index de094f0..0000000 --- a/src/MyWebLog.App/Modules/AdminModule.fs +++ /dev/null @@ -1,22 +0,0 @@ -namespace MyWebLog - -open MyWebLog.Data -open MyWebLog.Entities -open MyWebLog.Logic.WebLog -open MyWebLog.Resources -open Nancy -open RethinkDb.Driver.Net - -/// Handle /admin routes -type AdminModule (data : IMyWebLogData) as this = - inherit NancyModule ("/admin") - - do - this.Get ("/", fun _ -> this.Dashboard ()) - - /// Admin dashboard - member this.Dashboard () : obj = - this.RequiresAccessLevel AuthorizationLevel.Administrator - let model = DashboardModel (this.Context, this.WebLog, findDashboardCounts data this.WebLog.Id) - model.PageTitle <- Strings.get "Dashboard" - upcast this.View.["admin/dashboard", model] diff --git a/src/MyWebLog.App/Modules/CategoryModule.fs b/src/MyWebLog.App/Modules/CategoryModule.fs deleted file mode 100644 index 1d752eb..0000000 --- a/src/MyWebLog.App/Modules/CategoryModule.fs +++ /dev/null @@ -1,97 +0,0 @@ -namespace MyWebLog - -open MyWebLog.Data -open MyWebLog.Logic.Category -open MyWebLog.Entities -open MyWebLog.Resources -open Nancy -open Nancy.ModelBinding -open Nancy.Security -open RethinkDb.Driver.Net - -/// Handle /category and /categories URLs -type CategoryModule (data : IMyWebLogData) as this = - inherit NancyModule () - - do - this.Get ("/categories", fun _ -> this.CategoryList ()) - this.Get ("/category/{id}/edit", fun p -> this.EditCategory (downcast p)) - this.Post ("/category/{id}/edit", fun p -> this.SaveCategory (downcast p)) - this.Post ("/category/{id}/delete", fun p -> this.DeleteCategory (downcast p)) - - /// Display a list of categories - member this.CategoryList () : obj = - this.RequiresAccessLevel AuthorizationLevel.Administrator - let model = - CategoryListModel ( - this.Context, this.WebLog, findAllCategories data this.WebLog.Id - |> List.map (fun cat -> IndentedCategory.Create cat (fun _ -> false))) - model.PageTitle <- Strings.get "Categories" - upcast this.View.["admin/category/list", model] - - /// Edit a category - member this.EditCategory (parameters : DynamicDictionary) : obj = - this.RequiresAccessLevel AuthorizationLevel.Administrator - let catId = parameters.["id"].ToString () - match catId with "new" -> Some Category.Empty | _ -> tryFindCategory data this.WebLog.Id catId - |> function - | Some cat -> - let model = CategoryEditModel (this.Context, this.WebLog, cat) - model.Categories <- findAllCategories data this.WebLog.Id - |> List.map (fun c -> - IndentedCategory.Create c (fun catId -> catId = defaultArg cat.ParentId "")) - model.PageTitle <- Strings.get <| match catId with "new" -> "AddNewCategory" | _ -> "EditCategory" - upcast this.View.["admin/category/edit", model] - | _ -> this.NotFound () - - /// Save a category - member this.SaveCategory (parameters : DynamicDictionary) : obj = - this.ValidateCsrfToken () - this.RequiresAccessLevel AuthorizationLevel.Administrator - let catId = parameters.["id"].ToString () - let form = this.Bind () - match catId with - | "new" -> Some { Category.Empty with WebLogId = this.WebLog.Id } - | _ -> tryFindCategory data this.WebLog.Id catId - |> function - | Some old -> - let cat = - { old with - Name = form.Name - Slug = form.Slug - Description = match form.Description with "" -> None | d -> Some d - ParentId = match form.ParentId with "" -> None | p -> Some p - } - let newCatId = saveCategory data cat - match old.ParentId = cat.ParentId with - | true -> () - | _ -> - match old.ParentId with - | Some parentId -> removeCategoryFromParent data this.WebLog.Id parentId newCatId - | _ -> () - match cat.ParentId with - | Some parentId -> addCategoryToParent data this.WebLog.Id parentId newCatId - | _ -> () - let model = MyWebLogModel (this.Context, this.WebLog) - model.AddMessage - { UserMessage.Empty with - Message = System.String.Format - (Strings.get "MsgCategoryEditSuccess", - Strings.get (match catId with "new" -> "Added" | _ -> "Updated")) - } - this.Redirect (sprintf "/category/%s/edit" newCatId) model - | _ -> this.NotFound () - - /// Delete a category - member this.DeleteCategory (parameters : DynamicDictionary) : obj = - this.ValidateCsrfToken () - this.RequiresAccessLevel AuthorizationLevel.Administrator - let catId = parameters.["id"].ToString () - match tryFindCategory data this.WebLog.Id catId with - | Some cat -> - deleteCategory data cat - let model = MyWebLogModel (this.Context, this.WebLog) - model.AddMessage - { UserMessage.Empty with Message = System.String.Format(Strings.get "MsgCategoryDeleted", cat.Name) } - this.Redirect "/categories" model - | _ -> this.NotFound () diff --git a/src/MyWebLog.App/Modules/ModuleExtensions.fs b/src/MyWebLog.App/Modules/ModuleExtensions.fs deleted file mode 100644 index dd3d069..0000000 --- a/src/MyWebLog.App/Modules/ModuleExtensions.fs +++ /dev/null @@ -1,36 +0,0 @@ -[] -module MyWebLog.ModuleExtensions - -open MyWebLog.Entities -open Nancy -open Nancy.Security -open System -open System.Security.Claims - -/// Parent class for all myWebLog Nancy modules -type NancyModule with - - /// Strongly-typed access to the web log for the current request - member this.WebLog = this.Context.Items.[Keys.WebLog] :?> WebLog - - /// Display a view using the theme specified for the web log - member this.ThemedView view (model : MyWebLogModel) : obj = - upcast this.View.[(sprintf "themes/%s/%s" this.WebLog.ThemePath view), model] - - /// Return a 404 - member this.NotFound () : obj = upcast HttpStatusCode.NotFound - - /// Redirect a request, storing messages in the session if they exist - member this.Redirect url (model : MyWebLogModel) : obj = - match List.length model.Messages with - | 0 -> () - | _ -> this.Session.[Keys.Messages] <- model.Messages - // Temp (307) redirects don't reset the HTTP method; this allows POST-process-REDIRECT workflow - upcast this.Response.AsRedirect(url).WithStatusCode HttpStatusCode.MovedPermanently - - /// Require a specific level of access for the current web log - member this.RequiresAccessLevel level = - let findClaim = Predicate (fun claim -> - claim.Type = ClaimTypes.Role && claim.Value = sprintf "%s|%s" this.WebLog.Id level) - this.RequiresAuthentication () - this.RequiresClaims [| findClaim |] diff --git a/src/MyWebLog.App/Modules/PageModule.fs b/src/MyWebLog.App/Modules/PageModule.fs deleted file mode 100644 index a928dac..0000000 --- a/src/MyWebLog.App/Modules/PageModule.fs +++ /dev/null @@ -1,97 +0,0 @@ -namespace MyWebLog - -open MyWebLog.Data -open MyWebLog.Entities -open MyWebLog.Logic.Page -open MyWebLog.Resources -open Nancy -open Nancy.ModelBinding -open Nancy.Security -open NodaTime -open RethinkDb.Driver.Net - -/// Handle /pages and /page URLs -type PageModule (data : IMyWebLogData, clock : IClock) as this = - inherit NancyModule () - - do - this.Get ("/pages", fun _ -> this.PageList ()) - this.Get ("/page/{id}/edit", fun p -> this.EditPage (downcast p)) - this.Post ("/page/{id}/edit", fun p -> this.SavePage (downcast p)) - this.Delete ("/page/{id}/delete", fun p -> this.DeletePage (downcast p)) - - /// List all pages - member this.PageList () : obj = - this.RequiresAccessLevel AuthorizationLevel.Administrator - let model = - PagesModel (this.Context, this.WebLog, findAllPages data this.WebLog.Id - |> List.map (fun p -> PageForDisplay (this.WebLog, p))) - model.PageTitle <- Strings.get "Pages" - upcast this.View.["admin/page/list", model] - - /// Edit a page - member this.EditPage (parameters : DynamicDictionary) : obj = - this.RequiresAccessLevel AuthorizationLevel.Administrator - let pageId = parameters.["id"].ToString () - match pageId with "new" -> Some Page.Empty | _ -> tryFindPage data this.WebLog.Id pageId - |> function - | Some page -> - let rev = match page.Revisions - |> List.sortByDescending (fun r -> r.AsOf) - |> List.tryHead with - | Some r -> r - | _ -> Revision.Empty - let model = EditPageModel (this.Context, this.WebLog, page, rev) - model.PageTitle <- Strings.get <| match pageId with "new" -> "AddNewPage" | _ -> "EditPage" - upcast this.View.["admin/page/edit", model] - | _ -> this.NotFound () - - /// Save a page - member this.SavePage (parameters : DynamicDictionary) : obj = - this.ValidateCsrfToken () - this.RequiresAccessLevel AuthorizationLevel.Administrator - let pageId = parameters.["id"].ToString () - let form = this.Bind () - let now = clock.GetCurrentInstant().ToUnixTimeTicks () - match pageId with "new" -> Some Page.Empty | _ -> tryFindPage data this.WebLog.Id pageId - |> function - | Some p -> - let page = match pageId with "new" -> { p with WebLogId = this.WebLog.Id } | _ -> p - let pId = - { p with - Title = form.Title - Permalink = form.Permalink - PublishedOn = match pageId with "new" -> now | _ -> page.PublishedOn - UpdatedOn = now - ShowInPageList = form.ShowInPageList - Text = match form.Source with - | RevisionSource.Markdown -> (* Markdown.TransformHtml *) form.Text - | _ -> form.Text - Revisions = { AsOf = now - SourceType = form.Source - Text = form.Text - } :: page.Revisions - } - |> savePage data - let model = MyWebLogModel (this.Context, this.WebLog) - model.AddMessage - { UserMessage.Empty with - Message = System.String.Format - (Strings.get "MsgPageEditSuccess", - Strings.get (match pageId with "new" -> "Added" | _ -> "Updated")) - } - this.Redirect (sprintf "/page/%s/edit" pId) model - | _ -> this.NotFound () - - /// Delete a page - member this.DeletePage (parameters : DynamicDictionary) : obj = - this.ValidateCsrfToken () - this.RequiresAccessLevel AuthorizationLevel.Administrator - let pageId = parameters.["id"].ToString () - match tryFindPageWithoutRevisions data this.WebLog.Id pageId with - | Some page -> - deletePage data page.WebLogId page.Id - let model = MyWebLogModel (this.Context, this.WebLog) - model.AddMessage { UserMessage.Empty with Message = Strings.get "MsgPageDeleted" } - this.Redirect "/pages" model - | _ -> this.NotFound () diff --git a/src/MyWebLog.App/Modules/PostModule.fs b/src/MyWebLog.App/Modules/PostModule.fs deleted file mode 100644 index ec29d85..0000000 --- a/src/MyWebLog.App/Modules/PostModule.fs +++ /dev/null @@ -1,317 +0,0 @@ -namespace MyWebLog - -open MyWebLog.Data -open MyWebLog.Entities -open MyWebLog.Logic.Category -open MyWebLog.Logic.Page -open MyWebLog.Logic.Post -open MyWebLog.Resources -open Nancy -open Nancy.ModelBinding -open Nancy.Security -open Nancy.Session.Persistable -open NodaTime -open RethinkDb.Driver.Net -open System -open System.Xml.Linq - -type NewsItem = - { Title : string - Link : string - ReleaseDate : DateTime - Description : string - } - -/// Routes dealing with posts (including the home page, /tag, /category, RSS, and catch-all routes) -type PostModule (data : IMyWebLogData, clock : IClock) as this = - inherit NancyModule () - - /// Get the page number from the dictionary - let getPage (parameters : DynamicDictionary) = - match parameters.ContainsKey "page" with - | true -> match System.Int32.TryParse (parameters.["page"].ToString ()) with true, pg -> pg | _ -> 1 - | _ -> 1 - - /// Convert a list of posts to a list of posts for display - let forDisplay posts = posts |> List.map (fun post -> PostForDisplay (this.WebLog, post)) - - /// Generate an RSS/Atom feed of the latest posts - let generateFeed format : obj = - let myChannelFeed channelTitle channelLink channelDescription (items : NewsItem list) = - let xn = XName.Get - let elem name (valu : string) = XElement (xn name, valu) - let elems = - items - |> List.sortByDescending (fun i -> i.ReleaseDate) - |> List.map (fun i -> - XElement ( - xn "item", - elem "title" (System.Net.WebUtility.HtmlEncode i.Title), - elem "link" i.Link, - elem "guid" i.Link, - elem "pubDate" (i.ReleaseDate.ToString "r"), - elem "description" (System.Net.WebUtility.HtmlEncode i.Description) - )) - XDocument ( - XDeclaration ("1.0", "utf-8", "yes"), - XElement ( - xn "rss", - XAttribute (xn "version", "2.0"), - elem "title" channelTitle, - elem "link" channelLink, - elem "description" (defaultArg channelDescription ""), - elem "language" "en-us", - XElement (xn "channel", elems)) - |> box) - let schemeAndUrl = sprintf "%s://%s" this.Request.Url.Scheme this.WebLog.UrlBase - let feed = - findFeedPosts data this.WebLog.Id 10 - |> List.map (fun (post, _) -> - { Title = post.Title - Link = sprintf "%s/%s" schemeAndUrl post.Permalink - ReleaseDate = Instant.FromUnixTimeTicks(post.PublishedOn).ToDateTimeOffset().DateTime - Description = post.Text - }) - |> myChannelFeed this.WebLog.Name schemeAndUrl this.WebLog.Subtitle - let stream = new IO.MemoryStream () - Xml.XmlWriter.Create stream |> feed.Save - //|> match format with "atom" -> feed.SaveAsAtom10 | _ -> feed.SaveAsRss20 - stream.Position <- 0L - upcast this.Response.FromStream (stream, sprintf "application/%s+xml" format) - // TODO: how to return this? - - (* - let feed = - SyndicationFeed( - this.WebLog.Name, defaultArg this.WebLog.Subtitle null, - Uri(sprintf "%s://%s" this.Request.Url.Scheme this.WebLog.UrlBase), null, - (match posts |> List.tryHead with - | Some (post, _) -> Instant(post.UpdatedOn).ToDateTimeOffset () - | _ -> System.DateTimeOffset(System.DateTime.MinValue)), - posts - |> List.map (fun (post, user) -> - let item = - SyndicationItem( - BaseUri = Uri(sprintf "%s://%s/%s" this.Request.Url.Scheme this.WebLog.UrlBase post.Permalink), - PublishDate = Instant(post.PublishedOn).ToDateTimeOffset (), - LastUpdatedTime = Instant(post.UpdatedOn).ToDateTimeOffset (), - Title = TextSyndicationContent(post.Title), - Content = TextSyndicationContent(post.Text, TextSyndicationContentKind.Html)) - user - |> Option.iter (fun u -> item.Authors.Add - (SyndicationPerson(u.UserName, u.PreferredName, defaultArg u.Url null))) - post.Categories - |> List.iter (fun c -> item.Categories.Add(SyndicationCategory(c.Name))) - item)) - let stream = new IO.MemoryStream() - Xml.XmlWriter.Create(stream) - |> match format with "atom" -> feed.SaveAsAtom10 | _ -> feed.SaveAsRss20 - stream.Position <- int64 0 - upcast this.Response.FromStream(stream, sprintf "application/%s+xml" format) *) - - do - this.Get ("/", fun _ -> this.HomePage ()) - this.Get ("/{permalink*}", fun p -> this.CatchAll (downcast p)) - this.Get ("/posts/page/{page:int}", fun p -> this.PublishedPostsPage (getPage <| downcast p)) - this.Get ("/category/{slug}", fun p -> this.CategorizedPosts (downcast p)) - this.Get ("/category/{slug}/page/{page:int}", fun p -> this.CategorizedPosts (downcast p)) - this.Get ("/tag/{tag}", fun p -> this.TaggedPosts (downcast p)) - this.Get ("/tag/{tag}/page/{page:int}", fun p -> this.TaggedPosts (downcast p)) - this.Get ("/feed", fun _ -> this.Feed ()) - this.Get ("/posts/list", fun _ -> this.PostList 1) - this.Get ("/posts/list/page/{page:int}", fun p -> this.PostList (getPage <| downcast p)) - this.Get ("/post/{postId}/edit", fun p -> this.EditPost (downcast p)) - this.Post ("/post/{postId}/edit", fun p -> this.SavePost (downcast p)) - - // ---- Display posts to users ---- - - /// Display a page of published posts - member this.PublishedPostsPage pageNbr : obj = - let model = PostsModel (this.Context, this.WebLog) - model.PageNbr <- pageNbr - model.Posts <- findPageOfPublishedPosts data this.WebLog.Id pageNbr 10 |> forDisplay - model.HasNewer <- match pageNbr with - | 1 -> false - | _ -> match List.isEmpty model.Posts with - | true -> false - | _ -> Option.isSome <| tryFindNewerPost data (List.last model.Posts).Post - model.HasOlder <- match List.isEmpty model.Posts with - | true -> false - | _ -> Option.isSome <| tryFindOlderPost data (List.head model.Posts).Post - model.UrlPrefix <- "/posts" - model.PageTitle <- match pageNbr with 1 -> "" | _ -> sprintf "%s%i" (Strings.get "PageHash") pageNbr - this.ThemedView "index" model - - /// Display either the newest posts or the configured home page - member this.HomePage () : obj = - match this.WebLog.DefaultPage with - | "posts" -> this.PublishedPostsPage 1 - | pageId -> - match tryFindPageWithoutRevisions data this.WebLog.Id pageId with - | Some page -> - let model = PageModel(this.Context, this.WebLog, page) - model.PageTitle <- page.Title - this.ThemedView "page" model - | _ -> this.NotFound () - - /// Derive a post or page from the URL, or redirect from a prior URL to the current one - member this.CatchAll (parameters : DynamicDictionary) : obj = - let url = parameters.["permalink"].ToString () - match tryFindPostByPermalink data this.WebLog.Id url with - | Some post -> // Hopefully the most common result; the permalink is a permalink! - let model = PostModel(this.Context, this.WebLog, post) - model.NewerPost <- tryFindNewerPost data post - model.OlderPost <- tryFindOlderPost data post - model.PageTitle <- post.Title - this.ThemedView "single" model - | _ -> // Maybe it's a page permalink instead... - match tryFindPageByPermalink data this.WebLog.Id url with - | Some page -> // ...and it is! - let model = PageModel (this.Context, this.WebLog, page) - model.PageTitle <- page.Title - this.ThemedView "page" model - | _ -> // Maybe it's an old permalink for a post - match tryFindPostByPriorPermalink data this.WebLog.Id url with - | Some post -> // Redirect them to the proper permalink - upcast this.Response.AsRedirect(sprintf "/%s" post.Permalink) - .WithStatusCode HttpStatusCode.MovedPermanently - | _ -> this.NotFound () - - /// Display categorized posts - member this.CategorizedPosts (parameters : DynamicDictionary) : obj = - let slug = parameters.["slug"].ToString () - match tryFindCategoryBySlug data this.WebLog.Id slug with - | Some cat -> - let pageNbr = getPage parameters - let model = PostsModel (this.Context, this.WebLog) - model.PageNbr <- pageNbr - model.Posts <- findPageOfCategorizedPosts data this.WebLog.Id cat.Id pageNbr 10 |> forDisplay - model.HasNewer <- match List.isEmpty model.Posts with - | true -> false - | _ -> Option.isSome <| tryFindNewerCategorizedPost data cat.Id - (List.head model.Posts).Post - model.HasOlder <- match List.isEmpty model.Posts with - | true -> false - | _ -> Option.isSome <| tryFindOlderCategorizedPost data cat.Id - (List.last model.Posts).Post - model.UrlPrefix <- sprintf "/category/%s" slug - model.PageTitle <- sprintf "\"%s\" Category%s" cat.Name - (match pageNbr with | 1 -> "" | n -> sprintf " | Page %i" n) - model.Subtitle <- Some <| match cat.Description with - | Some desc -> desc - | _ -> sprintf "Posts in the \"%s\" category" cat.Name - this.ThemedView "index" model - | _ -> this.NotFound () - - /// Display tagged posts - member this.TaggedPosts (parameters : DynamicDictionary) : obj = - let tag = parameters.["tag"].ToString () - let pageNbr = getPage parameters - let model = PostsModel (this.Context, this.WebLog) - model.PageNbr <- pageNbr - model.Posts <- findPageOfTaggedPosts data this.WebLog.Id tag pageNbr 10 |> forDisplay - model.HasNewer <- match List.isEmpty model.Posts with - | true -> false - | _ -> Option.isSome <| tryFindNewerTaggedPost data tag (List.head model.Posts).Post - model.HasOlder <- match List.isEmpty model.Posts with - | true -> false - | _ -> Option.isSome <| tryFindOlderTaggedPost data tag (List.last model.Posts).Post - model.UrlPrefix <- sprintf "/tag/%s" tag - model.PageTitle <- sprintf "\"%s\" Tag%s" tag (match pageNbr with 1 -> "" | n -> sprintf " | Page %i" n) - model.Subtitle <- Some <| sprintf "Posts tagged \"%s\"" tag - this.ThemedView "index" model - - /// Generate an RSS feed - member this.Feed () : obj = - let query = this.Request.Query :?> DynamicDictionary - match query.ContainsKey "format" with - | true -> - match query.["format"].ToString () with - | x when x = "atom" || x = "rss" -> generateFeed x - | x when x = "rss2" -> generateFeed "rss" - | _ -> this.Redirect "/feed" (MyWebLogModel (this.Context, this.WebLog)) - | _ -> generateFeed "rss" - - // ---- Administer posts ---- - - /// Display a page of posts in the admin area - member this.PostList pageNbr : obj = - this.RequiresAccessLevel AuthorizationLevel.Administrator - let model = PostsModel (this.Context, this.WebLog) - model.PageNbr <- pageNbr - model.Posts <- findPageOfAllPosts data this.WebLog.Id pageNbr 25 |> forDisplay - model.HasNewer <- pageNbr > 1 - model.HasOlder <- List.length model.Posts > 24 - model.UrlPrefix <- "/posts/list" - model.PageTitle <- Strings.get "Posts" - upcast this.View.["admin/post/list", model] - - /// Edit a post - member this.EditPost (parameters : DynamicDictionary) : obj = - this.RequiresAccessLevel AuthorizationLevel.Administrator - let postId = parameters.["postId"].ToString () - match postId with "new" -> Some Post.Empty | _ -> tryFindPost data this.WebLog.Id postId - |> function - | 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 <- findAllCategories data this.WebLog.Id - |> List.map (fun cat -> - DisplayCategory.Create cat (post.CategoryIds |> List.contains (fst cat).Id)) - model.PageTitle <- Strings.get <| match post.Id with "new" -> "AddNewPost" | _ -> "EditPost" - upcast this.View.["admin/post/edit", model] - | _ -> this.NotFound () - - /// Save a post - member this.SavePost (parameters : DynamicDictionary) : obj = - this.RequiresAccessLevel AuthorizationLevel.Administrator - this.ValidateCsrfToken () - let postId = parameters.["postId"].ToString () - let form = this.Bind () - let now = clock.GetCurrentInstant().ToUnixTimeTicks () - match postId with "new" -> Some Post.Empty | _ -> tryFindPost data this.WebLog.Id postId - |> function - | Some p -> - let justPublished = p.PublishedOn = 0L && form.PublishNow - let post = - match postId with - | "new" -> - { p with - WebLogId = this.WebLog.Id - AuthorId = this.Request.PersistableSession.GetOrDefault(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 | _ -> post.PublishedOn - 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.sort - |> Seq.toList - Revisions = { AsOf = now - SourceType = form.Source - Text = form.Text } :: post.Revisions } - |> savePost data - let model = MyWebLogModel(this.Context, this.WebLog) - model.AddMessage - { UserMessage.Empty with - Message = System.String.Format - (Strings.get "MsgPostEditSuccess", - Strings.get (match postId with "new" -> "Added" | _ -> "Updated"), - (match justPublished with true -> Strings.get "AndPublished" | _ -> "")) - } - this.Redirect (sprintf "/post/%s/edit" pId) model - | _ -> this.NotFound () diff --git a/src/MyWebLog.App/Modules/UserModule.fs b/src/MyWebLog.App/Modules/UserModule.fs deleted file mode 100644 index eca46af..0000000 --- a/src/MyWebLog.App/Modules/UserModule.fs +++ /dev/null @@ -1,64 +0,0 @@ -namespace MyWebLog - -open MyWebLog.Data -open MyWebLog.Entities -open MyWebLog.Logic.User -open MyWebLog.Resources -open Nancy -open Nancy.Authentication.Forms -open Nancy.Cryptography -open Nancy.ModelBinding -open Nancy.Security -open Nancy.Session.Persistable -open RethinkDb.Driver.Net -open System.Text - -/// Handle /user URLs -type UserModule (data : IMyWebLogData, cfg : AppConfig) as this = - inherit NancyModule ("/user") - - /// Hash the user's password - let pbkdf2 (pw : string) = - PassphraseKeyGenerator(pw, cfg.PasswordSalt, 4096).GetBytes 512 - |> Seq.fold (fun acc byt -> sprintf "%s%s" acc (byt.ToString "x2")) "" - - do - this.Get ("/log-on", fun _ -> this.ShowLogOn ()) - this.Post ("/log-on", fun p -> this.DoLogOn (downcast p)) - this.Get ("/log-off", fun _ -> this.LogOff ()) - - /// Show the log on page - member this.ShowLogOn () : obj = - let model = LogOnModel (this.Context, this.WebLog) - let query = this.Request.Query :?> DynamicDictionary - model.Form.ReturnUrl <- match query.ContainsKey "returnUrl" with true -> query.["returnUrl"].ToString () | _ -> "" - model.PageTitle <- Strings.get "LogOn" - upcast this.View.["admin/user/log-on", model] - - /// Process a user log on - member this.DoLogOn (parameters : DynamicDictionary) : obj = - this.ValidateCsrfToken () - let form = this.Bind () - let model = MyWebLogModel(this.Context, this.WebLog) - match tryUserLogOn data form.Email (pbkdf2 form.Password) with - | Some user -> - this.Session.[Keys.User] <- user - model.AddMessage { UserMessage.Empty with Message = Strings.get "MsgLogOnSuccess" } - 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 - upcast this.LoginAndRedirect (System.Guid.Parse user.Id, - fallbackRedirectUrl = defaultArg (Option.ofObj form.ReturnUrl) "/") - | _ -> - { UserMessage.Empty with - Level = Level.Error - Message = Strings.get "ErrBadLogOnAttempt" } - |> model.AddMessage - this.Redirect (sprintf "/user/log-on?returnUrl=%s" form.ReturnUrl) model - - /// Log a user off - member this.LogOff () : obj = - this.Session.DeleteAll () - let model = MyWebLogModel (this.Context, this.WebLog) - model.AddMessage { UserMessage.Empty with Message = Strings.get "MsgLogOffSuccess" } - this.Redirect "" model |> ignore - upcast this.LogoutAndRedirect "/" diff --git a/src/MyWebLog.App/MyWebLog.App.xproj b/src/MyWebLog.App/MyWebLog.App.xproj deleted file mode 100644 index eb039d1..0000000 --- a/src/MyWebLog.App/MyWebLog.App.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - 9cea3a8b-e8aa-44e6-9f5f-2095ceed54eb - Nancy.Session.Persistable - .\obj - .\bin\ - v4.5.2 - - - - 2.0 - - - diff --git a/src/MyWebLog.App/Strings.fs b/src/MyWebLog.App/Strings.fs deleted file mode 100644 index f207444..0000000 --- a/src/MyWebLog.App/Strings.fs +++ /dev/null @@ -1,42 +0,0 @@ -module MyWebLog.Resources.Strings - -open MyWebLog -open Newtonsoft.Json -open System.Collections.Generic -open System.Reflection - -/// The locales we'll try to load -let private supportedLocales = [ "en-US" ] - -/// The fallback locale, if a key is not found in a non-default locale -let private fallbackLocale = "en-US" - -/// Get an embedded JSON file as a string -let private getEmbedded locale = - use rdr = - new System.IO.StreamReader - (typeof.GetTypeInfo().Assembly.GetManifestResourceStream(sprintf "MyWebLog.App.%s.json" locale)) - rdr.ReadToEnd() - -/// The dictionary of localized strings -let private strings = - supportedLocales - |> List.map (fun loc -> loc, JsonConvert.DeserializeObject>(getEmbedded loc)) - |> dict - -/// Get a key from the resources file for the given locale -let getForLocale locale key = - let getString thisLocale = - match strings.ContainsKey thisLocale with - | true -> match strings.[thisLocale].ContainsKey key with - | true -> Some strings.[thisLocale].[key] - | _ -> None - | _ -> None - match getString locale with - | Some xlat -> Some xlat - | _ when locale <> fallbackLocale -> getString fallbackLocale - | _ -> None - |> function Some xlat -> xlat | _ -> sprintf "%s.%s" locale key - -/// Translate the key for the current locale -let get key = getForLocale System.Globalization.CultureInfo.CurrentCulture.Name key diff --git a/src/MyWebLog.App/ViewModels.fs b/src/MyWebLog.App/ViewModels.fs deleted file mode 100644 index 7a0babe..0000000 --- a/src/MyWebLog.App/ViewModels.fs +++ /dev/null @@ -1,504 +0,0 @@ -namespace MyWebLog - -open MyWebLog.Entities -open MyWebLog.Logic.WebLog -open MyWebLog.Resources -open Nancy -open Nancy.Session.Persistable -open Newtonsoft.Json -open NodaTime -open NodaTime.Text -open System -open System.Net - -/// Levels for a user message -[] -module Level = - /// An informational message - let Info = "Info" - /// A message regarding a non-fatal but non-optimal condition - let Warning = "WARNING" - /// A message regarding a failure of the expected result - let Error = "ERROR" - - -/// A message for the user -type UserMessage = - { /// The level of the message (use Level module constants) - Level : string - /// The text of the message - Message : string - /// Further details regarding the message - Details : string option } -with - /// An empty message - static member Empty = - { Level = Level.Info - Message = "" - Details = None } - - /// Display version - [] - member this.ToDisplay = - let classAndLabel = - dict [ - Level.Error, ("danger", Strings.get "Error") - Level.Warning, ("warning", Strings.get "Warning") - Level.Info, ("info", "") - ] - seq { - yield "
" - match snd classAndLabel.[this.Level] with - | "" -> () - | lbl -> yield lbl.ToUpper () - yield " » " - yield this.Message - yield "" - match this.Details with - | Some d -> yield "
" - yield d - | None -> () - yield "
" - } - |> Seq.reduce (+) - - -/// Helpers to format local date/time using NodaTime -module FormatDateTime = - - /// Convert ticks to a zoned date/time - let zonedTime timeZone ticks = Instant.FromUnixTimeTicks(ticks).InZone(DateTimeZoneProviders.Tzdb.[timeZone]) - - /// Display a long date - let longDate timeZone ticks = - zonedTime timeZone ticks - |> ZonedDateTimePattern.CreateWithCurrentCulture("MMMM d',' yyyy", DateTimeZoneProviders.Tzdb).Format - - /// Display a short date - let shortDate timeZone ticks = - zonedTime timeZone ticks - |> ZonedDateTimePattern.CreateWithCurrentCulture("MMM d',' yyyy", DateTimeZoneProviders.Tzdb).Format - - /// Display the time - let time timeZone ticks = - (zonedTime timeZone ticks - |> ZonedDateTimePattern.CreateWithCurrentCulture("h':'mmtt", DateTimeZoneProviders.Tzdb).Format).ToLower () - - -/// Parent view model for all myWebLog views -type MyWebLogModel (ctx : NancyContext, webLog : WebLog) as this = - - /// Get the messages from the session - let getMessages () = - let msg = ctx.Request.PersistableSession.GetOrDefault (Keys.Messages, []) - match List.length msg with - | 0 -> () - | _ -> ctx.Request.Session.Delete Keys.Messages - msg - - /// Generate a footer logo with the given scheme - let footerLogo scheme = - seq { - yield sprintf "\"myWebLog\"" - } - |> Seq.reduce (+) - - /// The web log for this request - member this.WebLog = webLog - /// The subtitle for the webLog (SSVE can't do IsSome that deep) - member this.WebLogSubtitle = defaultArg this.WebLog.Subtitle "" - /// User messages - member val Messages = getMessages () with get, set - /// The currently logged in user - member this.User = ctx.Request.PersistableSession.GetOrDefault (Keys.User, User.Empty) - /// The title of the page - member val PageTitle = "" with get, set - /// The name and version of the application - member this.Generator = sprintf "myWebLog %s" (ctx.Items.[Keys.Version].ToString ()) - /// The request start time - member this.RequestStart = ctx.Items.[Keys.RequestStart] :?> int64 - /// Is a user authenticated for this request? - member this.IsAuthenticated = "" <> this.User.Id - /// Add a message to the output - member this.AddMessage message = this.Messages <- message :: this.Messages - - /// Display a long date - member this.DisplayLongDate ticks = FormatDateTime.longDate this.WebLog.TimeZone ticks - /// Display a short date - member this.DisplayShortDate ticks = FormatDateTime.shortDate this.WebLog.TimeZone ticks - /// Display the time - member this.DisplayTime ticks = FormatDateTime.time this.WebLog.TimeZone ticks - /// The page title with the web log name appended - member this.DisplayPageTitle = - match this.PageTitle with - | "" -> - match this.WebLog.Subtitle with - | Some st -> sprintf "%s | %s" this.WebLog.Name st - | None -> this.WebLog.Name - | pt -> sprintf "%s | %s" pt this.WebLog.Name - - /// An image with the version and load time in the tool tip (using light text) - member this.FooterLogoLight = footerLogo "light" - - /// An image with the version and load time in the tool tip (using dark text) - member this.FooterLogoDark = footerLogo "dark" - - -// ---- Admin models ---- - -/// Admin Dashboard view model -type DashboardModel (ctx, webLog, counts : DashboardCounts) = - inherit MyWebLogModel (ctx, webLog) - /// The number of posts for the current web log - member val Posts = counts.Posts with get, set - /// The number of pages for the current web log - member val Pages = counts.Pages with get, set - /// The number of categories for the current web log - member val Categories = counts.Categories with get, set - - -// ---- Category models ---- - -type IndentedCategory = - { Category : Category - Indent : int - Selected : bool } -with - /// Create an indented category - static member Create cat isSelected = - { Category = fst cat - Indent = snd cat - Selected = isSelected (fst cat).Id } - /// 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 - /// Display for this category as an option within a select box - member this.Option = - seq { - yield sprintf "" - } - |> String.concat "" - /// Does the category have a description? - member this.HasDescription = this.Category.Description.IsSome - - -/// Model for the list of categories -type CategoryListModel (ctx, webLog, categories) = - inherit MyWebLogModel (ctx, webLog) - /// The categories - member this.Categories : IndentedCategory list = categories - - -/// Form for editing a category -type CategoryForm (category : Category) = - new() = CategoryForm (Category.Empty) - /// The name of the category - member val Name = category.Name with get, set - /// The slug of the category (used in category URLs) - member val Slug = category.Slug with get, set - /// The description of the category - member val Description = defaultArg category.Description "" with get, set - /// The parent category for this one - member val ParentId = defaultArg category.ParentId "" with get, set - -/// Model for editing a category -type CategoryEditModel (ctx, webLog, category) = - inherit MyWebLogModel (ctx, webLog) - /// The form with the category information - member val Form = CategoryForm (category) with get, set - /// The category being edited - member val Category = category - /// The categories - member val Categories : IndentedCategory list = [] with get, set - - -// ---- Page models ---- - -/// Model for page display -type PageModel (ctx, webLog, page) = - inherit MyWebLogModel (ctx, webLog) - /// The page to be displayed - member this.Page : Page = page - - -/// Wrapper for a page with additional properties -type PageForDisplay (webLog, page) = - /// The page - member this.Page : Page = page - /// The time zone of the web log - member this.TimeZone = webLog.TimeZone - /// The date the page was last updated - member this.UpdatedDate = FormatDateTime.longDate this.TimeZone page.UpdatedOn - /// The time the page was last updated - member this.UpdatedTime = FormatDateTime.time this.TimeZone page.UpdatedOn - - -/// Model for page list display -type PagesModel (ctx, webLog, pages) = - inherit MyWebLogModel (ctx, webLog) - /// The pages - member this.Pages : PageForDisplay list = pages - - -/// Form used to edit a page -type EditPageForm() = - /// The title of the page - member val Title = "" with get, set - /// The link for the page - member val Permalink = "" with get, set - /// The source type of the revision - member val Source = "" with get, set - /// The text of the revision - member val Text = "" with get, set - /// Whether to show the page in the web log's page list - member val ShowInPageList = false with get, set - - /// Fill the form with applicable values from a page - member this.ForPage (page : Page) = - this.Title <- page.Title - this.Permalink <- page.Permalink - this.ShowInPageList <- page.ShowInPageList - this - - /// Fill the form with applicable values from a revision - member this.ForRevision rev = - this.Source <- rev.SourceType - this.Text <- rev.Text - this - - -/// Model for the edit page page -type EditPageModel (ctx, webLog, page, revision) = - inherit MyWebLogModel (ctx, webLog) - /// The page edit form - member val Form = EditPageForm().ForPage(page).ForRevision(revision) - /// The page itself - member this.Page = page - /// The page's published date - member this.PublishedDate = this.DisplayLongDate page.PublishedOn - /// The page's published time - member this.PublishedTime = this.DisplayTime page.PublishedOn - /// The page's last updated date - member this.LastUpdatedDate = this.DisplayLongDate page.UpdatedOn - /// The page's last updated time - member this.LastUpdatedTime = this.DisplayTime page.UpdatedOn - /// Is this a new page? - member this.IsNew = "new" = page.Id - /// Generate a checked attribute if this page shows in the page list - member this.PageListChecked = match page.ShowInPageList with true -> "checked=\"checked\"" | _ -> "" - - -// ---- Post models ---- - -/// Formatter for comment information -type CommentForDisplay (comment : Comment, tz) = - /// The comment on which this model is based - member this.Comment = comment - /// The commentor (linked with a URL if there is one) - member this.Commentor = - match comment.Url with Some url -> sprintf "%s" url comment.Name | _ -> comment.Name - /// The date/time this comment was posted - member this.CommentedOn = - sprintf "%s / %s" (FormatDateTime.longDate tz comment.PostedOn) (FormatDateTime.time tz comment.PostedOn) - -/// Model for single post display -type PostModel (ctx, webLog, post) = - inherit MyWebLogModel (ctx, webLog) - /// The post being displayed - member this.Post : Post = post - /// The next newer post - member val NewerPost : Post option = None with get, set - /// The next older post - member val OlderPost : Post option = None with get, set - /// The date the post was published - member this.PublishedDate = this.DisplayLongDate this.Post.PublishedOn - /// The time the post was published - member this.PublishedTime = this.DisplayTime this.Post.PublishedOn - /// The number of comments - member this.CommentCount = - match post.Comments |> List.length with - | 0 -> Strings.get "NoComments" - | 1 -> Strings.get "OneComment" - | x -> String.Format (Strings.get "XComments", x) - /// The comments for display - member this.Comments = post.Comments - |> List.filter (fun c -> c.Status = CommentStatus.Approved) - |> List.map (fun c -> CommentForDisplay (c, webLog.TimeZone)) - - /// Does the post have tags? - member this.HasTags = not <| List.isEmpty post.Tags - /// Get the tags sorted - member this.Tags = post.Tags - |> List.sort - |> List.map (fun tag -> tag, tag.Replace(' ', '+')) - /// Does this post have a newer post? - member this.HasNewer = this.NewerPost.IsSome - /// Does this post have an older post? - member this.HasOlder = this.OlderPost.IsSome - - -/// Wrapper for a post with additional properties -type PostForDisplay (webLog : WebLog, post : Post) = - /// Turn tags into a pipe-delimited string of tags - let pipedTags tags = tags |> List.reduce (fun acc x -> sprintf "%s | %s" acc x) - /// The actual post - member this.Post = post - /// The time zone for the web log to which this post belongs - member this.TimeZone = webLog.TimeZone - /// The date the post was published - member this.PublishedDate = - match this.Post.Status with - | PostStatus.Published -> FormatDateTime.longDate this.TimeZone this.Post.PublishedOn - | _ -> FormatDateTime.longDate this.TimeZone this.Post.UpdatedOn - /// The time the post was published - member this.PublishedTime = - match this.Post.Status with - | PostStatus.Published -> FormatDateTime.time this.TimeZone this.Post.PublishedOn - | _ -> FormatDateTime.time this.TimeZone this.Post.UpdatedOn - /// The number of comments - member this.CommentCount = - match post.Comments |> List.length with - | 0 -> Strings.get "NoComments" - | 1 -> Strings.get "OneComment" - | x -> String.Format (Strings.get "XComments", x) - /// Tags - member this.Tags = - match List.length this.Post.Tags with - | 0 -> "" - | 1 | 2 | 3 | 4 | 5 -> this.Post.Tags |> pipedTags - | count -> sprintf "%s %s" (this.Post.Tags |> List.take 3 |> pipedTags) - (System.String.Format(Strings.get "andXMore", count - 3)) - - -/// Model for all page-of-posts pages -type PostsModel (ctx, webLog) = - inherit MyWebLogModel (ctx, webLog) - /// The subtitle for the page - member val Subtitle : string option = None with get, set - /// The posts to display - member val Posts : PostForDisplay list = [] with get, set - /// The page number of the post list - member val PageNbr = 0 with get, set - /// Whether there is a newer page of posts for the list - member val HasNewer = false with get, set - /// Whether there is an older page of posts for the list - member val HasOlder = true with get, set - /// The prefix for the next/prior links - member val UrlPrefix = "" with get, set - - /// The link for the next newer page of posts - member this.NewerLink = - match this.UrlPrefix = "/posts" && this.PageNbr = 2 && this.WebLog.DefaultPage = "posts" with - | true -> "/" - | _ -> sprintf "%s/page/%i" this.UrlPrefix (this.PageNbr - 1) - - /// The link for the prior (older) page of posts - member this.OlderLink = sprintf "%s/page/%i" this.UrlPrefix (this.PageNbr + 1) - - -/// 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 : string[] = [||] with get, set - /// Whether the post should be published - member val PublishNow = false with get, set - - /// Fill the form with applicable values from a post - member this.ForPost (post : Post) = - this.Title <- post.Title - this.Permalink <- post.Permalink - this.Tags <- match List.isEmpty post.Tags with - | true -> "" - | _ -> List.reduce (fun acc x -> sprintf "%s, %s" acc x) post.Tags - this.Categories <- List.toArray post.CategoryIds - this.PublishNow <- post.Status = PostStatus.Published || "new" = post.Id - this - - /// Fill the form with applicable values from a revision - member this.ForRevision rev = - this.Source <- rev.SourceType - this.Text <- rev.Text - this - -/// Category information for display -type DisplayCategory = { - Id : string - Indent : string - Name : string - Description : string - IsChecked : bool - } -with - /// Create a display category - static member Create (cat : Category, indent) isChecked = - { Id = cat.Id - Indent = String.replicate indent "     " - Name = WebUtility.HtmlEncode cat.Name - IsChecked = isChecked - Description = WebUtility.HtmlEncode (match cat.Description with Some d -> d | _ -> cat.Name) - } - /// The "checked" attribute for this category - member this.CheckedAttr - with get() = match this.IsChecked with true -> "checked=\"checked\"" | _ -> "" - -/// 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 : DisplayCategory list = [] 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 - /// The "checked" attribute for the Publish Now box - member this.PublishNowCheckedAttr = match this.Form.PublishNow with true -> "checked=\"checked\"" | _ -> "" - - -// ---- User models ---- - -/// Form for the log on page -type LogOnForm () = - /// The URL to which the user will be directed upon successful log on - member val ReturnUrl = "" with get, set - /// The e-mail address - member val Email = "" with get, set - /// The user's passwor - member val Password = "" with get, set - - -/// Model to support the user log on page -type LogOnModel (ctx, webLog) = - inherit MyWebLogModel (ctx, webLog) - /// The log on form - member val Form = LogOnForm () with get, set diff --git a/src/MyWebLog.App/en-US.json b/src/MyWebLog.App/en-US.json deleted file mode 100644 index be2715a..0000000 --- a/src/MyWebLog.App/en-US.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "Action": "Action", - "Added": "Added", - "AddNew": "Add New", - "AddNewCategory": "Add New Category", - "AddNewPage": "Add New Page", - "AddNewPost": "Add New Post", - "Admin": "Admin", - "AndPublished": " and Published", - "andXMore": "and {0} more...", - "at": "at", - "BackToCategoryList": "Back to Category List", - "BackToPageList": "Back to Page List", - "BackToPostList": "Back to Post List", - "Categories": "Categories", - "Category": "Category", - "CategoryDeleteWarning": "Are you sure you wish to delete the category", - "Close": "Close", - "Comments": "Comments", - "Dashboard": "Dashboard", - "Date": "Date", - "Delete": "Delete", - "Description": "Description", - "Edit": "Edit", - "EditCategory": "Edit Category", - "EditPage": "Edit Page", - "EditPost": "Edit Post", - "EmailAddress": "E-mail Address", - "ErrBadAppConfig": "Could not convert config.json to myWebLog configuration", - "ErrBadLogOnAttempt": "Invalid e-mail address or password", - "ErrDataConfig": "Could not convert data-config.json to RethinkDB connection", - "ErrNotConfigured": "is not properly configured for myWebLog", - "Error": "Error", - "LastUpdated": "Last Updated", - "LastUpdatedDate": "Last Updated Date", - "ListAll": "List All", - "LoadedIn": "Loaded in", - "LogOff": "Log Off", - "LogOn": "Log On", - "MsgCategoryDeleted": "Deleted category {0} successfully", - "MsgCategoryEditSuccess": "{0} category successfully", - "MsgLogOffSuccess": "Log off successful | Have a nice day!", - "MsgLogOnSuccess": "Log on successful | Welcome to myWebLog!", - "MsgPageDeleted": "Deleted page successfully", - "MsgPageEditSuccess": "{0} page successfully", - "MsgPostEditSuccess": "{0}{1} post successfully", - "Name": "Name", - "NewerPosts": "Newer Posts", - "NextPost": "Next Post", - "NoComments": "No Comments", - "NoParent": "No Parent", - "OlderPosts": "Older Posts", - "OneComment": "1 Comment", - "PageDeleteWarning": "Are you sure you wish to delete the page", - "PageDetails": "Page Details", - "PageHash": "Page #", - "Pages": "Pages", - "ParentCategory": "Parent Category", - "Password": "Password", - "Permalink": "Permalink", - "PermanentLinkTo": "Permanent Link to", - "PostDetails": "Post Details", - "Posts": "Posts", - "PostsTagged": "Posts Tagged", - "PostStatus": "Post Status", - "PoweredBy": "Powered by", - "PreviousPost": "Previous Post", - "PublishedDate": "Published Date", - "PublishThisPost": "Publish This Post", - "Save": "Save", - "Seconds": "Seconds", - "ShowInPageList": "Show in Page List", - "Slug": "Slug", - "startingWith": "starting with", - "Status": "Status", - "Tags": "Tags", - "Time": "Time", - "Title": "Title", - "Updated": "Updated", - "View": "View", - "Warning": "Warning", - "XComments": "{0} Comments" -} diff --git a/src/MyWebLog.App/project.json b/src/MyWebLog.App/project.json deleted file mode 100644 index 3ed4776..0000000 --- a/src/MyWebLog.App/project.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "buildOptions": { - "compilerName": "fsc", - "compile": { - "includeFiles": [ - "AssemblyInfo.fs", - "Entities/Entities.fs", - "Entities/IMyWebLogData.fs", - "Data/Extensions.fs", - "Data/Table.fs", - "Data/DataConfig.fs", - "Data/Category.fs", - "Data/Page.fs", - "Data/Post.fs", - "Data/User.fs", - "Data/WebLog.fs", - "Data/SetUp.fs", - "Data/RethinkMyWebLogData.fs", - "Logic/Category.fs", - "Logic/Page.fs", - "Logic/Post.fs", - "Logic/User.fs", - "Logic/WebLog.fs", - "Keys.fs", - "AppConfig.fs", - "Strings.fs", - "ViewModels.fs", - "Modules/ModuleExtensions.fs", - "Modules/AdminModule.fs", - "Modules/CategoryModule.fs", - "Modules/PageModule.fs", - "Modules/PostModule.fs", - "Modules/UserModule.fs", - "App.fs" - ] - }, - "embed": { - "include": [ "en-US.json" ] - } - }, - "dependencies": { - "Nancy": "2.0.0-barneyrubble", - "Nancy.Authentication.Forms": "2.0.0-barneyrubble", - "Nancy.Session.Persistable": "0.9.1-pre", - "Nancy.Session.RethinkDB": "0.9.1-pre", - "Newtonsoft.Json": "9.0.1", - "NodaTime": "2.0.0-alpha20160729", - "RethinkDb.Driver": "2.3.15", - "Suave": "2.0.0-rc2" - }, - "frameworks": { - "netstandard1.6": { - "imports": "dnxcore50", - "dependencies": { - "Microsoft.FSharp.Core.netcore": "1.0.0-alpha-161111", - "NETStandard.Library": "1.6.0" - } - } - }, - "tools": { - "dotnet-compile-fsc": "1.0.0-preview2-*" - }, - "version": "0.9.2" -} diff --git a/src/MyWebLog.Old/MyWebLog.xproj b/src/MyWebLog.Old/MyWebLog.xproj deleted file mode 100644 index 4fca1d9..0000000 --- a/src/MyWebLog.Old/MyWebLog.xproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - - - B9F6DB52-65A1-4C2A-8C97-739E08A1D4FB - MyWebLog - .\obj - .\bin\ - v4.5.2 - - - - 2.0 - - - diff --git a/src/MyWebLog.Old/Program.cs b/src/MyWebLog.Old/Program.cs deleted file mode 100644 index 01ea1cf..0000000 --- a/src/MyWebLog.Old/Program.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace MyWebLog -{ - class Program - { - static void Main(string[] args) - { - App.Run(); - } - } -} diff --git a/src/MyWebLog.Old/Properties/AssemblyInfo.cs b/src/MyWebLog.Old/Properties/AssemblyInfo.cs deleted file mode 100644 index f1792ff..0000000 --- a/src/MyWebLog.Old/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -[assembly: AssemblyTitle("MyWebLog")] -[assembly: AssemblyDescription("A lightweight blogging platform built on Nancy, and RethinkDB")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MyWebLog")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: ComVisible(false)] -[assembly: Guid("b9f6db52-65a1-4c2a-8c97-739e08a1d4fb")] -[assembly: AssemblyVersion("0.9.2.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/MyWebLog.Old/config.json b/src/MyWebLog.Old/config.json deleted file mode 100644 index 53897f0..0000000 --- a/src/MyWebLog.Old/config.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - // https://www.grc.com/passwords.htm is a great source of high-entropy passwords for these first 4 settings. - // Although what is there looks strong, keep in mind that it's what's in source control, so all instances of myWebLog - // could be using these values; that severly decreases their usefulness. :) - // - // WARNING: Changing this first one will render every single user's login inaccessible, including yours. Only do - // this if you are editing this file before setting up an instance, or if that is what you intend to do. - "password-salt": "3RVkw1jESpLFHr8F3WTThSbFnO3tFrMIckQsKzc9dymzEEXUoUS7nurF4rGpJ8Z", - // Changing any of these next 3 will render all current logins invalid, and the user will be force to reauthenticate. - "auth-salt": "2TweL5wcyGWg5CmMqZSZMonbe9xqQ2Q4vDNeysFRaUgVs4BpFZL85Iew79tn2IJ", - "encryption-passphrase": "jZjY6XyqUZypBcT0NaDXjEKc8xUjB4eb4V9EDHDedadRLuRUeRvIQx67yhx6bQP", - "hmac-passphrase": "42dzKb93X8YUkK8ms8JldjtkEvCKnPQGWCkO2yFaZ7lkNwECGCX00xzrx5ZSElO", - "data": { - "database": "myWebLog", - "hostname": "localhost" - } -} diff --git a/src/MyWebLog.Old/content/logo-dark.png b/src/MyWebLog.Old/content/logo-dark.png deleted file mode 100644 index 19bdcca..0000000 Binary files a/src/MyWebLog.Old/content/logo-dark.png and /dev/null differ diff --git a/src/MyWebLog.Old/content/logo-light.png b/src/MyWebLog.Old/content/logo-light.png deleted file mode 100644 index c2d3357..0000000 Binary files a/src/MyWebLog.Old/content/logo-light.png and /dev/null differ diff --git a/src/MyWebLog.Old/project.json b/src/MyWebLog.Old/project.json deleted file mode 100644 index 86ea157..0000000 --- a/src/MyWebLog.Old/project.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "buildOptions": { - "emitEntryPoint": true, - "copyToOutput": { - "include": [ "views", "content", "config.json" ] - } - }, - "dependencies": { - "MyWebLog.App": "0.9.2", - }, - "frameworks": { - "netcoreapp1.0": { - "dependencies": { - "Microsoft.NETCore.App": { - "type": "platform", - "version": "1.1.0" - } - }, - "imports": "dnxcore50" - } - }, - "publishOptions": { - "include": [ "views", "content", "config.json" ] - }, - "version": "0.9.2" -} diff --git a/src/MyWebLog.Old/views/admin/admin-layout.html b/src/MyWebLog.Old/views/admin/admin-layout.html deleted file mode 100644 index 7974c7e..0000000 --- a/src/MyWebLog.Old/views/admin/admin-layout.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - @Model.PageTitle | @Translate.Admin | @Model.WebLog.Name - - - - - - -
- -
-
- @Each.Messages - @Current.ToDisplay - @EndEach - @Section['Content']; -
-
-
-
-
@Model.FooterLogoLight  
-
-
-
- - - - @Section['Scripts']; - - \ No newline at end of file diff --git a/src/MyWebLog.Old/views/admin/category/edit.html b/src/MyWebLog.Old/views/admin/category/edit.html deleted file mode 100644 index ca39310..0000000 --- a/src/MyWebLog.Old/views/admin/category/edit.html +++ /dev/null @@ -1,55 +0,0 @@ -@Master['admin/admin-layout'] - -@Section['Content'] -
- @AntiForgeryToken -
-
- - @Translate.BackToCategoryList - -
- - -
-
-
-
-
-
- - -
-
- - -
-
-
-
- - -
-
-

- -

-
-
-
-@EndSection - -@Section['Scripts'] - -@EndSection diff --git a/src/MyWebLog.Old/views/admin/category/list.html b/src/MyWebLog.Old/views/admin/category/list.html deleted file mode 100644 index 14d7faa..0000000 --- a/src/MyWebLog.Old/views/admin/category/list.html +++ /dev/null @@ -1,51 +0,0 @@ -@Master['admin/admin-layout'] - -@Section['Content'] - -
- - - - - - - @Each.Categories - - - - - - @EndEach -
@Translate.Action@Translate.Category@Translate.Description
- @Translate.Edit   - - @Translate.Delete - - @Current.ListName - @If.HasDescription - @Current.Category.Description.Value - @EndIf - @IfNot.HasDescription -   - @EndIf -
-
-
- @AntiForgeryToken -
-@EndSection - -@Section['Scripts'] - -@EndSection \ No newline at end of file diff --git a/src/MyWebLog.Old/views/admin/content/admin.css b/src/MyWebLog.Old/views/admin/content/admin.css deleted file mode 100644 index 7a8dc5c..0000000 --- a/src/MyWebLog.Old/views/admin/content/admin.css +++ /dev/null @@ -1,5 +0,0 @@ -footer { - background-color: #808080; - border-top: solid 1px black; - color: white; -} \ No newline at end of file diff --git a/src/MyWebLog.Old/views/admin/content/tinymce-init.js b/src/MyWebLog.Old/views/admin/content/tinymce-init.js deleted file mode 100644 index 48c9339..0000000 --- a/src/MyWebLog.Old/views/admin/content/tinymce-init.js +++ /dev/null @@ -1,10 +0,0 @@ -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" -}) \ No newline at end of file diff --git a/src/MyWebLog.Old/views/admin/dashboard.html b/src/MyWebLog.Old/views/admin/dashboard.html deleted file mode 100644 index c4e75a5..0000000 --- a/src/MyWebLog.Old/views/admin/dashboard.html +++ /dev/null @@ -1,31 +0,0 @@ -@Master['admin/admin-layout'] - -@Section['Content'] -
-
-

@Translate.Posts  @Model.Posts

-

- @Translate.ListAll -     - @Translate.AddNew -

-
-
-

@Translate.Pages  @Model.Pages

-

- @Translate.ListAll -     - @Translate.AddNew -

-
-
-

@Translate.Categories  @Model.Categories

-

- @Translate.ListAll -     - @Translate.AddNew -

-
-
-
-@EndSection diff --git a/src/MyWebLog.Old/views/admin/page/edit.html b/src/MyWebLog.Old/views/admin/page/edit.html deleted file mode 100644 index facb824..0000000 --- a/src/MyWebLog.Old/views/admin/page/edit.html +++ /dev/null @@ -1,61 +0,0 @@ -@Master['admin/admin-layout'] - -@Section['Content'] -
- @AntiForgeryToken -
-
- - @Translate.BackToPageList - -
- - -
-
- - -

@Translate.startingWith //@Model.WebLog.UrlBase/

-
- - -
- -
-
-
-
-
@Translate.PageDetails
-
- @IfNot.isNew -
- -

@Model.PublishedDate
@Model.PublishedTime

-
-
- -

@Model.LastUpdatedDate
@Model.LastUpdatedTime

-
- @EndIf -
- -   -
-
-
-
-

-
-
-
-
-@EndSection - -@Section['Scripts'] - - -@EndSection diff --git a/src/MyWebLog.Old/views/admin/page/list.html b/src/MyWebLog.Old/views/admin/page/list.html deleted file mode 100644 index 7e5f759..0000000 --- a/src/MyWebLog.Old/views/admin/page/list.html +++ /dev/null @@ -1,42 +0,0 @@ -@Master['admin/admin-layout'] - -@Section['Content'] - -
- - - - - - @Each.Pages - - - - - @EndEach -
@Translate.Title@Translate.LastUpdated
- @Current.Page.Title
- @Translate.View   - @Translate.Edit   - @Translate.Delete -
@Current.UpdatedDate
@Translate.at @Current.UpdatedTime
-
-
- @AntiForgeryToken -
-@EndSection - -@Section['Scripts'] - -@EndSection diff --git a/src/MyWebLog.Old/views/admin/post/edit.html b/src/MyWebLog.Old/views/admin/post/edit.html deleted file mode 100644 index 39963c7..0000000 --- a/src/MyWebLog.Old/views/admin/post/edit.html +++ /dev/null @@ -1,90 +0,0 @@ -@Master['admin/admin-layout'] - -@Section['Content'] -
- @AntiForgeryToken -
-
- - @Translate.BackToPostList - -
- - -
-
- - -

@Translate.startingWith //@Model.WebLog.UrlBase/

-
- - -
- -
-
- - -
-
-
-
-
-

@Translate.PostDetails

-
-
-
- -

@Model.Post.Status

-
- @If.IsPublished -
- -

@Model.PublishedDate
@Model.PublishedTime

-
- @EndIf -
-
-
-
-

@Translate.Categories

-
-
- @Each.Categories - @Current.Indent - -   - -
- @EndEach -
-
-
- @If.IsPublished - - @EndIf - @IfNot.IsPublished -
- -   -
- @EndIf -

- -

-
-
-
-
-@EndSection - -@Section['Scripts'] - - -@EndSection diff --git a/src/MyWebLog.Old/views/admin/post/list.html b/src/MyWebLog.Old/views/admin/post/list.html deleted file mode 100644 index ee9cf74..0000000 --- a/src/MyWebLog.Old/views/admin/post/list.html +++ /dev/null @@ -1,49 +0,0 @@ -@Master['admin/admin-layout'] - -@Section['Content'] - -
- - - - - - - - @Each.Posts - - - - - - - @EndEach -
@Translate.Date@Translate.Title@Translate.Status@Translate.Tags
- @Current.PublishedDate
- @Translate.at @Current.PublishedTime -
- @Current.Post.Title
- @Translate.View  |  - @Translate.Edit  |  - @Translate.Delete -
@Current.Post.Status@Current.Tags
-
-
-
- @If.HasNewer -

«  @Translate.NewerPosts

- @EndIf -
-
- @If.HasOlder -

@Translate.OlderPosts  »

- @EndIf -
-
-@EndSection diff --git a/src/MyWebLog.Old/views/admin/user/log-on.html b/src/MyWebLog.Old/views/admin/user/log-on.html deleted file mode 100644 index 2115f3c..0000000 --- a/src/MyWebLog.Old/views/admin/user/log-on.html +++ /dev/null @@ -1,41 +0,0 @@ -@Master['admin/admin-layout'] - -@Section['Content'] -
- @AntiForgeryToken - -
-
-
- - -
-
-
-
-
-
-
- - -
-
-
-
-
-

-
- -

-
-
-
-@EndSection - -@Section['Scripts'] - -@EndSection diff --git a/src/MyWebLog.Old/views/themes/default/comment.html b/src/MyWebLog.Old/views/themes/default/comment.html deleted file mode 100644 index 2c7b34b..0000000 --- a/src/MyWebLog.Old/views/themes/default/comment.html +++ /dev/null @@ -1,4 +0,0 @@ -

- @Model.Commentor    @Model.CommentedOn -

-@Model.Comment.Text \ No newline at end of file diff --git a/src/MyWebLog.Old/views/themes/default/content/bootstrap-theme.css b/src/MyWebLog.Old/views/themes/default/content/bootstrap-theme.css deleted file mode 100644 index b0fdfcb..0000000 --- a/src/MyWebLog.Old/views/themes/default/content/bootstrap-theme.css +++ /dev/null @@ -1,476 +0,0 @@ -/*! - * Bootstrap v3.3.4 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ - -.btn-default, -.btn-primary, -.btn-success, -.btn-info, -.btn-warning, -.btn-danger { - text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); -} -.btn-default:active, -.btn-primary:active, -.btn-success:active, -.btn-info:active, -.btn-warning:active, -.btn-danger:active, -.btn-default.active, -.btn-primary.active, -.btn-success.active, -.btn-info.active, -.btn-warning.active, -.btn-danger.active { - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); -} -.btn-default .badge, -.btn-primary .badge, -.btn-success .badge, -.btn-info .badge, -.btn-warning .badge, -.btn-danger .badge { - text-shadow: none; -} -.btn:active, -.btn.active { - background-image: none; -} -.btn-default { - text-shadow: 0 1px 0 #fff; - background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); - background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); - background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - background-repeat: repeat-x; - border-color: #dbdbdb; - border-color: #ccc; -} -.btn-default:hover, -.btn-default:focus { - background-color: #e0e0e0; - background-position: 0 -15px; -} -.btn-default:active, -.btn-default.active { - background-color: #e0e0e0; - border-color: #dbdbdb; -} -.btn-default.disabled, -.btn-default:disabled, -.btn-default[disabled] { - background-color: #e0e0e0; - background-image: none; -} -.btn-primary { - background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); - background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - background-repeat: repeat-x; - border-color: #245580; -} -.btn-primary:hover, -.btn-primary:focus { - background-color: #265a88; - background-position: 0 -15px; -} -.btn-primary:active, -.btn-primary.active { - background-color: #265a88; - border-color: #245580; -} -.btn-primary.disabled, -.btn-primary:disabled, -.btn-primary[disabled] { - background-color: #265a88; - background-image: none; -} -.btn-success { - background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); - background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); - background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - background-repeat: repeat-x; - border-color: #3e8f3e; -} -.btn-success:hover, -.btn-success:focus { - background-color: #419641; - background-position: 0 -15px; -} -.btn-success:active, -.btn-success.active { - background-color: #419641; - border-color: #3e8f3e; -} -.btn-success.disabled, -.btn-success:disabled, -.btn-success[disabled] { - background-color: #419641; - background-image: none; -} -.btn-info { - background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); - background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); - background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - background-repeat: repeat-x; - border-color: #28a4c9; -} -.btn-info:hover, -.btn-info:focus { - background-color: #2aabd2; - background-position: 0 -15px; -} -.btn-info:active, -.btn-info.active { - background-color: #2aabd2; - border-color: #28a4c9; -} -.btn-info.disabled, -.btn-info:disabled, -.btn-info[disabled] { - background-color: #2aabd2; - background-image: none; -} -.btn-warning { - background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); - background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); - background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - background-repeat: repeat-x; - border-color: #e38d13; -} -.btn-warning:hover, -.btn-warning:focus { - background-color: #eb9316; - background-position: 0 -15px; -} -.btn-warning:active, -.btn-warning.active { - background-color: #eb9316; - border-color: #e38d13; -} -.btn-warning.disabled, -.btn-warning:disabled, -.btn-warning[disabled] { - background-color: #eb9316; - background-image: none; -} -.btn-danger { - background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); - background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); - background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - background-repeat: repeat-x; - border-color: #b92c28; -} -.btn-danger:hover, -.btn-danger:focus { - background-color: #c12e2a; - background-position: 0 -15px; -} -.btn-danger:active, -.btn-danger.active { - background-color: #c12e2a; - border-color: #b92c28; -} -.btn-danger.disabled, -.btn-danger:disabled, -.btn-danger[disabled] { - background-color: #c12e2a; - background-image: none; -} -.thumbnail, -.img-thumbnail { - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); - box-shadow: 0 1px 2px rgba(0, 0, 0, .075); -} -.dropdown-menu > li > a:hover, -.dropdown-menu > li > a:focus { - background-color: #e8e8e8; - background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); - background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); - background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); - background-repeat: repeat-x; -} -.dropdown-menu > .active > a, -.dropdown-menu > .active > a:hover, -.dropdown-menu > .active > a:focus { - background-color: #2e6da4; - background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); - background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); - background-repeat: repeat-x; -} -.navbar-default { - background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); - background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); - background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - background-repeat: repeat-x; - border-radius: 4px; - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); -} -.navbar-default .navbar-nav > .open > a, -.navbar-default .navbar-nav > .active > a { - background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); - background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); - background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); - background-repeat: repeat-x; - -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); - box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); -} -.navbar-brand, -.navbar-nav > li > a { - text-shadow: 0 1px 0 rgba(255, 255, 255, .25); -} -.navbar-inverse { - background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); - background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); - background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); - filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); - background-repeat: repeat-x; -} -.navbar-inverse .navbar-nav > .open > a, -.navbar-inverse .navbar-nav > .active > a { - background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); - background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); - background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); - background-repeat: repeat-x; - -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); - box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); -} -.navbar-inverse .navbar-brand, -.navbar-inverse .navbar-nav > li > a { - text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); -} -.navbar-static-top, -.navbar-fixed-top, -.navbar-fixed-bottom { - border-radius: 0; -} -@media (max-width: 767px) { - .navbar .navbar-nav .open .dropdown-menu > .active > a, - .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, - .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { - color: #fff; - background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); - background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); - background-repeat: repeat-x; - } -} -.alert { - text-shadow: 0 1px 0 rgba(255, 255, 255, .2); - -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); -} -.alert-success { - background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); - background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); - background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); - background-repeat: repeat-x; - border-color: #b2dba1; -} -.alert-info { - background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); - background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); - background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); - background-repeat: repeat-x; - border-color: #9acfea; -} -.alert-warning { - background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); - background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); - background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); - background-repeat: repeat-x; - border-color: #f5e79e; -} -.alert-danger { - background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); - background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); - background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); - background-repeat: repeat-x; - border-color: #dca7a7; -} -.progress { - background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); - background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); - background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); - background-repeat: repeat-x; -} -.progress-bar { - background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); - background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); - background-repeat: repeat-x; -} -.progress-bar-success { - background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); - background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); - background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); - background-repeat: repeat-x; -} -.progress-bar-info { - background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); - background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); - background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); - background-repeat: repeat-x; -} -.progress-bar-warning { - background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); - background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); - background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); - background-repeat: repeat-x; -} -.progress-bar-danger { - background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); - background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); - background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); - background-repeat: repeat-x; -} -.progress-bar-striped { - background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); - background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -} -.list-group { - border-radius: 4px; - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); - box-shadow: 0 1px 2px rgba(0, 0, 0, .075); -} -.list-group-item.active, -.list-group-item.active:hover, -.list-group-item.active:focus { - text-shadow: 0 -1px 0 #286090; - background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); - background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); - background-repeat: repeat-x; - border-color: #2b669a; -} -.list-group-item.active .badge, -.list-group-item.active:hover .badge, -.list-group-item.active:focus .badge { - text-shadow: none; -} -.panel { - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); - box-shadow: 0 1px 2px rgba(0, 0, 0, .05); -} -.panel-default > .panel-heading { - background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); - background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); - background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); - background-repeat: repeat-x; -} -.panel-primary > .panel-heading { - background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); - background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); - background-repeat: repeat-x; -} -.panel-success > .panel-heading { - background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); - background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); - background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); - background-repeat: repeat-x; -} -.panel-info > .panel-heading { - background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); - background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); - background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); - background-repeat: repeat-x; -} -.panel-warning > .panel-heading { - background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); - background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); - background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); - background-repeat: repeat-x; -} -.panel-danger > .panel-heading { - background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); - background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); - background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); - background-repeat: repeat-x; -} -.well { - background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); - background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); - background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); - background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); - filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); - background-repeat: repeat-x; - border-color: #dcdcdc; - -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); - box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); -} -/*# sourceMappingURL=bootstrap-theme.css.map */ diff --git a/src/MyWebLog.Old/views/themes/default/content/bootstrap-theme.css.map b/src/MyWebLog.Old/views/themes/default/content/bootstrap-theme.css.map deleted file mode 100644 index 5a12d63..0000000 --- a/src/MyWebLog.Old/views/themes/default/content/bootstrap-theme.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","bootstrap-theme.css","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAcA;;;;;;EAME,0CAAA;ECgDA,6FAAA;EACQ,qFAAA;EC5DT;AFgBC;;;;;;;;;;;;EC2CA,0DAAA;EACQ,kDAAA;EC7CT;AFVD;;;;;;EAiBI,mBAAA;EECH;AFiCC;;EAEE,wBAAA;EE/BH;AFoCD;EGnDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EAgC2C,2BAAA;EAA2B,oBAAA;EEzBvE;AFLC;;EAEE,2BAAA;EACA,8BAAA;EEOH;AFJC;;EAEE,2BAAA;EACA,uBAAA;EEMH;AFHC;;;EAGE,2BAAA;EACA,wBAAA;EEKH;AFUD;EGpDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEgCD;AF9BC;;EAEE,2BAAA;EACA,8BAAA;EEgCH;AF7BC;;EAEE,2BAAA;EACA,uBAAA;EE+BH;AF5BC;;;EAGE,2BAAA;EACA,wBAAA;EE8BH;AFdD;EGrDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEyDD;AFvDC;;EAEE,2BAAA;EACA,8BAAA;EEyDH;AFtDC;;EAEE,2BAAA;EACA,uBAAA;EEwDH;AFrDC;;;EAGE,2BAAA;EACA,wBAAA;EEuDH;AFtCD;EGtDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEkFD;AFhFC;;EAEE,2BAAA;EACA,8BAAA;EEkFH;AF/EC;;EAEE,2BAAA;EACA,uBAAA;EEiFH;AF9EC;;;EAGE,2BAAA;EACA,wBAAA;EEgFH;AF9DD;EGvDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EE2GD;AFzGC;;EAEE,2BAAA;EACA,8BAAA;EE2GH;AFxGC;;EAEE,2BAAA;EACA,uBAAA;EE0GH;AFvGC;;;EAGE,2BAAA;EACA,wBAAA;EEyGH;AFtFD;EGxDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEoID;AFlIC;;EAEE,2BAAA;EACA,8BAAA;EEoIH;AFjIC;;EAEE,2BAAA;EACA,uBAAA;EEmIH;AFhIC;;;EAGE,2BAAA;EACA,wBAAA;EEkIH;AFxGD;;EChBE,oDAAA;EACQ,4CAAA;EC4HT;AFnGD;;EGzEI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHwEF,2BAAA;EEyGD;AFvGD;;;EG9EI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8EF,2BAAA;EE6GD;AFpGD;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EJ6GA,oBAAA;EC/CA,6FAAA;EACQ,qFAAA;EC0JT;AF/GD;;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,0DAAA;EACQ,kDAAA;ECoKT;AF5GD;;EAEE,gDAAA;EE8GD;AF1GD;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EF+OD;AFlHD;;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,yDAAA;EACQ,iDAAA;EC0LT;AF5HD;;EAYI,2CAAA;EEoHH;AF/GD;;;EAGE,kBAAA;EEiHD;AF5FD;EAfI;;;IAGE,aAAA;IG3IF,0EAAA;IACA,qEAAA;IACA,+FAAA;IAAA,wEAAA;IACA,6BAAA;IACA,wHAAA;ID0PD;EACF;AFxGD;EACE,+CAAA;ECzGA,4FAAA;EACQ,oFAAA;ECoNT;AFhGD;EGpKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4GD;AFvGD;EGrKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoHD;AF9GD;EGtKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4HD;AFrHD;EGvKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoID;AFrHD;EG/KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDuSH;AFlHD;EGzLI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8SH;AFxHD;EG1LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDqTH;AF9HD;EG3LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED4TH;AFpID;EG5LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDmUH;AF1ID;EG7LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED0UH;AF7ID;EGhKI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDgTH;AFzID;EACE,oBAAA;EC5JA,oDAAA;EACQ,4CAAA;ECwST;AF1ID;;;EAGE,+BAAA;EGjNE,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH+MF,uBAAA;EEgJD;AFrJD;;;EAQI,mBAAA;EEkJH;AFxID;ECjLE,mDAAA;EACQ,2CAAA;EC4TT;AFlID;EG1OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED+WH;AFxID;EG3OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDsXH;AF9ID;EG5OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED6XH;AFpJD;EG7OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDoYH;AF1JD;EG9OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED2YH;AFhKD;EG/OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDkZH;AFhKD;EGtPI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHoPF,uBAAA;ECzMA,2FAAA;EACQ,mFAAA;ECgXT","file":"bootstrap-theme.css","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &:disabled,\n &[disabled] {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n",".btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default:disabled,\n.btn-default[disabled] {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary:disabled,\n.btn-primary[disabled] {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success:disabled,\n.btn-success[disabled] {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info:disabled,\n.btn-info[disabled] {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning:disabled,\n.btn-warning[disabled] {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger:disabled,\n.btn-danger[disabled] {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]} \ No newline at end of file diff --git a/src/MyWebLog.Old/views/themes/default/content/bootstrap-theme.min.css b/src/MyWebLog.Old/views/themes/default/content/bootstrap-theme.min.css deleted file mode 100644 index cefa3d1..0000000 --- a/src/MyWebLog.Old/views/themes/default/content/bootstrap-theme.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Bootstrap v3.3.4 (http://getbootstrap.com) - * Copyright 2011-2015 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} \ No newline at end of file diff --git a/src/MyWebLog.Old/views/themes/default/footer.html b/src/MyWebLog.Old/views/themes/default/footer.html deleted file mode 100644 index ec3f017..0000000 --- a/src/MyWebLog.Old/views/themes/default/footer.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
-
-
-
- @Model.FooterLogoDark -
-
-
-
\ No newline at end of file diff --git a/src/MyWebLog.Old/views/themes/default/index-content.html b/src/MyWebLog.Old/views/themes/default/index-content.html deleted file mode 100644 index 18a7f6b..0000000 --- a/src/MyWebLog.Old/views/themes/default/index-content.html +++ /dev/null @@ -1,43 +0,0 @@ -@Each.Messages - @Current.ToDisplay -@EndEach -@If.SubTitle.IsSome -

- @Model.SubTitle -

-@EndIf -@Each.Posts -
-
-
-

- @Current.Post.Title -

-

- @Current.PublishedDate   - @Current.PublishedTime   - @Current.CommentCount -

- @Current.Post.Text -
-
-
-
-@EndEach -
-
- @If.HasNewer -

- @Translate.NewerPosts -

- @EndIf -
-
- @If.HasOlder -

- @Translate.OlderPosts -

- @EndIf -
-
\ No newline at end of file diff --git a/src/MyWebLog.Old/views/themes/default/index.html b/src/MyWebLog.Old/views/themes/default/index.html deleted file mode 100644 index 6ef4c3a..0000000 --- a/src/MyWebLog.Old/views/themes/default/index.html +++ /dev/null @@ -1,9 +0,0 @@ -@Master['themes/default/layout'] - -@Section['Content'] - @Partial['themes/default/index-content', Model] -@EndSection - -@Section['Footer'] - @Partial['themes/default/footer', Model] -@EndSection diff --git a/src/MyWebLog.Old/views/themes/default/layout.html b/src/MyWebLog.Old/views/themes/default/layout.html deleted file mode 100644 index ed29d2c..0000000 --- a/src/MyWebLog.Old/views/themes/default/layout.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - @Model.DisplayPageTitle - - - - - - @Section['Head']; - - -
- -
-
- @Section['Content']; -
- @Section['Footer']; - - - @Section['Scripts']; - - \ No newline at end of file diff --git a/src/MyWebLog.Old/views/themes/default/page-content.html b/src/MyWebLog.Old/views/themes/default/page-content.html deleted file mode 100644 index 731e6b8..0000000 --- a/src/MyWebLog.Old/views/themes/default/page-content.html +++ /dev/null @@ -1,4 +0,0 @@ -
-

@Model.Page.Title

- @Model.Page.Text -
\ No newline at end of file diff --git a/src/MyWebLog.Old/views/themes/default/page.html b/src/MyWebLog.Old/views/themes/default/page.html deleted file mode 100644 index d3d60c1..0000000 --- a/src/MyWebLog.Old/views/themes/default/page.html +++ /dev/null @@ -1,9 +0,0 @@ -@Master['themes/default/layout'] - -@Section['Content'] - @Partial['themes/default/page-content', Model] -@EndSection - -@Section['Footer'] - @Partial['themes/default/footer', Model] -@EndSection diff --git a/src/MyWebLog.Old/views/themes/default/single-content.html b/src/MyWebLog.Old/views/themes/default/single-content.html deleted file mode 100644 index c2e3f94..0000000 --- a/src/MyWebLog.Old/views/themes/default/single-content.html +++ /dev/null @@ -1,67 +0,0 @@ -
-
-

@Model.Post.Title

-
-
-
-

- @Model.PublishedDate   - @Model.PublishedTime   - @Model.CommentCount       - @Each.Post.Categories - - - @Current.Name -     - - @EndEach -

-
-
-
-
@Model.Post.Text
-
- @If.HasTags -
-
- @Each.Tags - - - @Current.Item1 -     - - @EndEach -
-
- @EndIf -
-
-

-
-
-
- @Each.Comments - @Partial['themes/default/comment', @Current] - @EndEach -
-
-
-

-
-
-
- @If.HasNewer - - «  @Model.NewerPost.Value.Title - - @EndIf -
-
- @If.HasOlder - - @Model.OlderPost.Value.Title  » - - @EndIf -
-
\ No newline at end of file diff --git a/src/MyWebLog.Old/views/themes/default/single.html b/src/MyWebLog.Old/views/themes/default/single.html deleted file mode 100644 index 894c3b7..0000000 --- a/src/MyWebLog.Old/views/themes/default/single.html +++ /dev/null @@ -1,9 +0,0 @@ -@Master['themes/default/layout'] - -@Section['Content'] - @Partial['themes/default/single-content', Model] -@EndSection - -@Section['Footer'] - @Partial['themes/default/footer', Model] -@EndSection diff --git a/src/MyWebLog.Tests/MyWebLog.Tests.fs b/src/MyWebLog.Tests/MyWebLog.Tests.fs deleted file mode 100644 index 9210d97..0000000 --- a/src/MyWebLog.Tests/MyWebLog.Tests.fs +++ /dev/null @@ -1,4 +0,0 @@ -namespace MyWebLog.Web - -type Web() = - member this.X = "F#" diff --git a/src/MyWebLog.Tests/MyWebLog.Tests.fsproj b/src/MyWebLog.Tests/MyWebLog.Tests.fsproj deleted file mode 100644 index 74198ff..0000000 --- a/src/MyWebLog.Tests/MyWebLog.Tests.fsproj +++ /dev/null @@ -1,70 +0,0 @@ - - - - - Debug - AnyCPU - 2.0 - 07e60874-6cf5-4d53-aee0-f17ef28228dd - Library - MyWebLog.Tests - MyWebLog.Tests - v4.5.2 - 4.4.0.0 - MyWebLog.Tests - - - true - full - false - false - bin\Debug\ - DEBUG;TRACE - 3 - bin\Debug\MyWebLog.Tests.xml - - - pdbonly - true - true - bin\Release\ - TRACE - 3 - bin\Release\MyWebLog.Tests.xml - - - - - True - - - - - - - - - - 11 - - - - - $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets - - - - - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets - - - - - -