├── Website ├── Icon.png ├── favicon.ico ├── bin │ ├── AjaxMin.dll │ ├── ImageCruncher.dll │ ├── AjaxMin.dll.refresh │ ├── System.Web.Razor.dll │ ├── WebMarkupMin.Core.dll │ ├── system.web.helpers.dll │ ├── system.web.webpages.dll │ ├── CookComputing.XmlRpcV2.dll │ ├── Microsoft.Web.Infrastructure.dll.refresh │ ├── System.Web.Helpers.dll.refresh │ ├── System.Web.Razor.dll.refresh │ ├── System.Web.WebPages.Deployment.dll.refresh │ ├── WebMarkupMin.Core.dll.refresh │ ├── system.web.webpages.razor.dll │ ├── CookComputing.XmlRpcServerV2.dll │ ├── System.Web.WebPages.dll.refresh │ ├── microsoft.web.infrastructure.dll │ ├── CookComputing.XmlRpcV2.dll.refresh │ ├── system.web.webpages.deployment.dll │ ├── System.Web.WebPages.Razor.dll.refresh │ ├── CookComputing.XmlRpcServerV2.dll.refresh │ └── System.Web.WebPages.Deployment.xml ├── posts │ ├── files │ │ └── image.png │ ├── a5e4d75a-6629-4942-aba3-8ed020d933f4.xml │ └── 9fa751db-c036-435b-97e3-bd6373b0991b.xml ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── views │ ├── Robots │ │ ├── Robots.cshtml │ │ ├── Sitemap.cshtml │ │ └── RSD.cshtml │ ├── CommentForm.cshtml │ ├── Login.cshtml │ └── AdminMenu.cshtml ├── scripts │ ├── _references.js │ ├── npm.js │ ├── jquery.hotkeys.js │ ├── admin.js │ └── comments.js ├── 404.cshtml ├── packages.config ├── wlwmanifest.xml ├── themes │ ├── OffCanvas │ │ ├── Comment.cshtml │ │ ├── Post.cshtml │ │ ├── _Layout.cshtml │ │ └── site.css │ ├── OneColumn │ │ ├── Comment.cshtml │ │ ├── Post.cshtml │ │ ├── _Layout.cshtml │ │ └── site.css │ └── TwoColumns │ │ ├── Comment.cshtml │ │ ├── Post.cshtml │ │ ├── _Layout.cshtml │ │ └── site.css ├── Global.asax ├── app_code │ ├── handlers │ │ ├── MinifyHandler.cs │ │ ├── FeedHandler.cs │ │ ├── WhitespaceModule.cs │ │ ├── PostHandler.cs │ │ ├── MetaWeblogHandler.cs │ │ └── CommentHandler.cs │ └── code │ │ ├── Post.cs │ │ ├── Comment.cs │ │ ├── Storage.cs │ │ └── Blog.cs ├── Content │ └── admin.css ├── Index.cshtml └── Web.config ├── .github └── FUNDING.yml ├── packages ├── bootstrap.3.3.4 │ ├── content │ │ ├── Icon.png │ │ ├── fonts │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ └── glyphicons-halflings-regular.woff2 │ │ └── Scripts │ │ │ └── npm.js │ └── bootstrap.3.3.4.nupkg ├── jQuery.2.1.3 │ ├── jQuery.2.1.3.nupkg │ └── Tools │ │ ├── install.ps1 │ │ ├── uninstall.ps1 │ │ └── common.ps1 ├── repositories.config ├── AjaxMin.5.14.5506.26202 │ ├── lib │ │ ├── net20 │ │ │ └── AjaxMin.dll │ │ ├── net35 │ │ │ └── AjaxMin.dll │ │ └── net40 │ │ │ └── AjaxMin.dll │ ├── tools │ │ ├── net35 │ │ │ ├── AjaxMin.dll │ │ │ ├── AjaxMinTask.dll │ │ │ └── AjaxMin.targets │ │ └── net40 │ │ │ ├── AjaxMin.dll │ │ │ ├── AjaxMinTask.dll │ │ │ └── AjaxMin.targets │ └── AjaxMin.5.14.5506.26202.nupkg ├── xmlrpcnet.3.0.0.266 │ ├── xmlrpcnet.3.0.0.266.nupkg │ ├── lib │ │ ├── net20 │ │ │ └── CookComputing.XmlRpcV2.dll │ │ ├── sl3-wp │ │ │ └── CookComputing.XmlRpcPhone.dll │ │ └── sl4 │ │ │ └── CookComputing.XmlRpcSilverlight.dll │ └── xmlrpcnet.3.0.0.266.nuspec ├── WebMarkupMin.Core.2.0.0 │ ├── WebMarkupMin.Core.2.0.0.nupkg │ ├── lib │ │ ├── net452 │ │ │ └── WebMarkupMin.Core.dll │ │ ├── net40-client │ │ │ └── WebMarkupMin.Core.dll │ │ └── netstandard1.3 │ │ │ └── WebMarkupMin.Core.dll │ └── readme.txt ├── Microsoft.AspNet.Razor.3.2.3 │ ├── lib │ │ └── net45 │ │ │ └── System.Web.Razor.dll │ └── Microsoft.AspNet.Razor.3.2.3.nupkg ├── xmlrpcnet-server.3.0.0.266 │ ├── xmlrpcnet-server.3.0.0.266.nupkg │ ├── lib │ │ └── net20 │ │ │ └── CookComputing.XmlRpcServerV2.dll │ └── xmlrpcnet-server.3.0.0.266.nuspec ├── Microsoft.AspNet.WebPages.3.2.3 │ ├── lib │ │ └── net45 │ │ │ ├── System.Web.Helpers.dll │ │ │ ├── System.Web.WebPages.dll │ │ │ ├── System.Web.WebPages.Razor.dll │ │ │ ├── System.Web.WebPages.Deployment.dll │ │ │ └── System.Web.WebPages.Deployment.xml │ ├── Microsoft.AspNet.WebPages.3.2.3.nupkg │ └── Content │ │ ├── Web.config.uninstall.xdt │ │ └── Web.config.install.xdt └── Microsoft.Web.Infrastructure.1.0.0.0 │ ├── Microsoft.Web.Infrastructure.1.0.0.0.nupkg │ ├── lib │ └── net40 │ │ └── Microsoft.Web.Infrastructure.dll │ └── Microsoft.Web.Infrastructure.1.0.0.0.nuspec ├── LICENSE.md ├── .gitattributes ├── MiniBlog.sln ├── .gitignore └── README.md /Website/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/Icon.png -------------------------------------------------------------------------------- /Website/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/favicon.ico -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: madskristensen 4 | -------------------------------------------------------------------------------- /Website/bin/AjaxMin.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/AjaxMin.dll -------------------------------------------------------------------------------- /Website/bin/ImageCruncher.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/ImageCruncher.dll -------------------------------------------------------------------------------- /Website/posts/files/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/posts/files/image.png -------------------------------------------------------------------------------- /Website/bin/AjaxMin.dll.refresh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/AjaxMin.dll.refresh -------------------------------------------------------------------------------- /Website/bin/System.Web.Razor.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/System.Web.Razor.dll -------------------------------------------------------------------------------- /Website/bin/WebMarkupMin.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/WebMarkupMin.Core.dll -------------------------------------------------------------------------------- /Website/bin/system.web.helpers.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/system.web.helpers.dll -------------------------------------------------------------------------------- /Website/bin/system.web.webpages.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/system.web.webpages.dll -------------------------------------------------------------------------------- /Website/bin/CookComputing.XmlRpcV2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/CookComputing.XmlRpcV2.dll -------------------------------------------------------------------------------- /Website/bin/Microsoft.Web.Infrastructure.dll.refresh: -------------------------------------------------------------------------------- 1 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll -------------------------------------------------------------------------------- /Website/bin/System.Web.Helpers.dll.refresh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/System.Web.Helpers.dll.refresh -------------------------------------------------------------------------------- /Website/bin/System.Web.Razor.dll.refresh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/System.Web.Razor.dll.refresh -------------------------------------------------------------------------------- /Website/bin/System.Web.WebPages.Deployment.dll.refresh: -------------------------------------------------------------------------------- 1 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll -------------------------------------------------------------------------------- /Website/bin/WebMarkupMin.Core.dll.refresh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/WebMarkupMin.Core.dll.refresh -------------------------------------------------------------------------------- /Website/bin/system.web.webpages.razor.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/system.web.webpages.razor.dll -------------------------------------------------------------------------------- /packages/bootstrap.3.3.4/content/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/bootstrap.3.3.4/content/Icon.png -------------------------------------------------------------------------------- /packages/jQuery.2.1.3/jQuery.2.1.3.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/jQuery.2.1.3/jQuery.2.1.3.nupkg -------------------------------------------------------------------------------- /Website/bin/CookComputing.XmlRpcServerV2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/CookComputing.XmlRpcServerV2.dll -------------------------------------------------------------------------------- /Website/bin/System.Web.WebPages.dll.refresh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/System.Web.WebPages.dll.refresh -------------------------------------------------------------------------------- /Website/bin/microsoft.web.infrastructure.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/microsoft.web.infrastructure.dll -------------------------------------------------------------------------------- /Website/bin/CookComputing.XmlRpcV2.dll.refresh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/CookComputing.XmlRpcV2.dll.refresh -------------------------------------------------------------------------------- /Website/bin/system.web.webpages.deployment.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/system.web.webpages.deployment.dll -------------------------------------------------------------------------------- /Website/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /Website/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /Website/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /packages/bootstrap.3.3.4/bootstrap.3.3.4.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/bootstrap.3.3.4/bootstrap.3.3.4.nupkg -------------------------------------------------------------------------------- /Website/bin/System.Web.WebPages.Razor.dll.refresh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/System.Web.WebPages.Razor.dll.refresh -------------------------------------------------------------------------------- /Website/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /Website/bin/CookComputing.XmlRpcServerV2.dll.refresh: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/Website/bin/CookComputing.XmlRpcServerV2.dll.refresh -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/lib/net20/AjaxMin.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/AjaxMin.5.14.5506.26202/lib/net20/AjaxMin.dll -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/lib/net35/AjaxMin.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/AjaxMin.5.14.5506.26202/lib/net35/AjaxMin.dll -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/lib/net40/AjaxMin.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/AjaxMin.5.14.5506.26202/lib/net40/AjaxMin.dll -------------------------------------------------------------------------------- /packages/xmlrpcnet.3.0.0.266/xmlrpcnet.3.0.0.266.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/xmlrpcnet.3.0.0.266/xmlrpcnet.3.0.0.266.nupkg -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/tools/net35/AjaxMin.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/AjaxMin.5.14.5506.26202/tools/net35/AjaxMin.dll -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/tools/net40/AjaxMin.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/AjaxMin.5.14.5506.26202/tools/net40/AjaxMin.dll -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/AjaxMin.5.14.5506.26202.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/AjaxMin.5.14.5506.26202/AjaxMin.5.14.5506.26202.nupkg -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/tools/net35/AjaxMinTask.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/AjaxMin.5.14.5506.26202/tools/net35/AjaxMinTask.dll -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/tools/net40/AjaxMinTask.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/AjaxMin.5.14.5506.26202/tools/net40/AjaxMinTask.dll -------------------------------------------------------------------------------- /packages/WebMarkupMin.Core.2.0.0/WebMarkupMin.Core.2.0.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/WebMarkupMin.Core.2.0.0/WebMarkupMin.Core.2.0.0.nupkg -------------------------------------------------------------------------------- /packages/WebMarkupMin.Core.2.0.0/lib/net452/WebMarkupMin.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/WebMarkupMin.Core.2.0.0/lib/net452/WebMarkupMin.Core.dll -------------------------------------------------------------------------------- /packages/xmlrpcnet.3.0.0.266/lib/net20/CookComputing.XmlRpcV2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/xmlrpcnet.3.0.0.266/lib/net20/CookComputing.XmlRpcV2.dll -------------------------------------------------------------------------------- /packages/Microsoft.AspNet.Razor.3.2.3/lib/net45/System.Web.Razor.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/Microsoft.AspNet.Razor.3.2.3/lib/net45/System.Web.Razor.dll -------------------------------------------------------------------------------- /packages/xmlrpcnet-server.3.0.0.266/xmlrpcnet-server.3.0.0.266.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/xmlrpcnet-server.3.0.0.266/xmlrpcnet-server.3.0.0.266.nupkg -------------------------------------------------------------------------------- /packages/xmlrpcnet.3.0.0.266/lib/sl3-wp/CookComputing.XmlRpcPhone.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/xmlrpcnet.3.0.0.266/lib/sl3-wp/CookComputing.XmlRpcPhone.dll -------------------------------------------------------------------------------- /packages/Microsoft.AspNet.Razor.3.2.3/Microsoft.AspNet.Razor.3.2.3.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/Microsoft.AspNet.Razor.3.2.3/Microsoft.AspNet.Razor.3.2.3.nupkg -------------------------------------------------------------------------------- /packages/WebMarkupMin.Core.2.0.0/lib/net40-client/WebMarkupMin.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/WebMarkupMin.Core.2.0.0/lib/net40-client/WebMarkupMin.Core.dll -------------------------------------------------------------------------------- /packages/bootstrap.3.3.4/content/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/bootstrap.3.3.4/content/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /packages/bootstrap.3.3.4/content/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/bootstrap.3.3.4/content/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /packages/bootstrap.3.3.4/content/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/bootstrap.3.3.4/content/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /packages/xmlrpcnet.3.0.0.266/lib/sl4/CookComputing.XmlRpcSilverlight.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/xmlrpcnet.3.0.0.266/lib/sl4/CookComputing.XmlRpcSilverlight.dll -------------------------------------------------------------------------------- /packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.Helpers.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.Helpers.dll -------------------------------------------------------------------------------- /packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.WebPages.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.WebPages.dll -------------------------------------------------------------------------------- /packages/WebMarkupMin.Core.2.0.0/lib/netstandard1.3/WebMarkupMin.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/WebMarkupMin.Core.2.0.0/lib/netstandard1.3/WebMarkupMin.Core.dll -------------------------------------------------------------------------------- /packages/bootstrap.3.3.4/content/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/bootstrap.3.3.4/content/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /packages/Microsoft.AspNet.WebPages.3.2.3/Microsoft.AspNet.WebPages.3.2.3.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/Microsoft.AspNet.WebPages.3.2.3/Microsoft.AspNet.WebPages.3.2.3.nupkg -------------------------------------------------------------------------------- /packages/xmlrpcnet-server.3.0.0.266/lib/net20/CookComputing.XmlRpcServerV2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/xmlrpcnet-server.3.0.0.266/lib/net20/CookComputing.XmlRpcServerV2.dll -------------------------------------------------------------------------------- /packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.WebPages.Razor.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.WebPages.Razor.dll -------------------------------------------------------------------------------- /packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.WebPages.Deployment.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.WebPages.Deployment.dll -------------------------------------------------------------------------------- /packages/Microsoft.Web.Infrastructure.1.0.0.0/Microsoft.Web.Infrastructure.1.0.0.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/Microsoft.Web.Infrastructure.1.0.0.0/Microsoft.Web.Infrastructure.1.0.0.0.nupkg -------------------------------------------------------------------------------- /packages/Microsoft.Web.Infrastructure.1.0.0.0/lib/net40/Microsoft.Web.Infrastructure.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/madskristensen/MiniBlog/HEAD/packages/Microsoft.Web.Infrastructure.1.0.0.0/lib/net40/Microsoft.Web.Infrastructure.dll -------------------------------------------------------------------------------- /Website/views/Robots/Robots.cshtml: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: /views/ 3 | 4 | @{ 5 | Response.ContentType = "text/plain"; 6 | var path = !string.IsNullOrWhiteSpace(Blog.BlogPath) ? "/" + Blog.BlogPath : ""; 7 | } 8 | 9 | sitemap: @(Request.Url.Scheme + "://" + Request.Url.Authority)@path/sitemap.xml -------------------------------------------------------------------------------- /Website/scripts/_references.js: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | /// -------------------------------------------------------------------------------- /Website/404.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Page.Title = "The page does not exist"; 3 | Layout = "~/themes/" + Blog.Theme + "/_Layout.cshtml"; 4 | Response.StatusCode = 404; 5 | } 6 | 7 |
8 |

The page doesn't exist

9 | 10 |

Sorry about that.

11 | 12 |

13 | Go to the front page 14 |

15 |
-------------------------------------------------------------------------------- /Website/views/Robots/Sitemap.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @foreach (Post post in Blog.GetPosts()) 5 | { 6 | 7 | @post.AbsoluteUrl 8 | @post.LastModified.ToString("yyyy-MM-ddThh:mmzzz") 9 | 10 | 11 | } 12 | 13 | 14 | @{ 15 | Response.ContentType = "text/xml"; 16 | } -------------------------------------------------------------------------------- /Website/scripts/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2013 Mads Kristensen 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 4 | 5 | http://www.apache.org/licenses/LICENSE-2.0 6 | 7 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- /packages/bootstrap.3.3.4/content/Scripts/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /Website/posts/a5e4d75a-6629-4942-aba3-8ed020d933f4.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test 1 4 | test-1 5 | 6 | 2014-06-02 09:58:00 7 | 2014-06-02 09:58:21 8 | this is a test 9 | <p>Testing the excerpt from WLW</p> 10 | true 11 | 12 | Best Practices 13 | WLW 14 | 15 | 16 | -------------------------------------------------------------------------------- /Website/views/Robots/RSD.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Response.ContentType = "text/xml"; 3 | var path = !string.IsNullOrWhiteSpace(Blog.BlogPath) ? "/" + Blog.BlogPath : ""; 4 | } 5 | 6 | 7 | 8 | MiniBlog 9 | http://github.com/madskristensen/miniblog/ 10 | @Request.Url.Scheme://@Request.Url.Authority@path 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/Microsoft.AspNet.WebPages.3.2.3/Content/Web.config.uninstall.xdt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Website/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/Microsoft.Web.Infrastructure.1.0.0.0/Microsoft.Web.Infrastructure.1.0.0.0.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Microsoft.Web.Infrastructure 5 | 1.0.0.0 6 | Microsoft.Web.Infrastructure 7 | Microsoft 8 | Microsoft 9 | http://go.microsoft.com/fwlink/?LinkID=214339 10 | http://www.asp.net/ 11 | https://download-codeplex.sec.s-msft.com/Download?ProjectName=aspnetwebstack&DownloadId=360555 12 | false 13 | This package contains the Microsoft.Web.Infrastructure assembly that lets you dynamically register HTTP modules at run time. 14 | This package contains the Microsoft.Web.Infrastructure assembly that lets you dynamically register HTTP modules at run time. 15 | ASPNETWEBPAGES 16 | 17 | -------------------------------------------------------------------------------- /packages/xmlrpcnet.3.0.0.266/xmlrpcnet.3.0.0.266.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | xmlrpcnet 5 | 3.0.0.266 6 | XML-RPC.NET - Client 3.0.0.266 7 | Charles Cook 8 | Charles Cook 9 | http://www.opensource.org/licenses/mit-license.php 10 | http://www.xml-rpc.net/ 11 | false 12 | XML-RPC.NET - an XML-RPC client library for .Net (see xmlrpcnet-server for the server package). 13 | NOTE: this is a pre-release development snapshot of the 3.0.0 release. 14 | The recommended stable release is version 2.5.0 which supports both client and server applications. 15 | XML-RPC.NET - an XML-RPC client library for .Net 16 | Copyright Charles Cook 2011 17 | xml-rpc xml rpc .net20 .net35 .net40 18 | 19 | -------------------------------------------------------------------------------- /Website/wlwmanifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Metaweblog 5 | Yes 6 | No 7 | Yes 8 | Yes 9 | No 10 | Yes 11 | Yes 12 | No 13 | No 14 | No 15 | No 16 | No 17 | No 18 | {FileName} 19 | 20 | 21 | MiniBlog 22 | favicon.ico 23 | favicon.ico 24 | View your blog 25 | Manage your blog 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /packages/xmlrpcnet-server.3.0.0.266/xmlrpcnet-server.3.0.0.266.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | xmlrpcnet-server 5 | 3.0.0.266 6 | XML-RPC.NET - Server 7 | Charles Cook 8 | Charles Cook 9 | http://www.opensource.org/licenses/mit-license.php 10 | http://www.xml-rpc.net/ 11 | false 12 | XML-RPC.NET - an XML-RPC server library for .NET 13 | (see xmlrpcnet for the client package). 14 | NOTE: this is a pre-release development snapshot of the 3.0.0 release. The recommended stable 15 | release is version 2.5.0 of the xmlrpcnet package which supports both client 16 | and server applications. 17 | XML-RPC.NET - an XML-RPC server library for .NET 18 | 19 | Copyright Charles Cook 2011 20 | 21 | xml-rpc xml rpc .net20 .net35 .net40 server 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /packages/jQuery.2.1.3/Tools/install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | . (Join-Path $toolsPath common.ps1) 4 | 5 | # VS 11 and above supports the new intellisense JS files 6 | $vsVersion = [System.Version]::Parse($dte.Version) 7 | $supportsJsIntelliSenseFile = $vsVersion.Major -ge 11 8 | 9 | if (-not $supportsJsIntelliSenseFile) { 10 | $displayVersion = $vsVersion.Major 11 | Write-Host "IntelliSense JS files are not supported by your version of Visual Studio: $displayVersion" 12 | exit 13 | } 14 | 15 | if ($scriptsFolderProjectItem -eq $null) { 16 | # No Scripts folder 17 | Write-Host "No Scripts folder found" 18 | exit 19 | } 20 | 21 | # Delete the vsdoc file from the project 22 | try { 23 | $vsDocProjectItem = $scriptsFolderProjectItem.ProjectItems.Item("jquery-$ver-vsdoc.js") 24 | Delete-ProjectItem $vsDocProjectItem 25 | } 26 | catch { 27 | Write-Host "Error deleting vsdoc file: " + $_.Exception -ForegroundColor Red 28 | exit 29 | } 30 | 31 | # Copy the intellisense file to the project from the tools folder 32 | $intelliSenseFileSourcePath = Join-Path $toolsPath $intelliSenseFileName 33 | try { 34 | $scriptsFolderProjectItem.ProjectItems.AddFromFileCopy($intelliSenseFileSourcePath) 35 | } 36 | catch { 37 | # This will throw if the file already exists, so we need to catch here 38 | } 39 | 40 | # Update the _references.js file 41 | AddOrUpdate-Reference $scriptsFolderProjectItem $jqueryFileNameRegEx $jqueryFileName -------------------------------------------------------------------------------- /Website/themes/OffCanvas/Comment.cshtml: -------------------------------------------------------------------------------- 1 |
2 | Comment by @Model.Author 3 |
4 | @Date() 5 |

@Html.Raw(Model.ContentWithLinks())

6 | @Author() 7 | @DeleteAndApproveButton() 8 |
9 | @ApprovalMessage() 10 |
11 | 12 | @helper Date() 13 | { 14 | var title = Model.PubDate.ToString("yyyy-MM-ddTHH:mm"); 15 | var display = Model.PubDate.ToString("MMMM d. yyyy HH:mm"); 16 | 17 | } 18 | @helper Author() 19 | { 20 | if (string.IsNullOrEmpty(Model.Website)) 21 | { 22 | @Model.Author 23 | } 24 | else 25 | { 26 | 27 | } 28 | } 29 | @helper DeleteAndApproveButton() 30 | { 31 | if (User.Identity.IsAuthenticated) 32 | { 33 | 34 | if (Blog.ModerateComments && !Model.IsApproved) 35 | { 36 | 37 | } 38 | } 39 | } 40 | @helper ApprovalMessage() 41 | { 42 | if (Blog.ModerateComments && !Model.IsApproved && !User.Identity.IsAuthenticated) 43 | { 44 |
! The comment will not be visible until a moderator approves it !
45 | } 46 | } -------------------------------------------------------------------------------- /Website/themes/OneColumn/Comment.cshtml: -------------------------------------------------------------------------------- 1 |
2 | Comment by @Model.Author 3 |
4 | @Date() 5 |

@Html.Raw(Model.ContentWithLinks())

6 | @Author() 7 | @DeleteAndApproveButton() 8 |
9 | @ApprovalMessage() 10 |
11 | 12 | @helper Date() 13 | { 14 | var title = Model.PubDate.ToString("yyyy-MM-ddTHH:mm"); 15 | var display = Model.PubDate.ToString("MMMM d. yyyy HH:mm"); 16 | 17 | } 18 | @helper Author() 19 | { 20 | if (string.IsNullOrEmpty(Model.Website)) 21 | { 22 | @Model.Author 23 | } 24 | else 25 | { 26 | 27 | } 28 | } 29 | @helper DeleteAndApproveButton() 30 | { 31 | if (User.Identity.IsAuthenticated) 32 | { 33 | 34 | if (Blog.ModerateComments && !Model.IsApproved) 35 | { 36 | 37 | } 38 | } 39 | } 40 | @helper ApprovalMessage() 41 | { 42 | if (Blog.ModerateComments && !Model.IsApproved && !User.Identity.IsAuthenticated) 43 | { 44 |
! The comment will not be visible until a moderator approves it !
45 | } 46 | } -------------------------------------------------------------------------------- /Website/themes/TwoColumns/Comment.cshtml: -------------------------------------------------------------------------------- 1 |
2 | Comment by @Model.Author 3 |
4 | @Date() 5 |

@Html.Raw(Model.ContentWithLinks())

6 | @Author() 7 | @DeleteAndApproveButton() 8 |
9 | @ApprovalMessage() 10 |
11 | 12 | @helper Date() 13 | { 14 | var title = Model.PubDate.ToString("yyyy-MM-ddTHH:mm"); 15 | var display = Model.PubDate.ToString("MMMM d. yyyy HH:mm"); 16 | 17 | } 18 | @helper Author() 19 | { 20 | if (string.IsNullOrEmpty(Model.Website)) 21 | { 22 | @Model.Author 23 | } 24 | else 25 | { 26 | 27 | } 28 | } 29 | @helper DeleteAndApproveButton() 30 | { 31 | if (User.Identity.IsAuthenticated) 32 | { 33 | 34 | if (Blog.ModerateComments && !Model.IsApproved) 35 | { 36 | 37 | } 38 | } 39 | } 40 | @helper ApprovalMessage() 41 | { 42 | if (Blog.ModerateComments && !Model.IsApproved && !User.Identity.IsAuthenticated) 43 | { 44 |
! The comment will not be visible until a moderator approves it !
45 | } 46 | } -------------------------------------------------------------------------------- /packages/jQuery.2.1.3/Tools/uninstall.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | . (Join-Path $toolsPath common.ps1) 4 | 5 | # Determine the file paths 6 | $projectIntelliSenseFilePath = Join-Path $projectScriptsFolderPath $intelliSenseFileName 7 | $origIntelliSenseFilePath = Join-Path $toolsPath $intelliSenseFileName 8 | 9 | if (Test-Path $projectIntelliSenseFilePath) { 10 | if ((Get-Checksum $projectIntelliSenseFilePath) -eq (Get-Checksum $origIntelliSenseFilePath)) { 11 | # The intellisense file in the project matches the file in the tools folder, delete it 12 | 13 | if ($scriptsFolderProjectItem -eq $null) { 14 | # No Scripts folder 15 | exit 16 | } 17 | 18 | try { 19 | # Get the project item for the intellisense file 20 | $intelliSenseFileProjectItem = $scriptsFolderProjectItem.ProjectItems.Item($intelliSenseFileName) 21 | } 22 | catch { 23 | # The item wasn't found 24 | exit 25 | } 26 | 27 | # Delete the project item 28 | Delete-ProjectItem $intelliSenseFileProjectItem 29 | } 30 | else { 31 | $projectScriptsFolderLeaf = Split-Path $projectScriptsFolderPath -Leaf 32 | Write-Host "Skipping '$projectScriptsFolderLeaf\$intelliSenseFileName' because it was modified." -ForegroundColor Magenta 33 | } 34 | } 35 | else { 36 | # The intellisense file was not found in project 37 | Write-Host "The intellisense file was not found in project at path $projectIntelliSenseFilePath" 38 | } 39 | 40 | # Update the _references.js file 41 | Remove-Reference $scriptsFolderProjectItem $jqueryFileNameRegEx -------------------------------------------------------------------------------- /Website/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Language="C#" %> 2 | 3 | 48 | -------------------------------------------------------------------------------- /Website/views/CommentForm.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var path = !string.IsNullOrWhiteSpace(Blog.BlogPath) ? "/" + Blog.BlogPath : ""; 3 | } 4 |
5 |
6 | Post comment 7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 | 15 |
16 | 17 |
18 | 19 |
20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 |
28 | 29 |
30 | 31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 | 40 |
41 |
42 |
43 |
-------------------------------------------------------------------------------- /packages/Microsoft.AspNet.WebPages.3.2.3/Content/Web.config.install.xdt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Website/views/Login.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Page.Title = "Sign in"; 3 | Layout = "~/themes/" + Blog.Theme + "/_Layout.cshtml"; 4 | Response.Cache.SetCacheability(HttpCacheability.NoCache); 5 | 6 | if (Request.HttpMethod == "POST") 7 | { 8 | string username = Request.Form["username"]; 9 | string password = Request.Form["password"]; 10 | string remember = Request.Form["remember"]; 11 | 12 | if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password)) 13 | { 14 | AntiForgery.Validate(); 15 | if (FormsAuthentication.Authenticate(username, password)) 16 | { 17 | FormsAuthentication.RedirectFromLoginPage(username, remember == "on"); 18 | } 19 | } 20 | else if (!string.IsNullOrEmpty(Request["signout"])) 21 | { 22 | FormsAuthentication.SignOut(); 23 | Response.Redirect(Request["ReturnUrl"], true); 24 | } 25 | } 26 | } 27 | 28 | @if (!User.Identity.IsAuthenticated) 29 | { 30 |
31 |
32 |

Sign in

33 | 34 | @if (Request.HttpMethod == "POST") 35 | { 36 |

Username or password is incorrect

37 | } 38 | 39 |
40 | 41 | 42 |
43 | 44 |
45 | 46 | 47 |
48 |
49 | 50 | 51 |
52 | 53 | 54 |
55 | 56 | @AntiForgery.GetHtml() 57 |
58 | } 59 | else 60 | { 61 |

You are already signed in

62 | } -------------------------------------------------------------------------------- /Website/app_code/handlers/MinifyHandler.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Ajax.Utilities; 2 | using System; 3 | using System.IO; 4 | using System.Web; 5 | using System.Web.Caching; 6 | 7 | public class MinifyHandler : IHttpHandler 8 | { 9 | public void ProcessRequest(HttpContext context) 10 | { 11 | string file = context.Server.MapPath(context.Request.CurrentExecutionFilePath); 12 | string ext = Path.GetExtension(file); 13 | 14 | if (context.Request.IsAuthenticated || context.IsDebuggingEnabled) 15 | { 16 | context.Response.TransmitFile(file); 17 | } 18 | else 19 | { 20 | Minify(context.Response, file, ext); 21 | } 22 | 23 | SetHeaders(context.Response, file, ext); 24 | } 25 | 26 | private static void SetHeaders(HttpResponse response, string file, string ext) 27 | { 28 | response.ContentType = ext == ".css" ? "text/css" : "text/javascript"; 29 | 30 | response.Cache.SetLastModified(File.GetLastWriteTimeUtc(file)); 31 | response.Cache.SetValidUntilExpires(true); 32 | response.Cache.SetExpires(DateTime.Now.AddYears(1)); 33 | response.Cache.SetCacheability(HttpCacheability.Public); 34 | response.Cache.SetVaryByCustom("Accept-Encoding"); 35 | 36 | response.AddCacheDependency(new CacheDependency(file)); 37 | } 38 | 39 | private static void Minify(HttpResponse response, string file, string ext) 40 | { 41 | string content = File.ReadAllText(file); 42 | Minifier minifier = new Minifier(); 43 | 44 | if (ext == ".css") 45 | { 46 | CssSettings settings = new CssSettings() { CommentMode = CssComment.None }; 47 | response.Write(minifier.MinifyStyleSheet(content, settings)); 48 | } 49 | else if (ext == ".js") 50 | { 51 | CodeSettings settings = new CodeSettings() { PreserveImportantComments = false }; 52 | response.Write(minifier.MinifyJavaScript(content, settings)); 53 | } 54 | } 55 | 56 | public bool IsReusable 57 | { 58 | get { return false; } 59 | } 60 | } -------------------------------------------------------------------------------- /Website/Content/admin.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 1024px) { 2 | .nav > li { 3 | display: inline-block; 4 | } 5 | 6 | .nav > li button, #ispublished { 7 | padding-left: 8px; 8 | padding-right: 8px; 9 | } 10 | } 11 | 12 | .admin { 13 | margin-top: 50px; 14 | } 15 | 16 | #admin { 17 | font: 12pt arial; 18 | } 19 | 20 | #admin .navbar-inner { 21 | min-height: 0; 22 | padding-top: 8px; 23 | } 24 | 25 | #admin form { 26 | position: absolute; 27 | top: 8px; 28 | right: 1em; 29 | margin: 0; 30 | } 31 | 32 | #admin .nav { 33 | margin-left: 1em; 34 | } 35 | 36 | #admin button:focus { 37 | outline: 0; 38 | white-space: nowrap; 39 | } 40 | 41 | #ispublished { 42 | display: none; 43 | position: relative; 44 | top: 7px; 45 | left: 10px; 46 | } 47 | 48 | #ispublished label { 49 | display: inline; 50 | position: relative; 51 | left: 4px; 52 | font-weight: normal; 53 | } 54 | 55 | #admin #tools { 56 | position: relative; 57 | top: 5px; 58 | left: 10px; 59 | margin: 0 1em; 60 | display: none; 61 | } 62 | 63 | #admin #tools a { 64 | font-size: 14px; 65 | font-weight: bold; 66 | padding: 0 9px; 67 | } 68 | 69 | #admin #tools div:not(:last-child) { 70 | border-right: 1px solid gray; 71 | } 72 | 73 | #admin aside { 74 | position: absolute; 75 | top: 4em; 76 | text-align: center; 77 | width: 100%; 78 | display: none; 79 | } 80 | 81 | #admin aside .alert { 82 | display: inline-block; 83 | padding: 8px 1em; 84 | } 85 | 86 | [contenteditable] { 87 | outline: thin dashed #ccc; 88 | width: 100%; 89 | display: inline-block; 90 | } 91 | 92 | [contenteditable] img { 93 | cursor: move; 94 | } 95 | 96 | .admin #excerpt { 97 | border:solid 1px #CCC; 98 | padding:10px; 99 | margin-bottom:20px; 100 | } 101 | .admin #excerpt > div { 102 | background-color:white; 103 | color:black; 104 | } -------------------------------------------------------------------------------- /MiniBlog.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30723.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "MiniBlog", "http://localhost:36123", "{3D10EDC4-D7A0-4912-84F1-853B6E694CE5}" 7 | ProjectSection(WebsiteProperties) = preProject 8 | UseIISExpress = "true" 9 | TargetFrameworkMoniker = ".NETFramework,Version%3Dv4.5" 10 | Debug.AspNetCompiler.VirtualPath = "/localhost_36123" 11 | Debug.AspNetCompiler.PhysicalPath = "Website\" 12 | Debug.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_36123\" 13 | Debug.AspNetCompiler.Updateable = "true" 14 | Debug.AspNetCompiler.ForceOverwrite = "true" 15 | Debug.AspNetCompiler.FixedNames = "false" 16 | Debug.AspNetCompiler.Debug = "True" 17 | Release.AspNetCompiler.VirtualPath = "/localhost_36123" 18 | Release.AspNetCompiler.PhysicalPath = "Website\" 19 | Release.AspNetCompiler.TargetPath = "PrecompiledWeb\localhost_36123\" 20 | Release.AspNetCompiler.Updateable = "true" 21 | Release.AspNetCompiler.ForceOverwrite = "true" 22 | Release.AspNetCompiler.FixedNames = "false" 23 | Release.AspNetCompiler.Debug = "False" 24 | SlnRelativePath = "Website\" 25 | DefaultWebSiteLanguage = "Visual C#" 26 | EndProjectSection 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C6A4C914-CC97-4804-ACA1-B8A0E61CE333}" 29 | ProjectSection(SolutionItems) = preProject 30 | .gitignore = .gitignore 31 | LICENSE.md = LICENSE.md 32 | README.md = README.md 33 | EndProjectSection 34 | EndProject 35 | Global 36 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 37 | Debug|Any CPU = Debug|Any CPU 38 | Release|Any CPU = Release|Any CPU 39 | EndGlobalSection 40 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 41 | {3D10EDC4-D7A0-4912-84F1-853B6E694CE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {3D10EDC4-D7A0-4912-84F1-853B6E694CE5}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {3D10EDC4-D7A0-4912-84F1-853B6E694CE5}.Release|Any CPU.ActiveCfg = Debug|Any CPU 44 | {3D10EDC4-D7A0-4912-84F1-853B6E694CE5}.Release|Any CPU.Build.0 = Debug|Any CPU 45 | EndGlobalSection 46 | GlobalSection(SolutionProperties) = preSolution 47 | HideSolutionNode = FALSE 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /Website/app_code/handlers/FeedHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ServiceModel.Syndication; 4 | using System.Web; 5 | using System.Xml; 6 | 7 | public class FeedHandler : IHttpHandler 8 | { 9 | public void ProcessRequest(HttpContext context) 10 | { 11 | SyndicationFeed feed = new SyndicationFeed() 12 | { 13 | Title = new TextSyndicationContent(Blog.Title), 14 | Description = new TextSyndicationContent("Latest blog posts"), 15 | BaseUri = new Uri(context.Request.Url.Scheme + "://" + context.Request.Url.Authority), 16 | Items = GetItems(), 17 | }; 18 | 19 | feed.Links.Add(new SyndicationLink(feed.BaseUri)); 20 | 21 | using (var writer = new XmlTextWriter(context.Response.Output)) 22 | { 23 | var formatter = GetFormatter(context, feed); 24 | writer.Formatting = Formatting.Indented; 25 | formatter.WriteTo(writer); 26 | } 27 | 28 | context.Response.ContentType = "text/xml"; 29 | } 30 | 31 | private IEnumerable GetItems() 32 | { 33 | foreach (Post p in Blog.GetPosts(10)) 34 | { 35 | var item = new SyndicationItem(p.Title, p.Content, p.AbsoluteUrl, p.AbsoluteUrl.ToString(), p.LastModified) 36 | { 37 | PublishDate = p.PubDate 38 | }; 39 | 40 | if (!string.IsNullOrWhiteSpace(p.Excerpt)) 41 | item.Summary = new TextSyndicationContent(p.Excerpt); 42 | 43 | item.Authors.Add(new SyndicationPerson("", p.Author, "")); 44 | yield return item; 45 | } 46 | } 47 | 48 | private SyndicationFeedFormatter GetFormatter(HttpContext context, SyndicationFeed feed) 49 | { 50 | string path = context.Request.Path.Trim('/'); 51 | int index = path.LastIndexOf('/'); 52 | 53 | if (index > -1 && path.Substring(index + 1) == "atom") 54 | { 55 | context.Response.ContentType = "application/atom+xml"; 56 | return new Atom10FeedFormatter(feed); 57 | } 58 | 59 | context.Response.ContentType = "application/rss+xml"; 60 | return new Rss20FeedFormatter(feed); 61 | } 62 | 63 | public bool IsReusable 64 | { 65 | get { return false; } 66 | } 67 | } -------------------------------------------------------------------------------- /Website/themes/TwoColumns/Post.cshtml: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 |

6 |
7 | 8 | 9 | 10 | @Model.CountApprovedComments(Context) Comments 11 | 12 | @Categories() 13 |
14 |
15 | 16 | @if ((Blog.CurrentPost != null || Blog.IsNewPost) && User.Identity.IsAuthenticated) 17 | { 18 | 22 | } 23 | 24 |
@Html.Raw(Blog.IsEditing ? Model.Content : Model.GetHtmlContent())
25 | 26 | @if (Blog.CurrentPost != null) 27 | { 28 |
29 | @if (Model.CountApprovedComments(Context) > 0) 30 | { 31 |

Comments

32 | } 33 | 34 | @foreach (Comment comment in Model.Comments) 35 | { 36 | if (comment.IsApproved || !Blog.ModerateComments || Context.User.Identity.IsAuthenticated) 37 | { 38 | @RenderPage("Comment.cshtml", comment) 39 | } 40 | } 41 |
42 | 43 | if (Model.AreCommentsOpen(Context)) 44 | { 45 | @RenderPage("~/views/CommentForm.cshtml") 46 | } 47 | } 48 |
49 | 50 | @helper Categories() 51 | { 52 | if (Model.Categories.Length > 0 || User.Identity.IsAuthenticated) 53 | { 54 |
    55 |
  •   Posted in: 
  • 56 | @foreach (string cat in Model.Categories) 57 | { 58 |
  • 59 | @cat 60 |
  • 61 | } 62 |
63 | } 64 | } -------------------------------------------------------------------------------- /Website/Index.cshtml: -------------------------------------------------------------------------------- 1 | @using System.Web.Caching; 2 | @{ 3 | Layout = "~/themes/" + Blog.Theme + "/_Layout.cshtml"; 4 | DateTime lastModified = DateTime.MinValue; 5 | 6 | if (string.IsNullOrEmpty(Blog.CurrentSlug)) 7 | { 8 | Page.Title = Blog.Title; 9 | Page.Description = Blog.Description; 10 | Page.ShowPaging = true; 11 | 12 | var posts = Blog.GetPosts(Blog.PostsPerPage); 13 | 14 | foreach (var post in posts) 15 | { 16 | @RenderPage("~/themes/" + Blog.Theme + "/Post.cshtml", post); 17 | } 18 | 19 | if (posts.Any()) 20 | { 21 | lastModified = posts.Max(p => p.LastModified); 22 | } 23 | 24 | Response.AddCacheItemDependency("posts"); 25 | Response.Cache.VaryByParams["page"] = true; 26 | Response.Cache.VaryByParams["category"] = true; 27 | } 28 | else 29 | { 30 | Post post = Blog.IsNewPost ? new Post() : Blog.CurrentPost; 31 | 32 | if (Blog.IsNewPost && !User.Identity.IsAuthenticated) 33 | { 34 | FormsAuthentication.RedirectToLoginPage(); 35 | } 36 | 37 | if (post == null) { throw new HttpException(404, "Post not found"); } 38 | 39 | Page.Title = string.Format("{0} - {1}", post.Title, Blog.Title); 40 | Page.Description = post.Excerpt; 41 | lastModified = post.LastModified; 42 | 43 | Response.AddCacheDependency(new CacheDependency(Server.MapPath("~/posts/" + post.ID + ".xml"))); 44 | 45 | @RenderPage("~/themes/" + Blog.Theme + "/Post.cshtml", post) 46 | } 47 | 48 | @AntiForgery.GetHtml() 49 | 50 | if ( !Request.IsLocal && !User.Identity.IsAuthenticated ) 51 | { 52 | Response.Cache.AppendCacheExtension("max-age=0"); 53 | Response.Cache.SetValidUntilExpires(true); 54 | Response.Cache.SetCacheability(HttpCacheability.ServerAndPrivate); 55 | Response.Cache.SetVaryByCustom("authenticated"); 56 | Response.Cache.VaryByParams["slug"] = true; 57 | Response.AddCacheDependency(new CacheDependency(Server.MapPath("~/"))); 58 | Response.AddCacheDependency(new CacheDependency(Server.MapPath("~/scripts"))); 59 | Response.AddCacheDependency(new CacheDependency(Server.MapPath("~/Content"))); 60 | Response.AddCacheDependency(new CacheDependency(Server.MapPath("~/themes/" + Blog.Theme))); 61 | 62 | 63 | Blog.SetConditionalGetHeaders(lastModified.ToLocalTime(), Context); 64 | } 65 | } -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/tools/net35/AjaxMin.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | $(ProjectDir)Content\ 12 | $(ProjectDir) 13 | 14 | 15 | false 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 38 | -------------------------------------------------------------------------------- /packages/AjaxMin.5.14.5506.26202/tools/net40/AjaxMin.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | $(ProjectDir)Content\ 12 | $(ProjectDir) 13 | 14 | 15 | false 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 37 | 38 | -------------------------------------------------------------------------------- /packages/WebMarkupMin.Core.2.0.0/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- 4 | README file for Web Markup Minifier: Core v2.0.0 5 | 6 | -------------------------------------------------------------------------------- 7 | 8 | Copyright (c) 2013-2016 Andrey Taritsyn - http://www.taritsyn.ru 9 | 10 | 11 | =========== 12 | DESCRIPTION 13 | =========== 14 | The Web Markup Minifier (abbreviated WebMarkupMin) is a .NET library that 15 | contains a set of markup minifiers. The objective of this project is to improve 16 | the performance of web applications by reducing the size of HTML, XHTML and XML 17 | code. 18 | 19 | WebMarkupMin absorbed the best of existing solutions from non-microsoft 20 | platforms: Juriy Zaytsev's Experimental HTML Minifier 21 | (http://github.com/kangax/html-minifier/) (written in JavaScript) and Sergiy 22 | Kovalchuk's HtmlCompressor (http://code.google.com/p/htmlcompressor/) (written 23 | in Java). 24 | 25 | Minification of markup produces by removing extra whitespaces, comments and 26 | redundant code (only for HTML and XHTML). In addition, HTML and XHTML minifiers 27 | supports the minification of CSS code from style tags and attributes, and 28 | minification of JavaScript code from script tags, event attributes and 29 | hyperlinks with javascript: protocol. WebMarkupMin.Core contains built-in 30 | JavaScript minifier based on the Douglas Crockford's JSMin 31 | (http://github.com/douglascrockford/JSMin) and built-in CSS minifier based on 32 | the Mads Kristensen's Efficient stylesheet minifier 33 | (http://madskristensen.net/post/Efficient-stylesheet-minification-in-C). 34 | The above mentioned minifiers produce only the most simple minifications of 35 | CSS and JavaScript code, but you can always install additional modules that 36 | support the more powerful algorithms of minification: WebMarkupMin.MsAjax 37 | (contains minifier-adapters for the Microsoft Ajax Minifier - 38 | http://ajaxmin.codeplex.com) and WebMarkupMin.Yui (contains minifier-adapters 39 | for YUI Compressor for .Net - http://github.com/PureKrome/YUICompressor.NET). 40 | 41 | Also supports minification of views of popular JavaScript template engines: 42 | KnockoutJS, Kendo UI MVVM and AngularJS 1.X. 43 | 44 | ============= 45 | RELEASE NOTES 46 | ============= 47 | 1. Added support of .NET Core 1.0 RTM; 48 | 2. Was made refactoring. 49 | 50 | ============= 51 | DOCUMENTATION 52 | ============= 53 | See documentation on GitHub - http://github.com/Taritsyn/WebMarkupMin/wiki -------------------------------------------------------------------------------- /Website/themes/OneColumn/Post.cshtml: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | 5 |

6 |
7 | 8 | 9 | 10 | @Model.CountApprovedComments(Context) Comments 11 | 12 | @Categories() 13 |
14 |
15 | 16 | @if ((Blog.CurrentPost != null || Blog.IsNewPost) && User.Identity.IsAuthenticated) 17 | { 18 | 22 | } 23 | 24 | @* If you want to use excerpts rather than full content in list view then uncomment the below 25 | and remove the other articleBody div *@ 26 | @*@if (Blog.CurrentPost == null) 27 | { 28 |
@Model.Excerpt
29 | } 30 | else 31 | { 32 |
@Html.Raw(Model.Content)
33 | }*@ 34 | 35 |
@Html.Raw(Blog.IsEditing ? Model.Content : Model.GetHtmlContent())
36 | 37 | @if (Blog.CurrentPost != null) 38 | { 39 |
40 | @if (Model.CountApprovedComments(Context) > 0) 41 | { 42 |

Comments

43 | } 44 | 45 | @foreach (Comment comment in Model.Comments) 46 | { 47 | if (comment.IsApproved || !Blog.ModerateComments || Context.User.Identity.IsAuthenticated) 48 | { 49 | @RenderPage("Comment.cshtml", comment) 50 | } 51 | } 52 |
53 | 54 | if (Model.AreCommentsOpen(Context)) 55 | { 56 | @RenderPage("~/views/CommentForm.cshtml") 57 | } 58 | } 59 |
60 | 61 | @helper Categories() 62 | { 63 | if (Model.Categories.Length > 0 || User.Identity.IsAuthenticated) 64 | { 65 |
    66 |
  •   Posted in: 
  • 67 | @foreach (string cat in Model.Categories) 68 | { 69 |
  • 70 | @cat 71 |
  • 72 | } 73 |
74 | } 75 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .vs 8 | .metadata 9 | #bin/ 10 | tmp/ 11 | *.tmp 12 | *.bak 13 | *.swp 14 | *~.nib 15 | local.properties 16 | .classpath 17 | .settings/ 18 | .loadpath 19 | 20 | # External tool builders 21 | .externalToolBuilders/ 22 | 23 | # Locally stored "Eclipse launch configurations" 24 | *.launch 25 | 26 | # CDT-specific 27 | .cproject 28 | 29 | # PDT-specific 30 | .buildpath 31 | 32 | 33 | ################# 34 | ## Visual Studio 35 | ################# 36 | 37 | ## Ignore Visual Studio temporary files, build results, and 38 | ## files generated by popular Visual Studio add-ons. 39 | 40 | # User-specific files 41 | *.suo 42 | *.user 43 | *.sln.docstates 44 | 45 | # Build results 46 | 47 | [Dd]ebug/ 48 | [Rr]elease/ 49 | x64/ 50 | build/ 51 | #[Bb]in/ 52 | [Oo]bj/ 53 | 54 | # MSTest test Results 55 | [Tt]est[Rr]esult*/ 56 | [Bb]uild[Ll]og.* 57 | 58 | *_i.c 59 | *_p.c 60 | *.ilk 61 | *.meta 62 | *.obj 63 | *.pch 64 | *.pdb 65 | *.pgc 66 | *.pgd 67 | *.rsp 68 | *.sbr 69 | *.tlb 70 | *.tli 71 | *.tlh 72 | *.tmp 73 | *.tmp_proj 74 | *.log 75 | *.vspscc 76 | *.vssscc 77 | .builds 78 | *.pidb 79 | *.log 80 | *.scc 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opensdf 87 | *.sdf 88 | *.cachefile 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | 95 | # Guidance Automation Toolkit 96 | *.gpState 97 | 98 | # ReSharper is a .NET coding add-in 99 | _ReSharper*/ 100 | *.[Rr]e[Ss]harper 101 | 102 | # TeamCity is a build add-in 103 | _TeamCity* 104 | 105 | # DotCover is a Code Coverage Tool 106 | *.dotCover 107 | 108 | # NCrunch 109 | *.ncrunch* 110 | .*crunch*.local.xml 111 | 112 | # Installshield output folder 113 | [Ee]xpress/ 114 | 115 | # DocProject is a documentation generator add-in 116 | DocProject/buildhelp/ 117 | DocProject/Help/*.HxT 118 | DocProject/Help/*.HxC 119 | DocProject/Help/*.hhc 120 | DocProject/Help/*.hhk 121 | DocProject/Help/*.hhp 122 | DocProject/Help/Html2 123 | DocProject/Help/html 124 | 125 | # Click-Once directory 126 | publish/ 127 | 128 | # Publish Web Output 129 | *.Publish.xml 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages Directory 134 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 135 | #packages/ 136 | 137 | # Windows Azure Build Output 138 | csx 139 | *.build.csdef 140 | 141 | # Windows Store app package directory 142 | AppPackages/ 143 | 144 | # Others 145 | sql/ 146 | *.Cache 147 | ClientBin/ 148 | [Ss]tyle[Cc]op.* 149 | ~$* 150 | *~ 151 | *.dbmdl 152 | *.[Pp]ublish.xml 153 | *.pfx 154 | *.publishsettings 155 | 156 | # RIA/Silverlight projects 157 | Generated_Code/ 158 | 159 | # Backup & report files from converting an old project file to a newer 160 | # Visual Studio version. Backup files are not needed, because we have git ;-) 161 | _UpgradeReport_Files/ 162 | Backup*/ 163 | UpgradeLog*.XML 164 | UpgradeLog*.htm 165 | 166 | # SQL Server files 167 | App_Data/*.mdf 168 | App_Data/*.ldf 169 | 170 | ############# 171 | ## Windows detritus 172 | ############# 173 | 174 | # Windows image file caches 175 | Thumbs.db 176 | ehthumbs.db 177 | 178 | # Folder config file 179 | Desktop.ini 180 | 181 | # Recycle Bin used on file shares 182 | $RECYCLE.BIN/ 183 | 184 | # Mac crap 185 | .DS_Store 186 | 187 | 188 | ############# 189 | ## Python 190 | ############# 191 | 192 | *.py[co] 193 | 194 | # Packages 195 | *.egg 196 | *.egg-info 197 | dist/ 198 | build/ 199 | eggs/ 200 | parts/ 201 | var/ 202 | sdist/ 203 | develop-eggs/ 204 | .installed.cfg 205 | 206 | # Installer logs 207 | pip-log.txt 208 | 209 | # Unit test / coverage reports 210 | .coverage 211 | .tox 212 | 213 | #Translations 214 | *.mo 215 | 216 | #Mr Developer 217 | .mr.developer.cfg 218 | -------------------------------------------------------------------------------- /Website/app_code/code/Post.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using System.Web; 7 | using CookComputing.XmlRpc; 8 | 9 | [XmlRpcMissingMapping(MappingAction.Ignore)] 10 | public class Post 11 | { 12 | public Post() 13 | { 14 | ID = Guid.NewGuid().ToString(); 15 | Title = "My new post"; 16 | Author = HttpContext.Current.User.Identity.Name; 17 | Content = "the content"; 18 | PubDate = DateTime.UtcNow; 19 | LastModified = DateTime.UtcNow; 20 | Categories = new string[0]; 21 | Comments = new List(); 22 | IsPublished = true; 23 | } 24 | 25 | [XmlRpcMember("postid")] 26 | public string ID { get; set; } 27 | 28 | [XmlRpcMember("title")] 29 | public string Title { get; set; } 30 | 31 | [XmlRpcMember("author")] 32 | public string Author { get; set; } 33 | 34 | [XmlRpcMember("wp_slug")] 35 | public string Slug { get; set; } 36 | 37 | [XmlRpcMember("mt_excerpt")] 38 | public string Excerpt { get; set; } 39 | 40 | [XmlRpcMember("description")] 41 | public string Content { get; set; } 42 | 43 | [XmlRpcMember("dateCreated")] 44 | public DateTime PubDate { get; set; } 45 | 46 | [XmlRpcMember("dateModified")] 47 | public DateTime LastModified { get; set; } 48 | 49 | public bool IsPublished { get; set; } 50 | 51 | [XmlRpcMember("categories")] 52 | public string[] Categories { get; set; } 53 | public List Comments { get; private set; } 54 | 55 | public Uri AbsoluteUrl 56 | { 57 | get 58 | { 59 | Uri requestUrl = HttpContext.Current.Request.Url; 60 | return new Uri(requestUrl.Scheme + "://" + requestUrl.Authority + Url, UriKind.Absolute); 61 | } 62 | } 63 | 64 | public Uri Url 65 | { 66 | get 67 | { 68 | return new Uri(VirtualPathUtility.ToAbsolute("~/post/" + Slug), UriKind.Relative); 69 | } 70 | } 71 | 72 | public bool AreCommentsOpen(HttpContextBase context) 73 | { 74 | return PubDate > DateTime.UtcNow.AddDays(-Blog.DaysToComment) || context.User.Identity.IsAuthenticated; 75 | } 76 | 77 | public int CountApprovedComments(HttpContextBase context) 78 | { 79 | return (Blog.ModerateComments && !context.User.Identity.IsAuthenticated) ? this.Comments.Count(c => c.IsApproved) : this.Comments.Count; 80 | } 81 | 82 | public string GetHtmlContent() 83 | { 84 | string result = Content; 85 | 86 | // Youtube content embedded using this syntax: [youtube:xyzAbc123] 87 | var video = "
"; 88 | result = Regex.Replace(result, @"\[youtube:(.*?)\]", (Match m) => string.Format(video, m.Groups[1].Value)); 89 | 90 | // Images replaced by CDN paths if they are located in the /posts/ folder 91 | var cdn = ConfigurationManager.AppSettings.Get("blog:cdnUrl"); 92 | var root = ConfigurationManager.AppSettings.Get("blog:path") + "/posts/"; 93 | 94 | if (!root.StartsWith("/")) 95 | root = "/" + root; 96 | 97 | result = Regex.Replace(result, " 98 | { 99 | string src = m.Groups[1].Value; 100 | int index = src.IndexOf(root); 101 | 102 | if (index > -1) 103 | { 104 | string clean = src.Substring(index); 105 | return m.Value.Replace(src, cdn + clean); 106 | } 107 | 108 | return m.Value; 109 | }); 110 | 111 | return result; 112 | } 113 | } -------------------------------------------------------------------------------- /Website/app_code/code/Comment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Text.RegularExpressions; 4 | using System.Web.Security; 5 | 6 | public class Comment 7 | { 8 | private static readonly Regex _linkRegex = new Regex("((http://|https://|www\\.)([A-Z0-9.\\-]{1,})\\.[0-9A-Z?;~&%\\(\\)#,=\\-_\\./\\+]{2,}[0-9A-Z?~&%#=\\-_/\\+])", RegexOptions.Compiled | RegexOptions.IgnoreCase); 9 | private const string Link = "{2}"; 10 | 11 | public Comment() 12 | { 13 | ID = Guid.NewGuid().ToString(); 14 | PubDate = DateTime.UtcNow; 15 | } 16 | 17 | public string ID { get; set; } 18 | public string Author { get; set; } 19 | public string Email { get; set; } 20 | public string Website { get; set; } 21 | public string Content { get; set; } 22 | public DateTime PubDate { get; set; } 23 | public string Ip { get; set; } 24 | public string UserAgent { get; set; } 25 | public bool IsAdmin { get; set; } 26 | public bool IsApproved { get; set; } 27 | 28 | public string GravatarUrl(int size) 29 | { 30 | var hash = FormsAuthentication.HashPasswordForStoringInConfigFile(Email.ToLowerInvariant(), "MD5").ToLower(); 31 | 32 | return string.Format("http://gravatar.com/avatar/{0}?s={1}&d=mm", hash, size); 33 | } 34 | 35 | public string ContentWithLinks() 36 | { 37 | return _linkRegex.Replace(Content, new MatchEvaluator(Evaluator)); 38 | } 39 | 40 | private static string Evaluator(Match match) 41 | { 42 | var info = CultureInfo.InvariantCulture; 43 | return string.Format(info, Link, !match.Value.Contains("://") ? "http://" : string.Empty, match.Value, ShortenUrl(match.Value, 50)); 44 | } 45 | 46 | private static string ShortenUrl(string url, int max) 47 | { 48 | if (url.Length <= max) 49 | { 50 | return url; 51 | } 52 | 53 | // Remove the protocal 54 | var startIndex = url.IndexOf("://"); 55 | if (startIndex > -1) 56 | { 57 | url = url.Substring(startIndex + 3); 58 | } 59 | 60 | if (url.Length <= max) 61 | { 62 | return url; 63 | } 64 | 65 | // Compress folder structure 66 | var firstIndex = url.IndexOf("/") + 1; 67 | var lastIndex = url.LastIndexOf("/"); 68 | if (firstIndex < lastIndex) 69 | { 70 | url = url.Remove(firstIndex, lastIndex - firstIndex); 71 | url = url.Insert(firstIndex, "..."); 72 | } 73 | 74 | if (url.Length <= max) 75 | { 76 | return url; 77 | } 78 | 79 | // Remove URL parameters 80 | var queryIndex = url.IndexOf("?"); 81 | if (queryIndex > -1) 82 | { 83 | url = url.Substring(0, queryIndex); 84 | } 85 | 86 | if (url.Length <= max) 87 | { 88 | return url; 89 | } 90 | 91 | // Remove URL fragment 92 | var fragmentIndex = url.IndexOf("#"); 93 | if (fragmentIndex > -1) 94 | { 95 | url = url.Substring(0, fragmentIndex); 96 | } 97 | 98 | if (url.Length <= max) 99 | { 100 | return url; 101 | } 102 | 103 | // Compress page 104 | firstIndex = url.LastIndexOf("/") + 1; 105 | lastIndex = url.LastIndexOf("."); 106 | if (lastIndex - firstIndex > 10) 107 | { 108 | var page = url.Substring(firstIndex, lastIndex - firstIndex); 109 | var length = url.Length - max + 3; 110 | if (page.Length > length) 111 | { 112 | url = url.Replace(page, string.Format("...{0}", page.Substring(length))); 113 | } 114 | } 115 | 116 | return url; 117 | } 118 | } -------------------------------------------------------------------------------- /Website/app_code/handlers/WhitespaceModule.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Web; 4 | using WebMarkupMin.Core; 5 | 6 | public class WhitespaceModule : IHttpModule 7 | { 8 | #region IHttpModule Members 9 | 10 | void IHttpModule.Dispose() 11 | { 12 | // Nothing to dispose; 13 | } 14 | 15 | void IHttpModule.Init(HttpApplication app) 16 | { 17 | app.PostRequestHandlerExecute += (o, e) => PostRequestHandlerExecute(app); 18 | } 19 | 20 | #endregion 21 | 22 | private void PostRequestHandlerExecute(HttpApplication app) 23 | { 24 | string contentType = app.Response.ContentType; 25 | string method = app.Request.HttpMethod; 26 | int status = app.Response.StatusCode; 27 | IHttpHandler handler = app.Context.CurrentHandler; 28 | 29 | if (contentType == "text/html" && method == "GET" && status == 200 && handler != null) 30 | { 31 | app.Response.Filter = new WhitespaceFilter(app.Response.Filter, app.Request.ContentEncoding); 32 | } 33 | } 34 | 35 | #region Stream filter 36 | 37 | private class WhitespaceFilter : Stream 38 | { 39 | private readonly Encoding _encoding; 40 | private readonly Stream _stream; 41 | private readonly MemoryStream _cache; 42 | private readonly static HtmlMinifier _minifier = new HtmlMinifier(new HtmlMinificationSettings 43 | { 44 | WhitespaceMinificationMode = WhitespaceMinificationMode.Aggressive, 45 | RemoveRedundantAttributes = false 46 | }); 47 | 48 | public WhitespaceFilter(Stream sink, Encoding encoding) 49 | { 50 | _stream = sink; 51 | _encoding = encoding; 52 | _cache = new MemoryStream(); 53 | } 54 | 55 | #region Properites 56 | 57 | public override bool CanRead 58 | { 59 | get { return true; } 60 | } 61 | 62 | public override bool CanSeek 63 | { 64 | get { return true; } 65 | } 66 | 67 | public override bool CanWrite 68 | { 69 | get { return true; } 70 | } 71 | 72 | public override void Flush() 73 | { 74 | _stream.Flush(); 75 | } 76 | 77 | public override long Length 78 | { 79 | get { return 0; } 80 | } 81 | 82 | private long _position; 83 | public override long Position 84 | { 85 | get { return _position; } 86 | set { _position = value; } 87 | } 88 | 89 | #endregion 90 | 91 | #region Methods 92 | 93 | public override int Read(byte[] buffer, int offset, int count) 94 | { 95 | return _stream.Read(buffer, offset, count); 96 | } 97 | 98 | public override long Seek(long offset, SeekOrigin origin) 99 | { 100 | return _stream.Seek(offset, origin); 101 | } 102 | 103 | public override void SetLength(long value) 104 | { 105 | _stream.SetLength(value); 106 | } 107 | 108 | public override void Write(byte[] buffer, int offset, int count) 109 | { 110 | _cache.Write(buffer, offset, count); 111 | } 112 | 113 | public override void Close() 114 | { 115 | byte[] buffer = _cache.ToArray(); 116 | int cacheSize = buffer.Length; 117 | 118 | string original = _encoding.GetString(buffer); 119 | string result = _minifier.Minify(original).MinifiedContent; 120 | byte[] output = _encoding.GetBytes(result); 121 | 122 | _stream.Write(output, 0, output.Length); 123 | _cache.Dispose(); 124 | _stream.Dispose(); 125 | } 126 | 127 | #endregion 128 | 129 | } 130 | 131 | #endregion 132 | 133 | } -------------------------------------------------------------------------------- /Website/themes/OneColumn/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | string next = Blog.GetNextPage(); 3 | string prev = Blog.GetPrevPage(); 4 | } 5 | 6 | 7 | 8 | 9 | @Page.Title 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | @AdminCss() 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | @if (!string.IsNullOrEmpty(prev)) 32 | { 33 | 34 | } 35 | @if (!string.IsNullOrEmpty(next)) 36 | { 37 | 38 | } 39 | 40 | 41 | 42 |
43 |
44 |
45 | @Blog.Description 46 |
47 | 48 |
49 | @RenderBody() 50 | 51 | @if (Page.ShowPaging != null) 52 | { 53 |
    54 | @if (Blog.GetPosts().Count() > Blog.PostsPerPage * Blog.CurrentPage) 55 | { 56 | 57 | } 58 | 59 | @if (Blog.CurrentPage > 1) 60 | { 61 | 62 | } 63 |
64 | } 65 |
66 |
67 | 68 |
69 | @if (!User.Identity.IsAuthenticated) 70 | { 71 | 72 | Sign in 73 | 74 | } 75 | 76 | Copyright © @DateTime.Now.Year 77 | Mads Kristensen 78 | 79 |
80 | 81 | 82 | @if ((Blog.CurrentPost != null && Blog.CurrentPost.AreCommentsOpen(Context)) || Blog.IsNewPost) 83 | { 84 | 85 | } 86 | 87 | @if (User.Identity.IsAuthenticated) 88 | { 89 | @RenderPage("~/views/AdminMenu.cshtml") 90 | } 91 | 92 | 93 | 94 | 95 | @helper AdminCss() 96 | { 97 | if (User.Identity.IsAuthenticated) 98 | { 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /packages/jQuery.2.1.3/Tools/common.ps1: -------------------------------------------------------------------------------- 1 | function Get-Checksum($file) { 2 | $cryptoProvider = New-Object "System.Security.Cryptography.MD5CryptoServiceProvider" 3 | 4 | $fileInfo = Get-Item $file 5 | trap { ; 6 | continue } $stream = $fileInfo.OpenRead() 7 | if ($? -eq $false) { 8 | # Couldn't open file for reading 9 | return $null 10 | } 11 | 12 | $bytes = $cryptoProvider.ComputeHash($stream) 13 | $checksum = '' 14 | foreach ($byte in $bytes) { 15 | $checksum += $byte.ToString('x2') 16 | } 17 | 18 | $stream.Close() | Out-Null 19 | 20 | return $checksum 21 | } 22 | 23 | function AddOrUpdate-Reference($scriptsFolderProjectItem, $fileNamePattern, $newFileName) { 24 | try { 25 | $referencesFileProjectItem = $scriptsFolderProjectItem.ProjectItems.Item("_references.js") 26 | } 27 | catch { 28 | # _references.js file not found 29 | return 30 | } 31 | 32 | if ($referencesFileProjectItem -eq $null) { 33 | # _references.js file not found 34 | return 35 | } 36 | 37 | $referencesFilePath = $referencesFileProjectItem.FileNames(1) 38 | $referencesTempFilePath = Join-Path $env:TEMP "_references.tmp.js" 39 | 40 | if ((Select-String $referencesFilePath -pattern $fileNamePattern).Length -eq 0) { 41 | # File has no existing matching reference line 42 | # Add the full reference line to the beginning of the file 43 | "/// " | Add-Content $referencesTempFilePath -Encoding UTF8 44 | Get-Content $referencesFilePath | Add-Content $referencesTempFilePath 45 | } 46 | else { 47 | # Loop through file and replace old file name with new file name 48 | Get-Content $referencesFilePath | ForEach-Object { $_ -replace $fileNamePattern, $newFileName } > $referencesTempFilePath 49 | } 50 | 51 | # Copy over the new _references.js file 52 | Copy-Item $referencesTempFilePath $referencesFilePath -Force 53 | Remove-Item $referencesTempFilePath -Force 54 | } 55 | 56 | function Remove-Reference($scriptsFolderProjectItem, $fileNamePattern) { 57 | try { 58 | $referencesFileProjectItem = $scriptsFolderProjectItem.ProjectItems.Item("_references.js") 59 | } 60 | catch { 61 | # _references.js file not found 62 | return 63 | } 64 | 65 | if ($referencesFileProjectItem -eq $null) { 66 | return 67 | } 68 | 69 | $referencesFilePath = $referencesFileProjectItem.FileNames(1) 70 | $referencesTempFilePath = Join-Path $env:TEMP "_references.tmp.js" 71 | 72 | if ((Select-String $referencesFilePath -pattern $fileNamePattern).Length -eq 1) { 73 | # Delete the line referencing the file 74 | Get-Content $referencesFilePath | ForEach-Object { if (-not ($_ -match $fileNamePattern)) { $_ } } > $referencesTempFilePath 75 | 76 | # Copy over the new _references.js file 77 | Copy-Item $referencesTempFilePath $referencesFilePath -Force 78 | Remove-Item $referencesTempFilePath -Force 79 | } 80 | } 81 | 82 | function Delete-ProjectItem($item) { 83 | $itemDeleted = $false 84 | for ($1=1; $i -le 5; $i++) { 85 | try { 86 | $item.Delete() 87 | $itemDeleted = $true 88 | break 89 | } 90 | catch { 91 | # Try again in 200ms 92 | [System.Threading.Thread]::Sleep(200) 93 | } 94 | } 95 | if ($itemDeleted -eq $false) { 96 | throw "Unable to delete project item after five attempts." 97 | } 98 | } 99 | 100 | # Extract the version number from the jquery file in the package's content\scripts folder 101 | $packageScriptsFolder = Join-Path $installPath Content\Scripts 102 | $jqueryFileName = Join-Path $packageScriptsFolder "jquery-*.js" | Get-ChildItem -Exclude "*.min.js","*-vsdoc.js" | Split-Path -Leaf 103 | $jqueryFileNameRegEx = "jquery-((?:\d+\.)?(?:\d+\.)?(?:\d+\.)?(?:\d+)).js" 104 | $jqueryFileName -match $jqueryFileNameRegEx 105 | $ver = $matches[1] 106 | 107 | $intelliSenseFileName = "jquery-$ver.intellisense.js" 108 | 109 | # Get the project item for the scripts folder 110 | try { 111 | $scriptsFolderProjectItem = $project.ProjectItems.Item("Scripts") 112 | $projectScriptsFolderPath = $scriptsFolderProjectItem.FileNames(1) 113 | } 114 | catch { 115 | # No Scripts folder 116 | Write-Host "No scripts folder found" 117 | } -------------------------------------------------------------------------------- /Website/themes/OffCanvas/Post.cshtml: -------------------------------------------------------------------------------- 1 | @if (Page.ShowPaging == true) 2 | { 3 |
4 |

@Model.Title

5 |

@MarkupHelper.GetDescription(Model.Content, 235, "...")

6 |

View details »

7 |
8 | } 9 | else 10 | { 11 |
12 |
13 |

14 | 15 |

16 |
17 | 18 | 19 | 20 | @Model.CountApprovedComments(Context) Comments 21 | 22 | @Categories() 23 |
24 |
25 | 26 | @if ((Blog.CurrentPost != null || Blog.IsNewPost) && User.Identity.IsAuthenticated) 27 | { 28 | 32 | } 33 | 34 |
@Html.Raw(Blog.IsEditing ? Model.Content : Model.GetHtmlContent())
35 | @if (Blog.CurrentPost != null) 36 | { 37 |
38 | @if (Model.CountApprovedComments(Context) > 0) 39 | { 40 |

Comments

41 | } 42 | 43 | @foreach (Comment comment in Model.Comments) 44 | { 45 | if (comment.IsApproved || !Blog.ModerateComments || Context.User.Identity.IsAuthenticated) 46 | { 47 | @RenderPage("Comment.cshtml", comment) 48 | } 49 | } 50 |
51 | 52 | if (Model.AreCommentsOpen(Context)) 53 | { 54 | @RenderPage("~/views/CommentForm.cshtml") 55 | } 56 | } 57 |
58 | 59 | @helper Categories() 60 | { 61 | if (Model.Categories.Length > 0 || User.Identity.IsAuthenticated) 62 | { 63 |
    64 |
  •   Posted in: 
  • 65 | @foreach (string cat in Model.Categories) 66 | { 67 |
  • 68 | @cat 69 |
  • 70 | } 71 |
72 | } 73 | } 74 | } 75 | 76 | @functions{ 77 | public class MarkupHelper 78 | { 79 | #region excerpt generation 80 | 81 | public static string GetDescription(string content, int length = 300, string ommission = "...") 82 | { 83 | return TruncateHtml(StripTags(content), length, ommission); 84 | } 85 | 86 | public static string TruncateHtml(string input, int length = 300, string ommission = "...") 87 | { 88 | if (input == null || input.Length < length) 89 | return input; 90 | int nextSpace = input.LastIndexOf(" ", length); 91 | return string.Format("{0}" + ommission, 92 | input.Substring(0, (nextSpace > 0) ? nextSpace : length).Trim()); 93 | } 94 | 95 | public static string StripTags(string markup) 96 | { 97 | try 98 | { 99 | StringReader stringReader = new StringReader(markup); 100 | System.Xml.XPath.XPathDocument xPathdocument; 101 | using (System.Xml.XmlReader xmlReader = System.Xml.XmlReader.Create(stringReader, 102 | new System.Xml.XmlReaderSettings() { ConformanceLevel = System.Xml.ConformanceLevel.Fragment })) 103 | { 104 | xPathdocument = new System.Xml.XPath.XPathDocument(xmlReader); 105 | } 106 | 107 | return xPathdocument.CreateNavigator().Value; 108 | } 109 | catch 110 | { 111 | return string.Empty; 112 | } 113 | } 114 | 115 | #endregion 116 | } 117 | } -------------------------------------------------------------------------------- /Website/bin/System.Web.WebPages.Deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.Web.WebPages.Deployment 5 | 6 | 7 | 8 | Provides a registration point for pre-application start code for Web Pages deployment. 9 | 10 | 11 | Registers pre-application start code for Web Pages deployment. 12 | 13 | 14 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Provides methods that are used to get deployment information about the Web application. 15 | 16 | 17 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the assembly path for the Web Pages deployment. 18 | The assembly path for the Web Pages deployment. 19 | The Web Pages version. 20 | 21 | 22 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the Web Pages version from the given binary path. 23 | The Web Pages version. 24 | The binary path for the Web Pages. 25 | 26 | 27 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the assembly references from the given path regardless of the Web Pages version. 28 | The dictionary containing the assembly references of the Web Pages and its version. 29 | The path to the Web Pages application. 30 | 31 | 32 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the maximum version of the Web Pages loaded assemblies. 33 | The maximum version of the Web Pages loaded assemblies. 34 | 35 | 36 | Gets the Web Pages version from the given path. 37 | The Web Pages version. 38 | The path of the root directory for the application. 39 | 40 | 41 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the Web Pages version using the configuration settings with the specified path. 42 | The Web Pages version. 43 | The path to the application settings. 44 | 45 | 46 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Returns the assemblies for this Web Pages deployment. 47 | A list containing the assemblies for this Web Pages deployment. 48 | 49 | 50 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Indicates whether the Web Pages deployment is enabled. 51 | true if the Web Pages deployment is enabled; otherwise, false. 52 | The path to the Web Pages deployment. 53 | 54 | 55 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Indicates whether the Web Pages deployment is explicitly disabled. 56 | true if the Web Pages deployment is explicitly disabled; otherwise, false. 57 | The path to the Web Pages deployment. 58 | 59 | 60 | -------------------------------------------------------------------------------- /packages/Microsoft.AspNet.WebPages.3.2.3/lib/net45/System.Web.WebPages.Deployment.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | System.Web.WebPages.Deployment 5 | 6 | 7 | 8 | Provides a registration point for pre-application start code for Web Pages deployment. 9 | 10 | 11 | Registers pre-application start code for Web Pages deployment. 12 | 13 | 14 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Provides methods that are used to get deployment information about the Web application. 15 | 16 | 17 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the assembly path for the Web Pages deployment. 18 | The assembly path for the Web Pages deployment. 19 | The Web Pages version. 20 | 21 | 22 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the Web Pages version from the given binary path. 23 | The Web Pages version. 24 | The binary path for the Web Pages. 25 | 26 | 27 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the assembly references from the given path regardless of the Web Pages version. 28 | The dictionary containing the assembly references of the Web Pages and its version. 29 | The path to the Web Pages application. 30 | 31 | 32 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the maximum version of the Web Pages loaded assemblies. 33 | The maximum version of the Web Pages loaded assemblies. 34 | 35 | 36 | Gets the Web Pages version from the given path. 37 | The Web Pages version. 38 | The path of the root directory for the application. 39 | 40 | 41 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Gets the Web Pages version using the configuration settings with the specified path. 42 | The Web Pages version. 43 | The path to the application settings. 44 | 45 | 46 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Returns the assemblies for this Web Pages deployment. 47 | A list containing the assemblies for this Web Pages deployment. 48 | 49 | 50 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Indicates whether the Web Pages deployment is enabled. 51 | true if the Web Pages deployment is enabled; otherwise, false. 52 | The path to the Web Pages deployment. 53 | 54 | 55 | This type/member supports the .NET Framework infrastructure and is not intended to be used directly from your code.Indicates whether the Web Pages deployment is explicitly disabled. 56 | true if the Web Pages deployment is explicitly disabled; otherwise, false. 57 | The path to the Web Pages deployment. 58 | 59 | 60 | -------------------------------------------------------------------------------- /Website/themes/OffCanvas/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | string next = Blog.GetNextPage(); 3 | string prev = Blog.GetPrevPage(); 4 | } 5 | 6 | 7 | 8 | 9 | @Page.Title 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | @AdminCss() 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | @if (!string.IsNullOrEmpty(prev)) 32 | { 33 | 34 | } 35 | @if (!string.IsNullOrEmpty(next)) 36 | { 37 | 38 | } 39 | 40 | 41 | 42 |
43 |
44 |
45 |
46 | @Blog.Description 47 |
48 | 49 |
50 | @RenderBody() 51 | 52 | @if (Page.ShowPaging != null) 53 | { 54 |
    55 | @if (Blog.GetPosts().Count() > Blog.PostsPerPage * Blog.CurrentPage) 56 | { 57 | 58 | } 59 | 60 | @if (Blog.CurrentPage > 1) 61 | { 62 | 63 | } 64 |
65 | } 66 |
67 | 68 | 85 |
86 |
87 | 88 |
89 | @if (!User.Identity.IsAuthenticated) 90 | { 91 | 92 | Sign in 93 | 94 | } 95 | 96 | Copyright © @DateTime.Now.Year 97 | Mads Kristensen 98 | 99 |
100 | 101 | 102 | @if ((Blog.CurrentPost != null && Blog.CurrentPost.AreCommentsOpen(Context)) || Blog.IsNewPost) 103 | { 104 | 105 | } 106 | 107 | @if (User.Identity.IsAuthenticated) 108 | { 109 | @RenderPage("~/views/AdminMenu.cshtml") 110 | } 111 | 112 | 113 | 114 | 115 | @helper AdminCss() 116 | { 117 | if (User.Identity.IsAuthenticated) 118 | { 119 | 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /Website/themes/TwoColumns/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | string next = Blog.GetNextPage(); 3 | string prev = Blog.GetPrevPage(); 4 | } 5 | 6 | 7 | 8 | 9 | @Page.Title 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | @AdminCss() 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | @if (!string.IsNullOrEmpty(prev)) 32 | { 33 | 34 | } 35 | @if (!string.IsNullOrEmpty(next)) 36 | { 37 | 38 | } 39 | 40 | 41 | 42 |
43 |
44 |
45 | @Blog.Description 46 |
47 | 48 |
49 | @RenderBody() 50 | 51 | @if (Page.ShowPaging != null) 52 | { 53 |
    54 | @if (Blog.GetPosts().Count() > Blog.PostsPerPage * Blog.CurrentPage) 55 | { 56 | 57 | } 58 | 59 | @if (Blog.CurrentPage > 1) 60 | { 61 | 62 | } 63 |
64 | } 65 |
66 | 67 | 91 |
92 | 93 |
94 | @if (!User.Identity.IsAuthenticated) 95 | { 96 | 97 | Sign in 98 | 99 | } 100 | 101 | Copyright © @DateTime.Now.Year 102 | Mads Kristensen 103 | 104 |
105 | 106 | 107 | @if ((Blog.CurrentPost != null && Blog.CurrentPost.AreCommentsOpen(Context)) || Blog.IsNewPost) 108 | { 109 | 110 | } 111 | 112 | @if (User.Identity.IsAuthenticated) 113 | { 114 | @RenderPage("~/views/AdminMenu.cshtml") 115 | } 116 | 117 | 118 | 119 | 120 | @helper AdminCss() 121 | { 122 | if (User.Identity.IsAuthenticated) 123 | { 124 | 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /Website/posts/9fa751db-c036-435b-97e3-bd6373b0991b.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Web Developer Checklist 4 | chrome-extension-web-developer-checklist 5 | 6 | 2013-06-06 11:44:23 7 | 2014-05-30 15:15:19 8 | Earlier this year, Sayed and I released something really cool 9 | <p>Earlier this year, <a href="http://sedodream.com/" target="_blank">Sayed</a> and I released <a href="http://webdevchecklist.com/" target="_blank">Web Developer Checklist</a> to help web developer adhere to best practices. Checklists like these can be really helpful to make sure we don’t forget anything before releasing new or updated websites. </p> <p><img width="279" height="580" title="image" style="margin-left: 10px; float: right;" alt="image" src="/posts/files/image.png" />Now we’re introducing Web Developer Checklist as a <a href="https://chrome.google.com/webstore/detail/web-developer-checklist/iahamcpedabephpcgkeikbclmaljebjp" target="_blank">Chrome extension</a> that can automate a big portion of the checklist. The extension let’s you run checks for various best practices on any website - including your own running from localhost.</p> <p>It performs a series of checks by analyzing the DOM as well as integrating with 3<sup>rd</sup>-party services like <a href="https://developers.google.com/speed/pagespeed/" target="_blank">Google PageSpeed</a>.</p> <h2>Next steps</h2> <p>This first version of the <a href="https://chrome.google.com/webstore/detail/web-developer-checklist/iahamcpedabephpcgkeikbclmaljebjp" target="_blank">Web Developer Checklist Chrome extension</a> does a series of really valuable checks. The next releases will have even more.</p> <p>Specifically, we’re looking at adding:</p> <ul> <li>HTML validation </li><li>CSS validation </li><li>Accessibility validation </li><li>JSHint </li><li>CssLint </li><li>Guidance for each item</li></ul> <p>We think these checks would be hugely beneficial. If you have ideas for other checks that can be automated, please let us know in the comments below.</p> <h2>Open source</h2> <p>The Web Developer Checklist Chrome extension is open source and hosted on our <a href="https://github.com/ligershark/BestPracticesChromeExtension" target="_blank">GitHub org</a>. As always, pull requests are more than welcome.</p> 10 | true 11 | 12 | Chrome 13 | Best Practices 14 | 15 | 16 | 17 | Jason 18 | jason.irwin@hotmail.com 19 | 20 | 21 | 22 | 2013-06-06 12:14:47 23 | Very useful extension. In the few minutes I&#39;ve played with it so far I&#39;ve found a few items that can be improved on my site. I&#39;d love to see some guidance baked into the tool. For example, for &quot;Add meaning with Microdata&quot; it would be beneficial to provide a link for further reading/some rudimentary tips, etc. <br /><br />Great work - I look forward to seeing this extension evolving! 24 | 25 | 26 | Mads Kristensen 27 | post@madskristensen.dk 28 | 29 | 30 | 31 | 2013-06-06 12:23:46 32 | Yes, I should update the roadmap because that is exactly what we are planning for as well. Thanks for the heads up! 33 | 34 | 35 | Maiis 36 | evuigner@gmail.com 37 | 38 | 39 | 40 | 2013-06-06 12:55:53 41 | This page dosen&#39;t get a green status :p 42 | 43 | 44 | Mads Kristensen 45 | post@madskristensen.dk 46 | 47 | 48 | 49 | 2013-06-06 13:12:43 50 | Yeah, that is rather ironic :) 51 | 52 | 53 | Maiis 54 | evuigner@gmail.com 55 | 56 | 57 | 58 | 2013-06-06 14:55:53 59 | A new not yet approved comment. 60 | 61 | 62 | -------------------------------------------------------------------------------- /Website/views/AdminMenu.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | var isPublished = Blog.CurrentPost != null && Blog.CurrentPost.IsPublished; 3 | var path = !string.IsNullOrWhiteSpace(Blog.BlogPath) ? "/" + Blog.BlogPath : ""; 4 | } 5 | 6 | 80 | 81 | @if (!string.IsNullOrEmpty(Blog.CurrentSlug)) 82 | { 83 | 84 | 85 | 86 | 87 | 88 | } 89 | -------------------------------------------------------------------------------- /Website/themes/OneColumn/site.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width: 1280px) { 2 | @-ms-viewport { 3 | width: 800px; 4 | } 5 | } 6 | 7 | @media only screen and (max-width: 768px) { 8 | @-ms-viewport { 9 | width: 600px; 10 | } 11 | } 12 | 13 | html, body{ 14 | height: 100%; 15 | } 16 | 17 | body { 18 | font: 1.8em/1.5 'Century Gothic', Verdana, Geneva, 'DejaVu Sans', sans-serif; 19 | margin: 0; 20 | } 21 | 22 | img, video, iframe { 23 | max-width: 100%; 24 | } 25 | 26 | td img, video, iframe { 27 | max-width: none; 28 | } 29 | 30 | a { 31 | color: #3277b3; 32 | } 33 | 34 | .container { 35 | min-height: 100%; 36 | padding-bottom: 35px; 37 | } 38 | 39 | .container > header { 40 | border: none; 41 | text-align: center; 42 | margin: 0 0 3em 0; 43 | } 44 | 45 | .container > header a { 46 | font-size: 4em; 47 | color: #000; 48 | } 49 | 50 | div[role=main] { 51 | margin-bottom: 2em; 52 | overflow: auto; 53 | } 54 | 55 | .pager { 56 | margin-top: 2em; 57 | } 58 | 59 | input[type], button, textarea { 60 | font: inherit !important; 61 | } 62 | 63 | /*#region Post */ 64 | 65 | .post header h1 { 66 | margin: 0 0 3px 0; 67 | } 68 | 69 | .post header a { 70 | color: #000; 71 | } 72 | 73 | .post header div { 74 | font-size: .75em; 75 | opacity: .8; 76 | margin-bottom: 1em; 77 | } 78 | 79 | .post header div a { 80 | margin-left: 10px; 81 | } 82 | 83 | .post header div .categories { 84 | margin: 0; 85 | padding: 10px; 86 | display: inline; 87 | } 88 | 89 | .post header div .categories a { 90 | margin: 0; 91 | } 92 | 93 | .post header div .categories li { 94 | display: inline; 95 | } 96 | 97 | .post header div .categories li:not(:first-child):not(:last-child):after { 98 | content: ", "; 99 | } 100 | 101 | .post abbr { 102 | border: none; 103 | } 104 | 105 | .post h2 { 106 | margin-bottom: 0; 107 | font-size: 1.3em; 108 | } 109 | 110 | .post h3 { 111 | margin-bottom: 0; 112 | font-size: 1.1em; 113 | } 114 | 115 | .post div[itemprop='articleBody'] { 116 | border-bottom: 1px solid #d3d3d3; 117 | padding-bottom: .5em; 118 | margin-bottom: 2em; 119 | -ms-text-size-adjust: 150%; 120 | } 121 | 122 | .post p { 123 | padding: .2em 0; 124 | } 125 | 126 | /*#endregion */ 127 | 128 | /*#region Comments */ 129 | 130 | #comments article { 131 | position: relative; 132 | max-height: 9999px; 133 | overflow: hidden; 134 | -webkit-transition: height .5s ease-in-out; 135 | -moz-transition: height .5s ease-in-out; 136 | -o-transition: height .5s ease-in-out; 137 | transition: height .5s ease-in-out; 138 | } 139 | 140 | #comments article div { 141 | padding-left: 70px; 142 | } 143 | 144 | #comments article div p { 145 | border: 1px solid #d3d3d3; 146 | border-radius: 5px; 147 | padding: 7px; 148 | position: relative; 149 | } 150 | 151 | #comments article div p:before { 152 | content: ""; 153 | position: absolute; 154 | top: 10px; 155 | left: -10px; 156 | border-style: solid; 157 | border-width: 9px 9px 9px 0; 158 | border-color: transparent #d3d3d3; 159 | } 160 | 161 | #comments article div p:after { 162 | content: ""; 163 | position: absolute; 164 | top: 10px; 165 | left: -9px; 166 | border-style: solid; 167 | border-width: 9px 9px 9px 0; 168 | border-color: transparent #ffffff; 169 | } 170 | 171 | #comments article.self div p { 172 | background: #f7f7f7; 173 | } 174 | 175 | #comments article.self div p:after { 176 | border-color: transparent #f7f7f7; 177 | } 178 | 179 | #comments article [itemprop='commentText'] { 180 | margin: 0; 181 | } 182 | 183 | #comments article img { 184 | border-radius: 5px; 185 | position: absolute; 186 | top: 19px; 187 | } 188 | 189 | #comments article [itemprop='commentTime'] { 190 | display: block; 191 | text-align: right; 192 | font-size: .7em; 193 | } 194 | 195 | #comments article [itemprop='creator'] { 196 | font-size: .8em; 197 | margin-left: 7px; 198 | } 199 | 200 | #comments article [itemprop='approvalWarning'] { 201 | margin-left: 70px; 202 | font-size: 0.6em; 203 | background-color: rgba(255, 255, 0, 0.50); 204 | padding: 5px; 205 | } 206 | 207 | /*#endregion */ 208 | 209 | /*#region Comment form */ 210 | 211 | #commentform { 212 | margin-top: 2em; 213 | } 214 | 215 | #status { 216 | margin-left: 1em; 217 | } 218 | 219 | /*#endregion */ 220 | 221 | /*#region Footer */ 222 | 223 | footer { 224 | background-color: #181818; 225 | color: #fff; 226 | font-size: .85em; 227 | padding: 5px; 228 | clear: both; 229 | position: relative; 230 | margin: -35px 0 0 0; 231 | height: 35px; 232 | } 233 | 234 | footer p { 235 | padding: 1em 0; 236 | text-align: center; 237 | } 238 | 239 | footer a { 240 | color: #a9d2e1; 241 | } 242 | 243 | /*#endregion */ 244 | 245 | .slideClone img { 246 | float: left; 247 | } 248 | -------------------------------------------------------------------------------- /Website/themes/TwoColumns/site.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width: 1280px) { 2 | @-ms-viewport { 3 | width: 800px; 4 | } 5 | } 6 | 7 | @media only screen and (max-width: 768px) { 8 | @-ms-viewport { 9 | width: 600px; 10 | } 11 | } 12 | 13 | html, body{ 14 | height: 100%; 15 | } 16 | 17 | body { 18 | font: 1.8em/1.5 'Century Gothic', Verdana, Geneva, 'DejaVu Sans', sans-serif; 19 | margin: 0; 20 | } 21 | 22 | img, video, iframe { 23 | max-width: 100%; 24 | } 25 | 26 | td img, video, iframe { 27 | max-width: none; 28 | } 29 | 30 | a { 31 | color: #3277b3; 32 | } 33 | 34 | .container { 35 | min-height: 100%; 36 | padding-bottom: 35px; 37 | } 38 | 39 | .container > header { 40 | border: none; 41 | text-align: center; 42 | margin: 0 0 3em 0; 43 | } 44 | 45 | .container > header a { 46 | font-size: 4em; 47 | color: #000; 48 | } 49 | 50 | div[role=main] { 51 | margin-bottom: 2em; 52 | overflow: auto; 53 | } 54 | 55 | .pager { 56 | margin-top: 2em; 57 | } 58 | 59 | input[type], button, textarea { 60 | font: inherit !important; 61 | } 62 | 63 | /*#region Post */ 64 | 65 | .post header h1 { 66 | margin: 0 0 3px 0; 67 | } 68 | 69 | .post header a { 70 | color: #000; 71 | } 72 | 73 | .post header div { 74 | font-size: .75em; 75 | opacity: .8; 76 | margin-bottom: 1em; 77 | } 78 | 79 | .post header div a { 80 | margin-left: 10px; 81 | } 82 | 83 | .post header div .categories { 84 | margin: 0; 85 | padding: 10px; 86 | display: inline; 87 | } 88 | 89 | .post header div .categories a { 90 | margin: 0; 91 | } 92 | 93 | .post header div .categories li { 94 | display: inline; 95 | } 96 | 97 | .post header div .categories li:not(:first-child):not(:last-child):after { 98 | content: ", "; 99 | } 100 | 101 | .post abbr { 102 | border: none; 103 | } 104 | 105 | .post h2 { 106 | margin-bottom: 0; 107 | font-size: 1.3em; 108 | } 109 | 110 | .post h3 { 111 | margin-bottom: 0; 112 | font-size: 1.1em; 113 | } 114 | 115 | .post div[itemprop='articleBody'] { 116 | border-bottom: 1px solid #d3d3d3; 117 | padding-bottom: .5em; 118 | margin-bottom: 2em; 119 | -ms-text-size-adjust: 150%; 120 | } 121 | 122 | .post p { 123 | padding: .2em 0; 124 | } 125 | 126 | /*#endregion */ 127 | 128 | /*#region Comments */ 129 | 130 | #comments article { 131 | position: relative; 132 | max-height: 9999px; 133 | overflow: hidden; 134 | -webkit-transition: height .5s ease-in-out; 135 | -moz-transition: height .5s ease-in-out; 136 | -o-transition: height .5s ease-in-out; 137 | transition: height .5s ease-in-out; 138 | } 139 | 140 | #comments article div { 141 | padding-left: 70px; 142 | } 143 | 144 | #comments article div p { 145 | border: 1px solid #d3d3d3; 146 | border-radius: 5px; 147 | padding: 7px; 148 | position: relative; 149 | } 150 | 151 | #comments article div p:before { 152 | content: ""; 153 | position: absolute; 154 | top: 10px; 155 | left: -10px; 156 | border-style: solid; 157 | border-width: 9px 9px 9px 0; 158 | border-color: transparent #d3d3d3; 159 | } 160 | 161 | #comments article div p:after { 162 | content: ""; 163 | position: absolute; 164 | top: 10px; 165 | left: -9px; 166 | border-style: solid; 167 | border-width: 9px 9px 9px 0; 168 | border-color: transparent #ffffff; 169 | } 170 | 171 | #comments article.self div p { 172 | background: #f7f7f7; 173 | } 174 | 175 | #comments article.self div p:after { 176 | border-color: transparent #f7f7f7; 177 | } 178 | 179 | #comments article [itemprop='commentText'] { 180 | margin: 0; 181 | } 182 | 183 | #comments article img { 184 | border-radius: 5px; 185 | position: absolute; 186 | top: 19px; 187 | } 188 | 189 | #comments article [itemprop='commentTime'] { 190 | display: block; 191 | text-align: right; 192 | font-size: .7em; 193 | } 194 | 195 | #comments article [itemprop='creator'] { 196 | font-size: .8em; 197 | margin-left: 7px; 198 | } 199 | 200 | #comments article [itemprop='approvalWarning'] { 201 | margin-left: 70px; 202 | font-size: 0.6em; 203 | background-color: rgba(255, 255, 0, 0.50); 204 | padding: 5px; 205 | } 206 | 207 | /*#endregion */ 208 | 209 | /*#region Comment form */ 210 | 211 | #commentform { 212 | margin-top: 2em; 213 | } 214 | 215 | #status { 216 | margin-left: 1em; 217 | } 218 | 219 | /*#endregion */ 220 | 221 | /*#region Footer */ 222 | 223 | footer { 224 | background-color: #181818; 225 | color: #fff; 226 | font-size: .85em; 227 | padding: 5px; 228 | clear: both; 229 | position: relative; 230 | margin: -35px 0 0 0; 231 | height: 35px; 232 | } 233 | 234 | footer p { 235 | padding: 1em 0; 236 | text-align: center; 237 | } 238 | 239 | footer a { 240 | color: #a9d2e1; 241 | } 242 | 243 | /*#endregion */ 244 | 245 | .slideClone img { 246 | float: left; 247 | } 248 | -------------------------------------------------------------------------------- /Website/themes/OffCanvas/site.css: -------------------------------------------------------------------------------- 1 | @media only screen and (max-width: 1280px) { 2 | @-ms-viewport { 3 | width: 800px; 4 | } 5 | } 6 | 7 | @media only screen and (max-width: 768px) { 8 | @-ms-viewport { 9 | width: 600px; 10 | } 11 | } 12 | 13 | html, body { 14 | height: 100%; 15 | } 16 | 17 | body { 18 | font: 1.8em/1.5 'Century Gothic', Verdana, Geneva, 'DejaVu Sans', sans-serif; 19 | margin: 0; 20 | } 21 | 22 | img, video, iframe { 23 | max-width: 100%; 24 | } 25 | 26 | td img, video, iframe { 27 | max-width: none; 28 | } 29 | 30 | a { 31 | color: #3277b3; 32 | } 33 | 34 | .container { 35 | min-height: 100%; 36 | padding-bottom: 35px; 37 | } 38 | 39 | .container > div > header { 40 | border: none; 41 | text-align: center; 42 | margin: 0 0 3em 0; 43 | } 44 | 45 | .container > div > header a { 46 | font-size: 4em; 47 | color: #000; 48 | } 49 | 50 | div[role=main] { 51 | margin-bottom: 2em; 52 | overflow: auto; 53 | } 54 | 55 | .pager { 56 | margin-top: 2em; 57 | } 58 | 59 | input[type], button, textarea { 60 | font: inherit !important; 61 | } 62 | 63 | /*#region Post */ 64 | 65 | .post header h1 { 66 | margin: 0 0 3px 0; 67 | } 68 | 69 | .post header a { 70 | color: #000; 71 | } 72 | 73 | .post header div { 74 | font-size: .75em; 75 | opacity: .8; 76 | margin-bottom: 1em; 77 | } 78 | 79 | .post header div a { 80 | margin-left: 10px; 81 | } 82 | 83 | .post header div .categories { 84 | margin: 0; 85 | padding: 10px; 86 | display: inline; 87 | } 88 | 89 | .post header div .categories a { 90 | margin: 0; 91 | } 92 | 93 | .post header div .categories li { 94 | display: inline; 95 | } 96 | 97 | .post header div .categories li:not(:first-child):not(:last-child):after { 98 | content: ", "; 99 | } 100 | 101 | .post abbr { 102 | border: none; 103 | } 104 | 105 | .post h2 { 106 | margin-bottom: 0; 107 | font-size: 1.3em; 108 | } 109 | 110 | .post h3 { 111 | margin-bottom: 0; 112 | font-size: 1.1em; 113 | } 114 | 115 | .post div[itemprop='articleBody'] { 116 | border-bottom: 1px solid #d3d3d3; 117 | padding-bottom: .5em; 118 | margin-bottom: 2em; 119 | -ms-text-size-adjust: 150%; 120 | } 121 | 122 | .post p { 123 | padding: .2em 0; 124 | } 125 | 126 | /*#endregion */ 127 | 128 | /*#region Comments */ 129 | 130 | #comments article { 131 | position: relative; 132 | max-height: 9999px; 133 | overflow: hidden; 134 | -webkit-transition: height .5s ease-in-out; 135 | -moz-transition: height .5s ease-in-out; 136 | -o-transition: height .5s ease-in-out; 137 | transition: height .5s ease-in-out; 138 | } 139 | 140 | #comments article div { 141 | padding-left: 70px; 142 | } 143 | 144 | #comments article div p { 145 | border: 1px solid #d3d3d3; 146 | border-radius: 5px; 147 | padding: 7px; 148 | position: relative; 149 | } 150 | 151 | #comments article div p:before { 152 | content: ""; 153 | position: absolute; 154 | top: 10px; 155 | left: -10px; 156 | border-style: solid; 157 | border-width: 9px 9px 9px 0; 158 | border-color: transparent #d3d3d3; 159 | } 160 | 161 | #comments article div p:after { 162 | content: ""; 163 | position: absolute; 164 | top: 10px; 165 | left: -9px; 166 | border-style: solid; 167 | border-width: 9px 9px 9px 0; 168 | border-color: transparent #ffffff; 169 | } 170 | 171 | #comments article.self div p { 172 | background: #f7f7f7; 173 | } 174 | 175 | #comments article.self div p:after { 176 | border-color: transparent #f7f7f7; 177 | } 178 | 179 | #comments article [itemprop='commentText'] { 180 | margin: 0; 181 | } 182 | 183 | #comments article img { 184 | border-radius: 5px; 185 | position: absolute; 186 | top: 19px; 187 | } 188 | 189 | #comments article [itemprop='commentTime'] { 190 | display: block; 191 | text-align: right; 192 | font-size: .7em; 193 | } 194 | 195 | #comments article [itemprop='creator'] { 196 | font-size: .8em; 197 | margin-left: 7px; 198 | } 199 | 200 | #comments article [itemprop='approvalWarning'] { 201 | margin-left: 70px; 202 | font-size: 0.6em; 203 | background-color: rgba(255, 255, 0, 0.50); 204 | padding: 5px; 205 | } 206 | 207 | /*#endregion */ 208 | 209 | /*#region Comment form */ 210 | 211 | #commentform { 212 | margin-top: 2em; 213 | } 214 | 215 | #status { 216 | margin-left: 1em; 217 | } 218 | 219 | /*#endregion */ 220 | 221 | /*#region Footer */ 222 | 223 | footer { 224 | background-color: #181818; 225 | color: #fff; 226 | font-size: .85em; 227 | padding: 5px; 228 | clear: both; 229 | position: relative; 230 | margin: -35px 0 0 0; 231 | height: 35px; 232 | } 233 | 234 | footer p { 235 | padding: 1em 0; 236 | text-align: center; 237 | } 238 | 239 | footer a { 240 | color: #a9d2e1; 241 | } 242 | 243 | /*#endregion */ 244 | 245 | .slideClone img { 246 | float: left; 247 | } 248 | 249 | .excerpt { 250 | font-size: 14px; 251 | } -------------------------------------------------------------------------------- /Website/app_code/handlers/PostHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.Linq; 5 | using System.Security.Policy; 6 | using System.Text; 7 | using System.Text.RegularExpressions; 8 | using System.Web; 9 | 10 | public class PostHandler : IHttpHandler 11 | { 12 | public void ProcessRequest(HttpContext context) 13 | { 14 | Blog.ValidateToken(context); 15 | 16 | if (!context.User.Identity.IsAuthenticated) 17 | throw new HttpException(403, "No access"); 18 | 19 | string mode = context.Request.QueryString["mode"]; 20 | string id = context.Request.Form["id"]; 21 | 22 | if (mode == "delete") 23 | { 24 | DeletePost(id); 25 | } 26 | else if (mode == "save") 27 | { 28 | EditPost(id, context.Request.Form["title"], context.Request.Form["excerpt"], context.Request.Form["content"], bool.Parse(context.Request.Form["isPublished"]), context.Request.Form["categories"].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)); 29 | } 30 | } 31 | 32 | private void DeletePost(string id) 33 | { 34 | Post post = Storage.GetAllPosts().FirstOrDefault(p => p.ID == id); 35 | 36 | if (post == null) 37 | throw new HttpException(404, "The post does not exist"); 38 | 39 | Storage.Delete(post); 40 | } 41 | 42 | private void EditPost(string id, string title, string excerpt, string content, bool isPublished, string[] categories) 43 | { 44 | Post post = Storage.GetAllPosts().FirstOrDefault(p => p.ID == id); 45 | 46 | if (post != null) 47 | { 48 | post.Title = title; 49 | post.Excerpt = excerpt; 50 | post.Content = content; 51 | post.Categories = categories; 52 | } 53 | else 54 | { 55 | post = new Post() { Title = title, Excerpt = excerpt, Content = content, Slug = CreateSlug(title), Categories = categories }; 56 | HttpContext.Current.Response.Write(post.Url); 57 | } 58 | 59 | SaveFilesToDisk(post); 60 | 61 | post.IsPublished = isPublished; 62 | Storage.Save(post); 63 | } 64 | 65 | private void SaveFilesToDisk(Post post) 66 | { 67 | foreach (Match match in Regex.Matches(post.Content, "(src|href)=\"(data:([^\"]+))\"(>.*?)?")) 68 | { 69 | string extension = string.Empty; 70 | string filename = string.Empty; 71 | string[] allowedExtensions = new [] { 72 | ".jpg", 73 | ".jpeg", 74 | ".gif", 75 | ".png", 76 | ".webp" 77 | }; 78 | 79 | // Image 80 | if (match.Groups[1].Value == "src") 81 | { 82 | extension = Regex.Match(match.Value, "data:([^/]+)/([a-z]+);base64").Groups[2].Value; 83 | } 84 | // Other file type 85 | else 86 | { 87 | // Entire filename 88 | extension = Regex.Match(match.Value, "data:([^/]+)/([a-z0-9+-.]+);base64.*\">(.*)").Groups[3].Value; 89 | } 90 | 91 | if (allowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase)) 92 | { 93 | byte[] bytes = ConvertToBytes(match.Groups[2].Value); 94 | string path = Blog.SaveFileToDisk(bytes, extension); 95 | 96 | string value = string.Format("src=\"{0}\" alt=\"\" ", path); 97 | 98 | if (match.Groups[1].Value == "href") 99 | value = string.Format("href=\"{0}\"", path); 100 | 101 | Match m = Regex.Match(match.Value, "(src|href)=\"(data:([^\"]+))\""); 102 | post.Content = post.Content.Replace(m.Value, value); 103 | } 104 | } 105 | } 106 | 107 | private byte[] ConvertToBytes(string base64) 108 | { 109 | int index = base64.IndexOf("base64,", StringComparison.Ordinal) + 7; 110 | return Convert.FromBase64String(base64.Substring(index)); 111 | } 112 | 113 | public static string CreateSlug(string title) 114 | { 115 | title = title.ToLowerInvariant().Replace(" ", "-"); 116 | title = RemoveDiacritics(title); 117 | title = RemoveReservedUrlCharacters(title); 118 | 119 | if (Storage.GetAllPosts().Any(p => string.Equals(p.Slug, title, StringComparison.OrdinalIgnoreCase))) 120 | throw new HttpException(409, "Already in use"); 121 | 122 | return title.ToLowerInvariant(); 123 | } 124 | 125 | static string RemoveReservedUrlCharacters(string text) 126 | { 127 | var reservedCharacters = new List() { "!", "#", "$", "&", "'", "(", ")", "*", ",", "/", ":", ";", "=", "?", "@", "[", "]", "\"", "%", ".", "<", ">", "\\", "^", "_", "'", "{", "}", "|", "~", "`", "+" }; 128 | 129 | foreach (var chr in reservedCharacters) 130 | { 131 | text = text.Replace(chr, ""); 132 | } 133 | 134 | return text; 135 | } 136 | 137 | static string RemoveDiacritics(string text) 138 | { 139 | var normalizedString = text.Normalize(NormalizationForm.FormD); 140 | var stringBuilder = new StringBuilder(); 141 | 142 | foreach (var c in normalizedString) 143 | { 144 | var unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(c); 145 | if (unicodeCategory != UnicodeCategory.NonSpacingMark) 146 | { 147 | stringBuilder.Append(c); 148 | } 149 | } 150 | 151 | return stringBuilder.ToString().Normalize(NormalizationForm.FormC); 152 | } 153 | 154 | public bool IsReusable 155 | { 156 | get { return false; } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Website/scripts/jquery.hotkeys.js: -------------------------------------------------------------------------------- 1 | /*jslint browser: true*/ 2 | /*jslint jquery: true*/ 3 | 4 | /* 5 | * jQuery Hotkeys Plugin 6 | * Copyright 2010, John Resig 7 | * Dual licensed under the MIT or GPL Version 2 licenses. 8 | * 9 | * Based upon the plugin by Tzury Bar Yochay: 10 | * https://github.com/tzuryby/jquery.hotkeys 11 | * 12 | * Original idea by: 13 | * Binny V A, http://www.openjs.com/scripts/events/keyboard_shortcuts/ 14 | */ 15 | 16 | /* 17 | * One small change is: now keys are passed by object { keys: '...' } 18 | * Might be useful, when you want to pass some other data to your handler 19 | */ 20 | 21 | (function(jQuery) { 22 | 23 | jQuery.hotkeys = { 24 | version: "0.8", 25 | 26 | specialKeys: { 27 | 8: "backspace", 28 | 9: "tab", 29 | 10: "return", 30 | 13: "return", 31 | 16: "shift", 32 | 17: "ctrl", 33 | 18: "alt", 34 | 19: "pause", 35 | 20: "capslock", 36 | 27: "esc", 37 | 32: "space", 38 | 33: "pageup", 39 | 34: "pagedown", 40 | 35: "end", 41 | 36: "home", 42 | 37: "left", 43 | 38: "up", 44 | 39: "right", 45 | 40: "down", 46 | 45: "insert", 47 | 46: "del", 48 | 59: ";", 49 | 61: "=", 50 | 96: "0", 51 | 97: "1", 52 | 98: "2", 53 | 99: "3", 54 | 100: "4", 55 | 101: "5", 56 | 102: "6", 57 | 103: "7", 58 | 104: "8", 59 | 105: "9", 60 | 106: "*", 61 | 107: "+", 62 | 109: "-", 63 | 110: ".", 64 | 111: "/", 65 | 112: "f1", 66 | 113: "f2", 67 | 114: "f3", 68 | 115: "f4", 69 | 116: "f5", 70 | 117: "f6", 71 | 118: "f7", 72 | 119: "f8", 73 | 120: "f9", 74 | 121: "f10", 75 | 122: "f11", 76 | 123: "f12", 77 | 144: "numlock", 78 | 145: "scroll", 79 | 173: "-", 80 | 186: ";", 81 | 187: "=", 82 | 188: ",", 83 | 189: "-", 84 | 190: ".", 85 | 191: "/", 86 | 192: "`", 87 | 219: "[", 88 | 220: "\\", 89 | 221: "]", 90 | 222: "'" 91 | }, 92 | 93 | shiftNums: { 94 | "`": "~", 95 | "1": "!", 96 | "2": "@", 97 | "3": "#", 98 | "4": "$", 99 | "5": "%", 100 | "6": "^", 101 | "7": "&", 102 | "8": "*", 103 | "9": "(", 104 | "0": ")", 105 | "-": "_", 106 | "=": "+", 107 | ";": ": ", 108 | "'": "\"", 109 | ",": "<", 110 | ".": ">", 111 | "/": "?", 112 | "\\": "|" 113 | }, 114 | 115 | // excludes: button, checkbox, file, hidden, image, password, radio, reset, search, submit, url 116 | textAcceptingInputTypes: [ 117 | "text", "password", "number", "email", "url", "range", "date", "month", "week", "time", "datetime", 118 | "datetime-local", "search", "color", "tel"], 119 | 120 | // default input types not to bind to unless bound directly 121 | textInputTypes: /textarea|input|select/i, 122 | 123 | options: { 124 | filterInputAcceptingElements: true, 125 | filterTextInputs: true, 126 | filterContentEditable: true 127 | } 128 | }; 129 | 130 | function keyHandler(handleObj) { 131 | if (typeof handleObj.data === "string") { 132 | handleObj.data = { 133 | keys: handleObj.data 134 | }; 135 | } 136 | 137 | // Only care when a possible input has been specified 138 | if (!handleObj.data || !handleObj.data.keys || typeof handleObj.data.keys !== "string") { 139 | return; 140 | } 141 | 142 | var origHandler = handleObj.handler, 143 | keys = handleObj.data.keys.toLowerCase().split(" "); 144 | 145 | handleObj.handler = function(event) { 146 | // Don't fire in text-accepting inputs that we didn't directly bind to 147 | if (this !== event.target && 148 | (jQuery.hotkeys.options.filterInputAcceptingElements && 149 | jQuery.hotkeys.textInputTypes.test(event.target.nodeName) || 150 | (jQuery.hotkeys.options.filterContentEditable && jQuery(event.target).attr('contenteditable')) || 151 | (jQuery.hotkeys.options.filterTextInputs && 152 | jQuery.inArray(event.target.type, jQuery.hotkeys.textAcceptingInputTypes) > -1))) { 153 | return; 154 | } 155 | 156 | var special = event.type !== "keypress" && jQuery.hotkeys.specialKeys[event.which], 157 | character = String.fromCharCode(event.which).toLowerCase(), 158 | modif = "", 159 | possible = {}; 160 | 161 | jQuery.each(["alt", "ctrl", "shift"], function(index, specialKey) { 162 | 163 | if (event[specialKey + 'Key'] && special !== specialKey) { 164 | modif += specialKey + '+'; 165 | } 166 | }); 167 | 168 | // metaKey is triggered off ctrlKey erronously 169 | if (event.metaKey && !event.ctrlKey && special !== "meta") { 170 | modif += "meta+"; 171 | } 172 | 173 | if (event.metaKey && special !== "meta" && modif.indexOf("alt+ctrl+shift+") > -1) { 174 | modif = modif.replace("alt+ctrl+shift+", "hyper+"); 175 | } 176 | 177 | if (special) { 178 | possible[modif + special] = true; 179 | } 180 | else { 181 | possible[modif + character] = true; 182 | possible[modif + jQuery.hotkeys.shiftNums[character]] = true; 183 | 184 | // "$" can be triggered as "Shift+4" or "Shift+$" or just "$" 185 | if (modif === "shift+") { 186 | possible[jQuery.hotkeys.shiftNums[character]] = true; 187 | } 188 | } 189 | 190 | for (var i = 0, l = keys.length; i < l; i++) { 191 | if (possible[keys[i]]) { 192 | return origHandler.apply(this, arguments); 193 | } 194 | } 195 | }; 196 | } 197 | 198 | jQuery.each(["keydown", "keyup", "keypress"], function() { 199 | jQuery.event.special[this] = { 200 | add: keyHandler 201 | }; 202 | }); 203 | 204 | })(jQuery || this.jQuery || window.jQuery); 205 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniBlog 2 | 3 | A blogging engine based on HTML5 and ASP.NET. For an ASP.NET Core version, see [Miniblog.Core](https://github.com/madskristensen/Miniblog.Core). 4 | 5 | [![Build status](https://ci.appveyor.com/api/projects/status/n78wm50a4a3odecb)](https://ci.appveyor.com/project/madskristensen/miniblog) 6 | 7 | [![Deploy to Azure](http://azuredeploy.net/deploybutton.png)](https://azuredeploy.net/) 8 | 9 | __Live demo__: http://miniblog.azurewebsites.net/ 10 | Username: _demo_ 11 | Password: _demo_ 12 | 13 | ### Custom theme 14 | In search for custom designed themes for MiniBlog? [Click here](https://francis.bio/miniblog-themes/). 15 | 16 | ## Simple, flexible and powerful 17 | 18 | A minimal, yet full featured blog engine using ASP.NET Razor Web Pages. 19 | Perfect for the blogger who wants to selfhost a blog. 20 | 21 | ### Features 22 | 23 | * Best-in-class __performance__ 24 | * Gets a perfect score of 100/100 on Google Page Speed 25 | * Uses __CDN__ for Bootstrap and jQuery in release mode (debug="false") 26 | * Easy setting for serving static files from another domain. 27 | * Supports the ASP.NET [Reverse Proxy](https://github.com/madskristensen/ReverseProxyCDN) 28 | * __Open Live Writer__ (OLW) support 29 | * Optimized for OLW 30 | * Assumes OLW is the main way to write posts 31 | * You don't have to use OLW (but you should) 32 | * RSS and ATOM __feeds__ 33 | * Schedule posts to be published on a future date 34 | * Get feedback on an unpublished post by sending a preview link 35 | * __SEO__ optimized 36 | * Uses HTML 5 __microdata__ to add semantic meaning 37 | * Support for __robots.txt__ and __sitemap.xml__ 38 | * Theming support 39 | * Based on Bootstrap themes. Makes it easy to customize your blog 40 | * Comes with a one-column, two-column, and off-canvas theme 41 | * __No database__ required 42 | * Uses the same XML format as BlogEngine.NET 43 | * Move your existing blog to MiniBlog using [MiniBlog Formatter](https://github.com/madskristensen/MiniBlogFormatter) 44 | * __Inline editing__ of blog posts 45 | * Comments support 46 | * __Gravatar__ support 47 | * Can easily be replaced by 3rd-party commenting system 48 | * __Drag 'n drop__ images to upload 49 | * Automatically __optimizes uploaded images__ 50 | * Uses latest technologies 51 | * __OpenGraph__ enabled 52 | * Based on jQuery and Bootstrap 53 | * Best-in-class __accessibility__ 54 | * __Mobile__ friendly 55 | * Works on any host including __Windows Azure__ Websites 56 | 57 | ### Why another blog engine? 58 | 7 years have passed since I started the [BlogEngine.NET](http://dotnetblogengine.net) project. 59 | It was using cutting edge technology for its time and quickly became the 60 | most popular blogging platform using ASP.NET. 61 | 62 | The MiniBlog was born as a test to see what a modern blog engine could 63 | look like today with the latest ASP.NET and HTML 5 technologies. Just like 64 | with BlogEngine.NET, the goal was to see how small and simple such a 65 | blog engine could be. 66 | 67 | This is the result. 68 | 69 | ### Connecting with Open Live Writer (OLW) 70 | 71 | To connect to MiniBlog with Open Live Writer: 72 | 73 | - Launch Open Live Writer 74 | 75 | - If you have not used Open Live Writer to connect to a blog you will get a dialog window asking you to specify what blog service you use. If you have already connected Open Live Writer to a blog, you can go to _Blogs -> Add blog account..._ and get to the same dialog window. 76 | 77 | - In the __What blog service do you use?__ dialog window you will tick the _Other services_ radio option and click next. 78 | 79 | - The __Add a blog account__ dialog window will ask you for the web address of your blog, the username and password. The web address is the root address of your site. For example, use http://miniblog.azurewebsites.net/ for the live demo site. 80 | 81 | - The __Download Blog Theme__ dialog window will let you know Open Live Writer can download your blog theme if you allow it to publish a temporary post. Selecting yes will allow you to view how your posts will look directly from the Open Live Writer editor. 82 | 83 | - The __Select blog type__ dialog window will let you know Open Live Writer was not able to detect your blog type. It will ask you for the type of blog and the remote posting URL. 84 | Type of blog that you are using: _Metaweblog API_ 85 | Remote posting URL for your blog: _http://<root-address>/metaweblog_ 86 | Click next. 87 | 88 | - The __Your blog has been set up__ dialog window will let you give your blog a nickname for the Open Live Writer instance. Change that if you want and click finish to get to posting! 89 | 90 | Open Live Writer can be downloaded at: 91 | [https://openlivewriter.com/](https://openlivewriter.com/) 92 | 93 | ### Configuring MiniBlog as Virtual Application 94 | 95 | MiniBlog is very compact and can be configured as a Virtual Application so you'd be able to use it alongside your existing websites. 96 | For example if you've got a running ASP.NET website at `http://yourexamplesite.com/` and you want to setup a blog under `/blog/` path, you could setup `http://yourexamplesite.com/blog/` with a few simple tweaks in web.config settings: 97 | 98 | - Set `blog:path` element of `appSettings` to the virtual path that you've configured for MiniBlog. Example with path `blog` 99 | 100 | ```xml 101 | 102 | ``` 103 | 104 | - Update the `path` attribute of all the `` in web.config. Example with path `blog` 105 | 106 | ```xml 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | ``` 127 | 128 | After changing the config all that is left is configuring a Virtual Application with the same path(ex. `blog`) inside your IIS website. -------------------------------------------------------------------------------- /Website/app_code/handlers/MetaWeblogHandler.cs: -------------------------------------------------------------------------------- 1 | using CookComputing.XmlRpc; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Configuration; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Web.Security; 8 | 9 | public interface IMetaWeblog 10 | { 11 | #region MetaWeblog API 12 | 13 | [XmlRpcMethod("metaWeblog.newPost")] 14 | string AddPost(string blogid, string username, string password, Post post, bool publish); 15 | 16 | [XmlRpcMethod("metaWeblog.editPost")] 17 | bool UpdatePost(string postid, string username, string password, Post post, bool publish); 18 | 19 | [XmlRpcMethod("metaWeblog.getPost")] 20 | object GetPost(string postid, string username, string password); 21 | 22 | [XmlRpcMethod("metaWeblog.getCategories")] 23 | object[] GetCategories(string blogid, string username, string password); 24 | 25 | [XmlRpcMethod("metaWeblog.getRecentPosts")] 26 | object[] GetRecentPosts(string blogid, string username, string password, int numberOfPosts); 27 | 28 | [XmlRpcMethod("metaWeblog.newMediaObject")] 29 | object NewMediaObject(string blogid, string username, string password, MediaObject mediaObject); 30 | 31 | #endregion 32 | 33 | #region Blogger API 34 | 35 | [XmlRpcMethod("blogger.deletePost")] 36 | [return: XmlRpcReturnValue(Description = "Returns true.")] 37 | bool DeletePost(string key, string postid, string username, string password, bool publish); 38 | 39 | [XmlRpcMethod("blogger.getUsersBlogs")] 40 | object[] GetUsersBlogs(string key, string username, string password); 41 | 42 | #endregion 43 | } 44 | 45 | public class MetaWeblogHandler : XmlRpcService, IMetaWeblog 46 | { 47 | string IMetaWeblog.AddPost(string blogid, string username, string password, Post post, bool publish) 48 | { 49 | ValidateUser(username, password); 50 | 51 | if (!string.IsNullOrWhiteSpace(post.Slug)) 52 | { 53 | post.Slug = PostHandler.CreateSlug(post.Slug); 54 | } 55 | else 56 | { 57 | post.Slug = PostHandler.CreateSlug(post.Title); 58 | } 59 | 60 | post.IsPublished = publish; 61 | Storage.Save(post); 62 | 63 | return post.ID; 64 | } 65 | 66 | bool IMetaWeblog.UpdatePost(string postid, string username, string password, Post post, bool publish) 67 | { 68 | ValidateUser(username, password); 69 | 70 | Post match = Storage.GetAllPosts().FirstOrDefault(p => p.ID == postid); 71 | 72 | if (match != null) 73 | { 74 | match.Title = post.Title; 75 | match.Excerpt = post.Excerpt; 76 | match.Content = post.Content; 77 | 78 | if (!string.Equals(match.Slug, post.Slug, StringComparison.OrdinalIgnoreCase)) 79 | match.Slug = PostHandler.CreateSlug(post.Slug); 80 | 81 | match.Categories = post.Categories; 82 | match.IsPublished = publish; 83 | 84 | Storage.Save(match); 85 | } 86 | 87 | return match != null; 88 | } 89 | 90 | bool IMetaWeblog.DeletePost(string key, string postid, string username, string password, bool publish) 91 | { 92 | ValidateUser(username, password); 93 | 94 | Post post = Storage.GetAllPosts().FirstOrDefault(p => p.ID == postid); 95 | 96 | if (post != null) 97 | { 98 | Storage.Delete(post); 99 | } 100 | 101 | return post != null; 102 | } 103 | 104 | object IMetaWeblog.GetPost(string postid, string username, string password) 105 | { 106 | ValidateUser(username, password); 107 | 108 | Post post = Storage.GetAllPosts().FirstOrDefault(p => p.ID == postid); 109 | 110 | if (post == null) 111 | throw new XmlRpcFaultException(0, "Post does not exist"); 112 | 113 | return new 114 | { 115 | description = post.Content, 116 | title = post.Title, 117 | dateCreated = post.PubDate, 118 | wp_slug = post.Slug, 119 | categories = post.Categories.ToArray(), 120 | postid = post.ID, 121 | mt_excerpt = post.Excerpt ?? "" 122 | }; 123 | } 124 | 125 | object[] IMetaWeblog.GetRecentPosts(string blogid, string username, string password, int numberOfPosts) 126 | { 127 | ValidateUser(username, password); 128 | 129 | List list = new List(); 130 | 131 | foreach (var post in Storage.GetAllPosts().Take(numberOfPosts)) 132 | { 133 | var info = new 134 | { 135 | description = post.Content, 136 | title = post.Title, 137 | dateCreated = post.PubDate, 138 | wp_slug = post.Slug, 139 | postid = post.ID 140 | }; 141 | 142 | list.Add(info); 143 | } 144 | 145 | return list.ToArray(); 146 | } 147 | 148 | object[] IMetaWeblog.GetCategories(string blogid, string username, string password) 149 | { 150 | ValidateUser(username, password); 151 | 152 | var categories = Blog.GetCategories(); 153 | 154 | var list = new List(); 155 | 156 | foreach ( string category in categories.Keys ) 157 | { 158 | list.Add(new { title = category }); 159 | } 160 | 161 | return list.ToArray(); 162 | } 163 | 164 | object IMetaWeblog.NewMediaObject(string blogid, string username, string password, MediaObject media) 165 | { 166 | ValidateUser(username, password); 167 | 168 | string path = Blog.SaveFileToDisk(media.bits, Path.GetExtension(media.name)); 169 | 170 | return new { url = path }; 171 | } 172 | 173 | object[] IMetaWeblog.GetUsersBlogs(string key, string username, string password) 174 | { 175 | ValidateUser(username, password); 176 | 177 | return new[] 178 | { 179 | new 180 | { 181 | blogid = "1", 182 | blogName = ConfigurationManager.AppSettings.Get("blog:name"), 183 | url = Context.Request.Url.Scheme + "://" + Context.Request.Url.Authority 184 | } 185 | }; 186 | } 187 | 188 | private void ValidateUser(string username, string password) 189 | { 190 | if (!FormsAuthentication.Authenticate(username, password)) 191 | { 192 | throw new XmlRpcFaultException(0, "User is not valid!"); 193 | } 194 | } 195 | } 196 | 197 | [XmlRpcMissingMapping(MappingAction.Ignore)] 198 | public struct MediaObject 199 | { 200 | public string name; 201 | public string type; 202 | public byte[] bits; 203 | } 204 | -------------------------------------------------------------------------------- /Website/app_code/code/Storage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Web; 5 | using System.Web.Hosting; 6 | using System.Xml.Linq; 7 | using System.Xml.XPath; 8 | 9 | public static class Storage 10 | { 11 | private static string _folder = HostingEnvironment.MapPath("~/posts/"); 12 | 13 | public static List GetAllPosts() 14 | { 15 | if (HttpRuntime.Cache["posts"] == null) 16 | LoadPosts(); 17 | 18 | if (HttpRuntime.Cache["posts"] != null) 19 | { 20 | return (List)HttpRuntime.Cache["posts"]; 21 | } 22 | return new List(); 23 | } 24 | 25 | // Can this be done async? 26 | public static void Save(Post post) 27 | { 28 | string file = Path.Combine(_folder, post.ID + ".xml"); 29 | post.LastModified = DateTime.UtcNow; 30 | 31 | XDocument doc = new XDocument( 32 | new XElement("post", 33 | new XElement("title", post.Title), 34 | new XElement("slug", post.Slug), 35 | new XElement("author", post.Author), 36 | new XElement("pubDate", post.PubDate.ToString("yyyy-MM-dd HH:mm:ss")), 37 | new XElement("lastModified", post.LastModified.ToString("yyyy-MM-dd HH:mm:ss")), 38 | new XElement("excerpt", post.Excerpt), 39 | new XElement("content", post.Content), 40 | new XElement("ispublished", post.IsPublished), 41 | new XElement("categories", string.Empty), 42 | new XElement("comments", string.Empty) 43 | )); 44 | 45 | XElement categories = doc.XPathSelectElement("post/categories"); 46 | foreach (string category in post.Categories) 47 | { 48 | categories.Add(new XElement("category", category)); 49 | } 50 | 51 | XElement comments = doc.XPathSelectElement("post/comments"); 52 | foreach (Comment comment in post.Comments) 53 | { 54 | comments.Add( 55 | new XElement("comment", 56 | new XElement("author", comment.Author), 57 | new XElement("email", comment.Email), 58 | new XElement("website", comment.Website), 59 | new XElement("ip", comment.Ip), 60 | new XElement("userAgent", comment.UserAgent), 61 | new XElement("date", comment.PubDate.ToString("yyyy-MM-dd HH:m:ss")), 62 | new XElement("content", comment.Content), 63 | new XAttribute("isAdmin", comment.IsAdmin), 64 | new XAttribute("isApproved", comment.IsApproved), 65 | new XAttribute("id", comment.ID) 66 | )); 67 | } 68 | 69 | if (!File.Exists(file)) // New post 70 | { 71 | var posts = GetAllPosts(); 72 | posts.Insert(0, post); 73 | posts.Sort((p1, p2) => p2.PubDate.CompareTo(p1.PubDate)); 74 | HttpRuntime.Cache.Insert("posts", posts); 75 | } 76 | else 77 | { 78 | Blog.ClearStartPageCache(); 79 | } 80 | 81 | doc.Save(file); 82 | } 83 | 84 | public static void Delete(Post post) 85 | { 86 | var posts = GetAllPosts(); 87 | string file = Path.Combine(_folder, post.ID + ".xml"); 88 | File.Delete(file); 89 | posts.Remove(post); 90 | Blog.ClearStartPageCache(); 91 | } 92 | 93 | private static void LoadPosts() 94 | { 95 | if (!Directory.Exists(_folder)) 96 | Directory.CreateDirectory(_folder); 97 | 98 | List list = new List(); 99 | 100 | // Can this be done in parallel to speed it up? 101 | foreach (string file in Directory.EnumerateFiles(_folder, "*.xml", SearchOption.TopDirectoryOnly)) 102 | { 103 | XElement doc = XElement.Load(file); 104 | 105 | Post post = new Post() 106 | { 107 | ID = Path.GetFileNameWithoutExtension(file), 108 | Title = ReadValue(doc, "title"), 109 | Author = ReadValue(doc, "author"), 110 | Excerpt = ReadValue(doc, "excerpt"), 111 | Content = ReadValue(doc, "content"), 112 | Slug = ReadValue(doc, "slug").ToLowerInvariant(), 113 | PubDate = DateTime.Parse(ReadValue(doc, "pubDate")), 114 | LastModified = DateTime.Parse(ReadValue(doc, "lastModified", DateTime.Now.ToString())), 115 | IsPublished = bool.Parse(ReadValue(doc, "ispublished", "true")), 116 | }; 117 | 118 | LoadCategories(post, doc); 119 | LoadComments(post, doc); 120 | list.Add(post); 121 | } 122 | 123 | if (list.Count > 0) 124 | { 125 | list.Sort((p1, p2) => p2.PubDate.CompareTo(p1.PubDate)); 126 | HttpRuntime.Cache.Insert("posts", list); 127 | } 128 | } 129 | 130 | private static void LoadCategories(Post post, XElement doc) 131 | { 132 | XElement categories = doc.Element("categories"); 133 | if (categories == null) 134 | return; 135 | 136 | List list = new List(); 137 | 138 | foreach (var node in categories.Elements("category")) 139 | { 140 | list.Add(node.Value); 141 | } 142 | 143 | post.Categories = list.ToArray(); 144 | } 145 | private static void LoadComments(Post post, XElement doc) 146 | { 147 | var comments = doc.Element("comments"); 148 | 149 | if (comments == null) 150 | return; 151 | 152 | foreach (var node in comments.Elements("comment")) 153 | { 154 | Comment comment = new Comment() 155 | { 156 | ID = ReadAttribute(node, "id"), 157 | Author = ReadValue(node, "author"), 158 | Email = ReadValue(node, "email"), 159 | Website = ReadValue(node, "website"), 160 | Ip = ReadValue(node, "ip"), 161 | UserAgent = ReadValue(node, "userAgent"), 162 | IsAdmin = bool.Parse(ReadAttribute(node, "isAdmin", "false")), 163 | IsApproved = bool.Parse(ReadAttribute(node, "isApproved", "true")), 164 | Content = ReadValue(node, "content").Replace("\n", "
"), 165 | PubDate = DateTime.Parse(ReadValue(node, "date", "2000-01-01")), 166 | }; 167 | 168 | post.Comments.Add(comment); 169 | } 170 | } 171 | 172 | private static string ReadValue(XElement doc, XName name, string defaultValue = "") 173 | { 174 | if (doc.Element(name) != null) 175 | return doc.Element(name).Value; 176 | 177 | return defaultValue; 178 | } 179 | 180 | private static string ReadAttribute(XElement element, XName name, string defaultValue = "") 181 | { 182 | if (element.Attribute(name) != null) 183 | return element.Attribute(name).Value; 184 | 185 | return defaultValue; 186 | } 187 | } -------------------------------------------------------------------------------- /Website/app_code/handlers/CommentHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Configuration; 3 | using System.Linq; 4 | using System.Net.Mail; 5 | using System.Text.RegularExpressions; 6 | using System.Web; 7 | using System.Web.Security; 8 | using System.Web.WebPages; 9 | 10 | public class CommentHandler : IHttpHandler 11 | { 12 | public void ProcessRequest(HttpContext context) 13 | { 14 | Post post = Storage.GetAllPosts().FirstOrDefault(p => p.ID == context.Request["postId"]); 15 | 16 | if (post == null) 17 | throw new HttpException(404, "The post does not exist"); 18 | 19 | string mode = context.Request["mode"]; 20 | 21 | if (mode == "save" && context.Request.HttpMethod == "POST") 22 | { 23 | Save(context, post); 24 | } 25 | else if (mode == "delete") 26 | { 27 | Delete(context, post); 28 | } 29 | else if (mode == "approve") 30 | { 31 | Approve(context, post); 32 | } 33 | } 34 | 35 | private static void Save(HttpContext context, Post post) 36 | { 37 | Blog.ValidateToken(context); 38 | 39 | if (!post.AreCommentsOpen(new HttpContextWrapper(context))) 40 | throw new HttpException(403, "The data token doesn't match or comments are closed"); 41 | 42 | string name = context.Request.Form["name"]; 43 | string email = context.Request.Form["email"]; 44 | string website = context.Request.Form["website"]; 45 | string content = context.Request.Form["content"]; 46 | 47 | Validate(name, email, content); 48 | 49 | Comment comment = new Comment() 50 | { 51 | Author = name.Trim(), 52 | Email = email.Trim(), 53 | Website = GetUrl(website), 54 | Ip = context.Request.UserHostAddress, 55 | UserAgent = context.Request.UserAgent, 56 | IsAdmin = context.User.Identity.IsAuthenticated, 57 | Content = HttpUtility.HtmlEncode(content.Trim()).Replace("\n", "
"), 58 | IsApproved = !Blog.ModerateComments, 59 | }; 60 | 61 | post.Comments.Add(comment); 62 | Storage.Save(post); 63 | 64 | if (!context.User.Identity.IsAuthenticated) 65 | { 66 | MailMessage mail = GenerateEmail(comment, post, context.Request); 67 | System.Threading.ThreadPool.QueueUserWorkItem((s) => SendEmail(mail)); 68 | } 69 | 70 | RenderComment(context, comment); 71 | } 72 | 73 | private static void RenderComment(HttpContext context, Comment comment) 74 | { 75 | var page = (WebPage)WebPageBase.CreateInstanceFromVirtualPath("~/themes/" + Blog.Theme + "/comment.cshtml"); 76 | page.Context = new HttpContextWrapper(context); 77 | page.ExecutePageHierarchy(new WebPageContext(page.Context, page: null, model: comment), context.Response.Output); 78 | } 79 | 80 | private static void SendEmail(MailMessage mail) 81 | { 82 | try 83 | { 84 | using (SmtpClient client = new SmtpClient()) 85 | { 86 | client.Send(mail); 87 | mail.Dispose(); 88 | } 89 | } 90 | catch 91 | { } 92 | } 93 | 94 | private static MailMessage GenerateEmail(Comment comment, Post post, HttpRequest request) 95 | { 96 | MailMessage mail = new MailMessage(); 97 | mail.From = new MailAddress(comment.Email, comment.Author); 98 | mail.ReplyToList.Add(comment.Email); 99 | mail.To.Add(ConfigurationManager.AppSettings.Get("blog:email")); 100 | mail.Subject = "Blog comment: " + post.Title; 101 | mail.IsBodyHtml = true; 102 | 103 | string absoluteUrl = request.Url.Scheme + "://" + request.Url.Authority; 104 | string deleteUrl = absoluteUrl + request.RawUrl + "?postId=" + post.ID + "&commentId=" + comment.ID + "&mode=delete"; 105 | string approveUrl = absoluteUrl + request.RawUrl + "?postId=" + post.ID + "&commentId=" + comment.ID + "&mode=approve"; 106 | mail.Body = "
" + 107 | comment.Author + " on " + post.Title + ":

" + 108 | comment.Content + "

" + 109 | (Blog.ModerateComments ? "Approve comment | " : string.Empty) + 110 | "Delete comment" + 111 | "


" + 112 | "Website: " + comment.Website + "
" + 113 | "E-mail: " + comment.Email + "
" + 114 | "IP-address: " + comment.Ip + 115 | "
"; 116 | return mail; 117 | } 118 | 119 | private static void Validate(string name, string email, string content) 120 | { 121 | bool isName = !string.IsNullOrEmpty(name); 122 | bool isMail = !string.IsNullOrEmpty(email) && Regex.IsMatch(email, @"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$"); 123 | bool isContent = !string.IsNullOrEmpty(content); 124 | 125 | if (!isName || !isMail || !isContent) 126 | { 127 | if (!isName) 128 | HttpContext.Current.Response.Status = "403 Please enter a valid name"; 129 | else if (!isMail) 130 | HttpContext.Current.Response.Status = "403 Please enter a valid e-mail address"; 131 | else if (!isContent) 132 | HttpContext.Current.Response.Status = "403 Please enter a valid comment"; 133 | 134 | HttpContext.Current.Response.End(); 135 | } 136 | } 137 | 138 | private static string GetUrl(string website) 139 | { 140 | if (!website.Contains("://")) 141 | website = "http://" + website; 142 | 143 | Uri url; 144 | if (Uri.TryCreate(website, UriKind.Absolute, out url)) 145 | return url.ToString(); 146 | 147 | return string.Empty; 148 | } 149 | 150 | private static void Delete(HttpContext context, Post post) 151 | { 152 | if (!context.User.Identity.IsAuthenticated) 153 | { 154 | FormsAuthentication.RedirectToLoginPage(); 155 | context.Response.End(); 156 | } 157 | 158 | Comment comment = GetComment(context, post); 159 | 160 | post.Comments.Remove(comment); 161 | Storage.Save(post); 162 | 163 | RedirectOnGET(context, post); 164 | } 165 | 166 | private static void Approve(HttpContext context, Post post) 167 | { 168 | if (!context.User.Identity.IsAuthenticated) 169 | { 170 | FormsAuthentication.RedirectToLoginPage(); 171 | context.Response.End(); 172 | } 173 | 174 | Comment comment = GetComment(context, post); 175 | 176 | comment.IsApproved = true; 177 | Storage.Save(post); 178 | 179 | RedirectOnGET(context, post); 180 | } 181 | 182 | private static Comment GetComment(HttpContext context, Post post) 183 | { 184 | string commentId = context.Request["commentId"]; 185 | Comment comment = post.Comments.FirstOrDefault(c => c.ID == commentId); 186 | 187 | if (comment == null) 188 | throw new HttpException(404, "Comment could not be found"); 189 | 190 | return comment; 191 | } 192 | 193 | private static void RedirectOnGET(HttpContext context, Post post) 194 | { 195 | if (context.Request.HttpMethod == "GET") 196 | context.Response.Redirect(post.AbsoluteUrl.ToString() + "#comments", true); 197 | } 198 | 199 | public bool IsReusable 200 | { 201 | get { return false; } 202 | } 203 | } -------------------------------------------------------------------------------- /Website/Web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /Website/scripts/admin.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | 3 | // #region Helpers 4 | 5 | function ConvertMarkupToValidXhtml(markup) { 6 | var docImplementation = document.implementation; 7 | var htmlDocument = docImplementation.createHTMLDocument("temp"); 8 | var xHtmlDocument = docImplementation.createDocument('http://www.w3.org/1999/xhtml', 'html', null); 9 | var xhtmlBody = xHtmlDocument.createElementNS('http://www.w3.org/1999/xhtml', 'body'); 10 | 11 | htmlDocument.body.innerHTML = "
" + markup + "
"; 12 | 13 | xHtmlDocument.documentElement.appendChild(xhtmlBody); 14 | xHtmlDocument.importNode(htmlDocument.body, true); 15 | xhtmlBody.appendChild(htmlDocument.body.firstChild); 16 | 17 | /
([\s\S]*?)<\/div><\/body>/i.exec(xHtmlDocument.documentElement.innerHTML); 18 | return RegExp.$1; 19 | } 20 | 21 | // #endregion 22 | 23 | var postId, isNew, 24 | txtTitle, txtExcerpt, txtContent, txtMessage, txtImage, chkPublish, 25 | btnNew, btnEdit, btnDelete, btnSave, btnCancel, blogPath, 26 | 27 | editPost = function () { 28 | txtTitle.attr('contentEditable', true); 29 | txtExcerpt.attr('contentEditable', true); 30 | txtExcerpt.css({ minHeight: "100px" }); 31 | txtExcerpt.parent().css('display', 'block'); 32 | txtContent.wysiwyg({ hotKeys: {}, activeToolbarClass: "active" }); 33 | txtContent.css({ minHeight: "400px" }); 34 | txtContent.focus(); 35 | 36 | btnNew.attr("disabled", true); 37 | btnEdit.attr("disabled", true); 38 | btnSave.removeAttr("disabled"); 39 | btnCancel.removeAttr("disabled"); 40 | chkPublish.removeAttr("disabled"); 41 | 42 | showCategoriesForEditing(); 43 | 44 | toggleSourceView(); 45 | 46 | $("#tools").fadeIn().css("display", "inline-block"); 47 | }, 48 | cancelEdit = function () { 49 | if (isNew) { 50 | if (confirm("Do you want to leave this page?")) { 51 | history.back(); 52 | } 53 | } else 54 | { 55 | window.location = window.location.href.split(/\?|#/)[0]; 56 | } 57 | }, 58 | toggleSourceView = function () { 59 | $(".source").bind("click", function () { 60 | var self = $(this); 61 | if (self.attr("data-cmd") === "source") { 62 | self.attr("data-cmd", "design"); 63 | self.addClass("active"); 64 | txtContent.text(txtContent.html()); 65 | } else { 66 | self.attr("data-cmd", "source"); 67 | self.removeClass("active"); 68 | txtContent.html(txtContent.text()); 69 | } 70 | }); 71 | }, 72 | savePost = function (e) { 73 | if ($(".source").attr("data-cmd") === "design") { 74 | $(".source").click(); 75 | } 76 | 77 | txtContent.cleanHtml(); 78 | 79 | var parsedDOM; 80 | 81 | /* IE9 doesn't support text/html MimeType https://github.com/madskristensen/MiniBlog/issues/35 82 | 83 | parsedDOM = new DOMParser().parseFromString(txtContent.html(), 'text/html'); 84 | parsedDOM = new XMLSerializer().serializeToString(parsedDOM); 85 | 86 | /(.*)<\/body>/im.exec(parsedDOM); 87 | parsedDOM = RegExp.$1; 88 | 89 | */ 90 | 91 | /* When its time to drop IE9 support toggle commented region with 92 | the following statement and ConvertMarkupToXhtml function */ 93 | parsedDOM = ConvertMarkupToValidXhtml(txtContent.html()); 94 | 95 | $.post(blogPath + "/post.ashx?mode=save", { 96 | id: postId, 97 | isPublished: chkPublish[0].checked, 98 | title: txtTitle.text().trim(), 99 | excerpt: txtExcerpt.text().trim(), 100 | content: parsedDOM, 101 | categories: getPostCategories(), 102 | __RequestVerificationToken: document.querySelector("input[name=__RequestVerificationToken]").getAttribute("value") 103 | }) 104 | .success(function (data) { 105 | if (isNew) { 106 | location.href = data; 107 | return; 108 | } 109 | 110 | showMessage(true, "The post was saved successfully"); 111 | cancelEdit(e); 112 | }) 113 | .fail(function (data) { 114 | if (data.status === 409) { 115 | showMessage(false, "The title is already in use"); 116 | } else { 117 | showMessage(false, "Something bad happened. Server reported " + data.status + " " + data.statusText); 118 | } 119 | }); 120 | }, 121 | deletePost = function () { 122 | if (confirm("Are you sure you want to delete this post?")) { 123 | $.post(blogPath + "/post.ashx?mode=delete", { id: postId, __RequestVerificationToken: document.querySelector("input[name=__RequestVerificationToken]").getAttribute("value") }) 124 | .success(function () { location.href = blogPath+"/"; }) 125 | .fail(function () { showMessage(false, "Something went wrong. Please try again"); }); 126 | } 127 | }, 128 | showMessage = function (success, message) { 129 | var className = success ? "alert-success" : "alert-error"; 130 | txtMessage.addClass(className); 131 | txtMessage.text(message); 132 | txtMessage.parent().fadeIn(); 133 | 134 | setTimeout(function () { 135 | txtMessage.parent().fadeOut("slow", function () { 136 | txtMessage.removeClass(className); 137 | }); 138 | }, 4000); 139 | }, 140 | getPostCategories = function () { 141 | var categories = ''; 142 | 143 | if ($("#txtCategories").length > 0) { 144 | categories = $("#txtCategories").val(); 145 | } else { 146 | $("ul.categories li a").each(function (index, item) { 147 | if (categories.length > 0) { 148 | categories += ","; 149 | } 150 | categories += $(item).html(); 151 | }); 152 | } 153 | return categories; 154 | }, 155 | showCategoriesForEditing = function () { 156 | var firstItemPassed = false; 157 | var categoriesString = getPostCategories(); 158 | $("ul.categories li").each(function (index, item) { 159 | if (!firstItemPassed) { 160 | firstItemPassed = true; 161 | } else { 162 | $(item).remove(); 163 | } 164 | }); 165 | $("ul.categories").append("
  • "); 166 | $("#txtCategories").val(categoriesString); 167 | }, 168 | showCategoriesForDisplay = function () { 169 | if ($("#txtCategories").length > 0) { 170 | var categoriesArray = $("#txtCategories").val().split(','); 171 | $("#txtCategories").parent().remove(); 172 | 173 | $.each(categoriesArray, function (index, category) { 174 | $("ul.categories").append('
  • ' + category + '
  • '); 175 | }); 176 | } 177 | }; 178 | 179 | postId = $("[itemprop~='blogPost']").attr("data-id"); 180 | 181 | txtTitle = $("[itemprop~='blogPost'] [itemprop~='name']"); 182 | txtExcerpt = $("[itemprop~='blogPost'] [itemprop~='description']"); 183 | txtContent = $("[itemprop~='articleBody']"); 184 | txtMessage = $("#admin .alert"); 185 | txtImage = $("#admin #txtImage"); 186 | 187 | btnNew = $("#btnNew"); 188 | btnEdit = $("#btnEdit"); 189 | btnDelete = $("#btnDelete").bind("click", deletePost); 190 | btnSave = $("#btnSave").bind("click", savePost); 191 | btnCancel = $("#btnCancel").bind("click", cancelEdit); 192 | chkPublish = $("#ispublished").find("input[type=checkbox]"); 193 | blogPath = $("#admin").data("blogPath"); 194 | 195 | isNew = location.pathname.replace(/\//g, "") === blogPath.replace(/\//g, "") + "postnew"; 196 | 197 | $(document).keyup(function (e) { 198 | if (!document.activeElement.isContentEditable) { 199 | if (e.keyCode === 46) { // Delete key 200 | deletePost(); 201 | } else if (e.keyCode === 27) { // ESC key 202 | cancelEdit(); 203 | } 204 | } 205 | }); 206 | 207 | $('.uploadimage').click(function (e) { 208 | e.preventDefault(); 209 | $('#txtImage').click(); 210 | }); 211 | 212 | if (isNew) { 213 | editPost(); 214 | $("#ispublished").fadeIn(); 215 | chkPublish[0].checked = true; 216 | } else if (txtTitle !== null && txtTitle.length === 1 && location.pathname.length > 1) { 217 | if (location.search.indexOf("mode=edit") != -1) 218 | editPost(); 219 | 220 | btnEdit.removeAttr("disabled"); 221 | btnDelete.removeAttr("disabled"); 222 | $("#ispublished").css({ "display": "inline" }); 223 | } 224 | 225 | $(".dropdown-menu > input").click(function (e) { 226 | e.stopPropagation(); 227 | }); 228 | })(jQuery); 229 | -------------------------------------------------------------------------------- /Website/app_code/code/Blog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Web; 8 | using System.Web.Caching; 9 | using System.Web.Helpers; 10 | using System.Web.Hosting; 11 | 12 | public static class Blog 13 | { 14 | static Blog() 15 | { 16 | Theme = ConfigurationManager.AppSettings.Get("blog:theme"); 17 | Title = ConfigurationManager.AppSettings.Get("blog:name"); 18 | Description = ConfigurationManager.AppSettings.Get("blog:description"); 19 | PostsPerPage = int.Parse(ConfigurationManager.AppSettings.Get("blog:postsPerPage")); 20 | DaysToComment = int.Parse(ConfigurationManager.AppSettings.Get("blog:daysToComment")); 21 | Image = ConfigurationManager.AppSettings.Get("blog:image"); 22 | ModerateComments = bool.Parse(ConfigurationManager.AppSettings.Get("blog:moderateComments")); 23 | BlogPath = ConfigurationManager.AppSettings.Get("blog:path"); 24 | } 25 | 26 | public static string Title { get; private set; } 27 | public static string Description { get; private set; } 28 | public static string Theme { get; private set; } 29 | public static string Image { get; private set; } 30 | public static int PostsPerPage { get; private set; } 31 | public static int DaysToComment { get; private set; } 32 | public static bool ModerateComments { get; private set; } 33 | public static string BlogPath { get; private set; } 34 | 35 | public static string CurrentSlug 36 | { 37 | get { return (HttpContext.Current.Request.QueryString["slug"] ?? string.Empty).Trim().ToLowerInvariant(); } 38 | } 39 | 40 | public static string CurrentCategory 41 | { 42 | get { return WebUtility.UrlDecode(HttpContext.Current.Request.QueryString["category"] ?? string.Empty).Trim().ToLowerInvariant(); } 43 | } 44 | 45 | public static bool IsNewPost 46 | { 47 | get 48 | { 49 | return HttpContext.Current.Request.RawUrl.Trim('/') == (!string.IsNullOrWhiteSpace(BlogPath) ? BlogPath + "/" : "") + "post/new"; 50 | } 51 | } 52 | 53 | public static bool IsEditing 54 | { 55 | get 56 | { 57 | return HttpContext.Current.Request.QueryString["mode"] == "edit"; 58 | } 59 | } 60 | 61 | public static Post CurrentPost 62 | { 63 | get 64 | { 65 | if (HttpContext.Current.Items["currentpost"] == null) 66 | { 67 | var post = FindCurrentPost(); 68 | if (post != null) 69 | HttpContext.Current.Items["currentpost"] = post; 70 | } 71 | 72 | return HttpContext.Current.Items["currentpost"] as Post; 73 | } 74 | } 75 | 76 | private static Post FindCurrentPost() 77 | { 78 | if (string.IsNullOrEmpty(CurrentSlug)) 79 | return null; 80 | 81 | var post = GetVisiblePosts().FirstOrDefault(p => p.Slug == CurrentSlug); 82 | if (post == null) 83 | { 84 | var previewId = HttpContext.Current.Request.QueryString["key"]; 85 | if (!string.IsNullOrEmpty(previewId)) 86 | { 87 | post = Storage.GetAllPosts().FirstOrDefault(p => p.Slug == CurrentSlug && p.ID.Equals(previewId, StringComparison.OrdinalIgnoreCase)); 88 | } 89 | } 90 | 91 | return post; 92 | } 93 | 94 | public static string GetNextPage() 95 | { 96 | if (!string.IsNullOrEmpty(CurrentSlug)) 97 | { 98 | var posts = GetVisiblePosts().ToList(); 99 | var current = posts.IndexOf(CurrentPost); 100 | if (current > 0) 101 | return posts[current - 1].Url.ToString(); 102 | } 103 | else if (CurrentPage > 1) 104 | { 105 | return GetPagingUrl(-1); 106 | } 107 | 108 | return null; 109 | } 110 | 111 | public static string GetPrevPage() 112 | { 113 | if (!string.IsNullOrEmpty(CurrentSlug)) 114 | { 115 | var posts = GetVisiblePosts().ToList(); 116 | var current = posts.IndexOf(CurrentPost); 117 | if (current > -1 && posts.Count > current + 1) 118 | return posts[current + 1].Url.ToString(); 119 | } 120 | else if(GetPosts().Count() > PostsPerPage * CurrentPage) 121 | { 122 | return GetPagingUrl(1); 123 | } 124 | 125 | return null; 126 | } 127 | 128 | public static int CurrentPage 129 | { 130 | get 131 | { 132 | int page = 0; 133 | if (int.TryParse(HttpContext.Current.Request.QueryString["page"], out page)) 134 | return page; 135 | 136 | return 1; 137 | } 138 | } 139 | 140 | public static IEnumerable GetPosts(int postsPerPage = 0) 141 | { 142 | var posts = GetVisiblePosts(); 143 | 144 | var category = CurrentCategory; 145 | 146 | if (!string.IsNullOrEmpty(category)) 147 | { 148 | posts = posts.Where(p => p.Categories.Any(c => string.Equals(c, category, StringComparison.OrdinalIgnoreCase))); 149 | } 150 | 151 | if (postsPerPage > 0) 152 | { 153 | posts = posts.Skip(postsPerPage * (CurrentPage - 1)).Take(postsPerPage); 154 | } 155 | 156 | return posts; 157 | } 158 | 159 | public static void ValidateToken(HttpContext context) 160 | { 161 | AntiForgery.Validate(); 162 | } 163 | 164 | public static string SaveFileToDisk(byte[] bytes, string extension) 165 | { 166 | string relative = "~/posts/files/" + Guid.NewGuid(); 167 | 168 | if (string.IsNullOrWhiteSpace(extension)) 169 | extension = ".bin"; 170 | else 171 | extension = "." + extension.Trim('.'); 172 | 173 | relative += extension; 174 | 175 | string file = HostingEnvironment.MapPath(relative); 176 | 177 | File.WriteAllBytes(file, bytes); 178 | 179 | return VirtualPathUtility.ToAbsolute(relative); 180 | } 181 | 182 | public static string GetPagingUrl(int move) 183 | { 184 | string url = "/page/{0}/"; 185 | string category = CurrentCategory; 186 | 187 | if (!string.IsNullOrEmpty(category)) 188 | { 189 | url = "/category/" + HttpUtility.UrlEncode(category.ToLowerInvariant()) + "/" + url; 190 | } 191 | 192 | string relative = string.Format("~" + url, Blog.CurrentPage + move); 193 | return VirtualPathUtility.ToAbsolute(relative); 194 | } 195 | 196 | public static string FingerPrint(string rootRelativePath, string cdnPath = "") 197 | { 198 | if ( HttpContext.Current.Request.IsLocal && String.IsNullOrWhiteSpace( Blog.BlogPath ) ) 199 | return rootRelativePath; 200 | 201 | if (!string.IsNullOrEmpty(cdnPath) && !HttpContext.Current.IsDebuggingEnabled) 202 | return cdnPath; 203 | 204 | if (HttpRuntime.Cache[rootRelativePath] == null) 205 | { 206 | string relative = VirtualPathUtility.ToAbsolute("~" + rootRelativePath); 207 | string absolute = HostingEnvironment.MapPath(relative); 208 | 209 | if (!File.Exists(absolute)) 210 | throw new FileNotFoundException("File not found", absolute); 211 | 212 | DateTime date = File.GetLastWriteTime(absolute); 213 | int index = relative.LastIndexOf('.'); 214 | 215 | string result = ConfigurationManager.AppSettings.Get("blog:cdnUrl") + relative.Insert(index, "_" + date.Ticks); 216 | 217 | HttpRuntime.Cache.Insert(rootRelativePath, result, new CacheDependency(absolute)); 218 | } 219 | 220 | return HttpRuntime.Cache[rootRelativePath] as string; 221 | } 222 | 223 | public static void SetConditionalGetHeaders(DateTime lastModified, HttpContextBase context) 224 | { 225 | HttpResponseBase response = context.Response; 226 | HttpRequestBase request = context.Request; 227 | lastModified = new DateTime(lastModified.Year, lastModified.Month, lastModified.Day, lastModified.Hour, lastModified.Minute, lastModified.Second); 228 | 229 | string incomingDate = request.Headers["If-Modified-Since"]; 230 | 231 | response.Cache.SetLastModified(lastModified); 232 | 233 | DateTime testDate = DateTime.MinValue; 234 | 235 | if (DateTime.TryParse(incomingDate, out testDate) && testDate == lastModified) 236 | { 237 | response.ClearContent(); 238 | response.StatusCode = (int)System.Net.HttpStatusCode.NotModified; 239 | response.SuppressContent = true; 240 | } 241 | } 242 | 243 | public static Dictionary GetCategories() 244 | { 245 | var result = GetVisiblePosts() 246 | .SelectMany(post => post.Categories) 247 | .GroupBy(category => category, (category, items) => new { Category = category, Count = items.Count() }) 248 | .OrderBy(x => x.Category) 249 | .ToDictionary(x => x.Category, x => x.Count); 250 | 251 | return result; 252 | } 253 | 254 | public static IEnumerable GetRecentPosts(int count) 255 | { 256 | return GetVisiblePosts().Take(count).ToList(); 257 | } 258 | 259 | private static IEnumerable GetVisiblePosts() 260 | { 261 | return Storage.GetAllPosts() 262 | .Where(p => ((p.IsPublished && p.PubDate <= DateTime.UtcNow) || HttpContext.Current.User.Identity.IsAuthenticated)); 263 | } 264 | 265 | public static void ClearStartPageCache() 266 | { 267 | HttpResponse.RemoveOutputCacheItem(string.Format("/{0}", BlogPath)); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /Website/scripts/comments.js: -------------------------------------------------------------------------------- 1 | /* globals NodeList, HTMLCollection */ 2 | 3 | (function () { 4 | var postId = null; 5 | 6 | //#region Helpers 7 | 8 | function objectToUrl(obj) { 9 | var string = ''; 10 | 11 | for (var prop in obj) { 12 | if (obj.hasOwnProperty(prop)) { 13 | string += encodeURIComponent(prop) + '=' + encodeURIComponent(obj[prop]) + '&'; 14 | } 15 | } 16 | 17 | string = string.substring(0, string.length - 1).replace(/%20/g, '+'); 18 | 19 | return string; 20 | } 21 | 22 | var AsynObject = AsynObject ? AsynObject : {}; 23 | 24 | AsynObject.ajax = function (url, callback) { 25 | var ajaxRequest = AsynObject.getAjaxRequest(callback); 26 | ajaxRequest.open("GET", url, true); 27 | ajaxRequest.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 28 | ajaxRequest.send(null); 29 | }; 30 | 31 | AsynObject.postAjax = function (url, callback, data) { 32 | var ajaxRequest = AsynObject.getAjaxRequest(callback); 33 | ajaxRequest.open("POST", url, true); 34 | ajaxRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); 35 | ajaxRequest.send(objectToUrl(data)); 36 | }; 37 | 38 | AsynObject.getAjaxRequest = function (callback) { 39 | 40 | var ajaxRequest = new XMLHttpRequest(); 41 | 42 | ajaxRequest.onreadystatechange = function () { 43 | if (ajaxRequest.readyState > 1 && ajaxRequest.status > 0) { 44 | callback(ajaxRequest.readyState, ajaxRequest.status, ajaxRequest.responseText); 45 | } 46 | }; 47 | 48 | return ajaxRequest; 49 | }; 50 | 51 | function hasClass(elem, className) { 52 | return new RegExp(' ' + className + ' ').test(' ' + elem.className + ' '); 53 | } 54 | 55 | function addClass(elem, className) { 56 | if (!hasClass(elem, className)) { 57 | elem.className += ' ' + className; 58 | } 59 | } 60 | 61 | function removeClass(elem, className) { 62 | var newClass = ' ' + elem.className.replace(/[\t\r\n]/g, ' ') + ' '; 63 | if (hasClass(elem, className)) { 64 | while (newClass.indexOf(' ' + className + ' ') >= 0) { 65 | newClass = newClass.replace(' ' + className + ' ', ' '); 66 | } 67 | elem.className = newClass.replace(/^\s+|\s+$/g, ''); 68 | } 69 | } 70 | 71 | function toDOM(htmlString) { 72 | var wrapper = document.createElement('div'); 73 | wrapper.innerHTML = htmlString; 74 | return wrapper.children; 75 | } 76 | 77 | function getParentsByAttribute(element, attr, value) { 78 | var arr = []; 79 | 80 | while (element) { 81 | element = element.parentNode; 82 | if (element.hasAttribute(attr) && element.getAttribute(attr) === value) { 83 | arr.push(element); 84 | } 85 | if (!element.parentNode.parentNode) { 86 | break; 87 | } 88 | } 89 | 90 | return arr; 91 | } 92 | 93 | Element.prototype.remove = function () { 94 | this.parentElement.removeChild(this); 95 | }; 96 | 97 | NodeList.prototype.remove = HTMLCollection.prototype.remove = function () { 98 | for (var i = 0, len = this.length; i < len; i++) { 99 | if (this[i] && this[i].parentElement) { 100 | this[i].parentElement.removeChild(this[i]); 101 | } 102 | } 103 | }; 104 | 105 | function slide(thisObj, direction, callback) { 106 | if (direction === "Up") { 107 | thisObj.style.height = '0px'; 108 | } else { 109 | var clone = thisObj.cloneNode(true); 110 | 111 | clone.style.position = 'absolute'; 112 | clone.style.visibility = 'hidden'; 113 | clone.style.height = 'auto'; 114 | 115 | addClass(clone, 'slideClone col-md-6 col-md-offset-3'); 116 | 117 | document.body.appendChild(clone); 118 | 119 | var slideClone = document.getElementsByClassName("slideClone")[0]; 120 | var newHeight = slideClone.clientHeight; 121 | 122 | slideClone.remove(); 123 | thisObj.style.height = newHeight + 'px'; 124 | if (callback) { 125 | setTimeout(function () { 126 | callback(); 127 | }, 500); 128 | } 129 | } 130 | } 131 | 132 | //#endregion 133 | 134 | var endpoint = "/comment.ashx"; 135 | 136 | function deleteComment(commentId, element) { 137 | 138 | if (confirm("Do you want to delete this comment?")) { 139 | AsynObject.postAjax(endpoint, function (state, status) { 140 | if (state === 4 && status === 200) { 141 | slide(element, "Up", function () { 142 | element.remove(); 143 | }); 144 | return; 145 | } else if (status !== 200) { 146 | alert("Something went wrong. Please try again"); 147 | } 148 | }, { 149 | mode: "delete", 150 | postId: postId, 151 | commentId: commentId, 152 | __RequestVerificationToken: document.querySelector("input[name=__RequestVerificationToken]").getAttribute("value") 153 | }); 154 | } 155 | } 156 | 157 | function approveComment(commentId, element) { 158 | 159 | AsynObject.postAjax(endpoint, function (state, status) { 160 | if (state === 4 && status === 200) { 161 | element.remove(); 162 | return; 163 | } else if (status !== 200) { 164 | alert("Something went wrong. Please try again"); 165 | } 166 | }, { 167 | mode: "approve", 168 | postId: postId, 169 | commentId: commentId, 170 | __RequestVerificationToken: document.querySelector("input[name=__RequestVerificationToken]").getAttribute("value") 171 | }); 172 | } 173 | 174 | function saveComment(name, email, website, content, callback) { 175 | 176 | if (localStorage) { 177 | localStorage.setItem("name", name); 178 | localStorage.setItem("email", email); 179 | localStorage.setItem("website", website); 180 | } 181 | 182 | AsynObject.postAjax(endpoint, function (state, status, data) { 183 | 184 | var elemStatus = document.getElementById("status"); 185 | if (state === 4 && status === 200) { 186 | elemStatus.innerHTML = "Your comment has been added"; 187 | removeClass(elemStatus, "alert-danger"); 188 | addClass(elemStatus, "alert-success"); 189 | 190 | document.getElementById("commentcontent").value = ""; 191 | 192 | var comment = toDOM(data)[0]; 193 | comment.style.height = "0px"; 194 | var elemComments = document.getElementById("comments"); 195 | elemComments.appendChild(comment); 196 | slide(comment, "Down"); 197 | callback(true); 198 | 199 | return; 200 | } else if (status !== 200) { 201 | addClass(elemStatus, "alert-danger"); 202 | elemStatus.innerText = "Unable to add comment"; 203 | callback(false); 204 | } 205 | }, { 206 | mode: "save", 207 | postId: postId, 208 | name: name, 209 | email: email, 210 | website: website, 211 | content: content, 212 | __RequestVerificationToken: document.querySelector("input[name=__RequestVerificationToken]").getAttribute("value") 213 | }); 214 | 215 | } 216 | 217 | function initialize() { 218 | postId = document.querySelector("[itemprop=blogPost]").getAttribute("data-id"); 219 | endpoint = document.getElementById("commentform").getAttribute("data-blog-path") + endpoint; 220 | var email = document.getElementById("commentemail"); 221 | var name = document.getElementById("commentname"); 222 | var website = document.getElementById("commenturl"); 223 | var content = document.getElementById("commentcontent"); 224 | var commentForm = document.getElementById("commentform"); 225 | 226 | var allComments = document.querySelectorAll("[itemprop=comment]"); 227 | for (var i = 0; i < allComments.length; ++i) { 228 | allComments[i].style.height = allComments[i].clientHeight + 'px'; 229 | } 230 | 231 | commentForm.onsubmit = function (e) { 232 | e.preventDefault(); 233 | var button = e.target; 234 | button.setAttribute("disabled", true); 235 | 236 | saveComment(name.value, email.value, website.value, content.value, function () { 237 | button.removeAttribute("disabled"); 238 | }); 239 | }; 240 | 241 | website.addEventListener("keyup", function (e) { 242 | var w = e.target; 243 | if (w.value.trim().length >= 4 && w.value.indexOf("http") === -1) { 244 | w.value = "http://" + w.value; 245 | } 246 | }); 247 | 248 | window.addEventListener("click", function (e) { 249 | var tag = e.target; 250 | 251 | if (hasClass(tag, "deletecomment")) { 252 | var comment = getParentsByAttribute(tag, "itemprop", "comment")[0]; 253 | deleteComment(comment.getAttribute("data-id"), comment); 254 | } 255 | if (hasClass(tag, "approvecomment")) { 256 | var comment = getParentsByAttribute(tag, "itemprop", "comment")[0]; 257 | approveComment(comment.getAttribute("data-id"), tag); 258 | } 259 | }); 260 | 261 | if (localStorage) { 262 | email.value = localStorage.getItem("email"); 263 | website.value = localStorage.getItem("website"); 264 | 265 | if (name.value.length === 0) { 266 | name.value = localStorage.getItem("name"); 267 | } 268 | } 269 | } 270 | 271 | if (document.getElementById("commentform")) { 272 | initialize(); 273 | } 274 | })(); 275 | --------------------------------------------------------------------------------